0x01 前景提要

Veinmind 在最初设计时,采用了插件系统的模式,目的是为了在尽可能多的场景下复用公共功能部分的代码,让工具的开发者尽可能的减少非关键业务的代码编写。

举个例子,在 1.0 版本时,我们提供了本地扫描的整体逻辑,插件仅需要通过注册命令服务,即可生成一个扫描本地镜像/本地容器的 cli 工具, 如:

func scanImage(ctx context.Context, api.Image) {
    // do some thing...
}

scanCommand.AddCommand(
    cmd.MapImageCommand(scanImageCommand, scanImage)
)

这样,使用时只需关心具体的扫描逻辑,也就是scanImage函数内如何对镜像进行操作即可,让开发者的注意力集中在安全扫描能力的建设上去。

同样的,为了实现在各种场景下被复用,我们在 runner 内扩展了扫描对象,这样设计不仅能支持扫描本地镜像,还可以直接扫描远程仓库内的镜像信息;对于 IaC 文件配置,同样支持直接扫描 git 仓库、kubernetes 集群。对于插件来说,无需做出任何修改,解决了每个插件独立的去做认证、下载、存储、删除等等一系列重复行为的问题。

基于这个思路,在 2.0 版本中,我们期望能够帮助开发者和使用者做更多的事情。

我们发现,各插件为了满足能够独立使用,都实现了一个report包用于数据的展示和输出。于是,我们提供了一个报告器模块,让用户只需要将安全事件进行上报,无需再多考虑输出模式和结果的渲染。进一步将开发者的注意力聚焦到 “如何扫描镜像” 的业务上。

0x02 小试牛刀

要实现一个报告器,首先面临两个场景:

  • 插件独立运行时,报告器应该独立输出。
  • 插件被 runner 宿主调用时,报告器应该汇总所有插件的事件结果,统一输出。

对于场景1,我们可以提供一个对象来存储所有被上报的安全事件,并监听到程序运行结束时,运行输出函数,根据用户指定的输出格式参数,进行事件渲染。

由此,我们实现了MapReportCmd方法,通过该方法将报告器相关的操作注入到cmd指令的调用流程,伪代码如下:

func MapReportCmd(c *cmd.Command, service *Service){
    // inject flags 
    c.Flags().StringP("format", "f" ...)
    c.Flags().BoolP("verbose", "v" ...)

    c.PreRun = func() {
        // init service struct...
    }
    if !libService.Hosted() {
        c.PostRun = func(c *cmd.Command, args []string) {
            // do output ...
        }
    }
}

可以发现,我们通过sdkHosted()方法,来判断插件在运行时是否为宿主调用。依此来解决当插件被宿主调用时会被重复输出的问题。

实现了独立运行的输出后,报告器还需要解决场景2:宿主统一输出。

我们通过sdk的服务机制,将上报事件的服务注册到Registry中进行索引,为所有运行的插件提供 report.Service服务,由此,插件与宿主之间即可进行安全事件的上报通信。

0x03 报告器功能如何使用?

当我们独立使用插件进行扫描时,可以发现,所有插件均包含了 -f/--format 参数(即使插件自身代码内没有指定接收该参数),此时可以通过指定该参数快速输出想要的报告格式:(目前支持 cli/html/json)

./veinmind-plugins scan image xxxx -f cli

扫描结果将会输出在控制台。

523bd539-9f46-4682-83e3-1bf7c7fe7fc6

报告器还可以指定多个输出格式,同时生成多份报表:

./veinmind-plugins scan image xxxx -f cli,html,json

1ff8a27d-caf6-4971-90b7-58640d889c58

0x04 自定义插件如何快速接入报告器?

对于插件的开发者,目前仅需要通过MapRerportCmd 函数即可帮助插件快速生成Service实例,并将格式化参数注入到插件的命令中。通过该函数,插件将不再需要关心任何与输出相关的问题。

talk is easy, show me the code

import(
    "github.com/chaitin/veinmind-common-go/service/report"
    "github.com/chaitin/veinmind-common-go/service/report/event"
    "github.com/chaitin/veinmind-common-go/service/report/service"
)
var (
    reportService = &report.Service{}
    rootCommand   = &cmd.Command{}
    scanCommand   = &cmd.Command{
        Use:   "scan",
        Short: "scan mode",
    }
    scanImageCommand   = &cmd.Command{
        Use:   "image",
        Short: "scan mode",
    }
)

func scanImage(c *cmd.Command, image api.Image) error {
    // ...do some scan 
    evt := &event.Event{}
    // now you only need report evt to service,
    // report.Service will render 
    err = reportService.Client.Report(evt)
}

func init() {
    rootCommand.AddCommand(scanCommand)
    // use MapReportCmd inject preRun/postRun 
    // which init reportSerivce and reportOoutput at end
    scanCommand.AddCommand(report.MapReportCmd(
        cmd.MapImageCommand(scanImageCommand, scanImage), 
        reportService
    ))
}

results matching ""

    No results matching ""