Grype 漏洞扫描原理
代码
位于主目录下的main.go中的main函数是程序的入口。main函数调用了cmd包的Execute方法。
程序cmd/cmd.go中的Execute函数执行了rootCmd.Execute()
rootCmd是在同包下的root.go中定义的
rootCmd是一个cobra.Command指针类型的变量。cobra是一个用于构建命令行应用的开源项目,可以先不管它的具体细节。RunE这个属性配置了匹配到命令后程序如何执行。可以看到函数中会返回执行rootExec这个函数的返回值。而ValidArgsFunction指定了一个dockerImageValidArgsFunction, 这是用于grype的代码补全功能。假设我本地有一个nginx的docker镜像,输入grype ngi <tab> <tab>
即可补全成grype nginx
rootExec也位于root.go中。这个函数解析用户在命令行输入的参数。参数数量有可能是0,因为用户可能会通过管道输入软件物料清单数据。比如 echo sbom.json | grype
。可以看到有startWorker这个函数,这个是执行主要逻辑的函数。
startWorker函数267到282行的这一部分检查grype本身是否需要更新
284-319的内容主要是两个go routine的运行. sync.WaitGroup用于等待两个go routine(go routine是异步的, 由go这个关键字标识)执行结束。第一个函数主要是检查漏洞数据库是否需要更新,并加载漏洞数据库。第二个函数的作用是收集包,赋值给packages变量,代码的331行会使用到packages变量进行漏洞查找:allMatches := grype.FindVulnerabilitiesForPackage(provider, context.Distro, matchers, packages)
从上图我们可以看到第二个函数调用了pkg.Provide。这个函数位于grype/pkg/provider.go。可以看到这个函数会使用syft来获得packages。通过阅读syftSBOMProvider和syftProvider,发现grype可以接受文件类型的或者是容器镜像名作为参数。不管是哪一个类型,都会使用到syft(此时作为一个go 模块进行使用,和syft cli不是一个概念)。
package变量的结构如下图所示
gather packages之后就会执行allMatches := grype.FindVulnerabilitiesForPackage(provider, context.Distro, matchers, packages)
来寻找所有的漏洞。