我们先分析下analyze-local-images这个客户端,然后在下一篇文章中在分析Clair。这样我们可以很清楚的了解到,analyze-local-images向Clair发送了什么数据。
首先analyze-local-images定义了几个全局变量。代码如下。
const (
postLayerURI = "/v1/layers"
getLayerFeaturesURI = "/v1/layers/%s?vulnerabilities"
httpPort = 9279
)
var (
flagEndpoint = flag.String("endpoint", "http://127.0.0.1:6060", "Address to Clair API")
flagMyAddress = flag.String("my-address", "127.0.0.1", "Address from the point of view of Clair")
flagMinimumSeverity = flag.String("minimum-severity", "Negligible", "Minimum severity of vulnerabilities to show (Unknown, Negligible, Low, Medium, High, Critical, Defcon1)")
flagColorMode = flag.String("color", "auto", "Colorize the output (always, auto, never)")
)
在使用analyze-local-images时,我们可以指定一些参数。
analyze-local-images -endpoint "http://10.28.182.152:6060" -my-address "10.28.182.151" nginx:latest
其中,endpoint为clair主机的ip地址。my-address为运行analyze-local-images这个客户端的地址。
postLayerURI是向clair API V1发送数据库的路由,getLayerFeaturesURI是从clair API V1获取漏洞信息的路由。
analyze-local-images在主函数调用intMain()函数,而intMain会首先去解析用户的输入参数。例如刚才的endpoint。
func intMain() int {
//解析命令行参数,并给刚才定义的一些全局变量赋值。
......
//创建一个临时目录
tmpPath, err := ioutil.TempDir("", "analyze-local-image-")
//在/tmp目录下创建以analyze-local-image-开头的文件夹。
//为了能够清楚的观察/tmp下目录的变化,我们将defer os.RemoveAll(tmpPath)这句注释掉,再重新编译。
......
//调用AnalyzeLocalImage方法分析镜像
go func() {
analyzeCh <- AnalyzeLocalImage(imageName, minSeverity, *flagEndpoint, *flagMyAddress, tmpPath)
}()
}
程序执行流程是main()->intMain()->AnalyzeLocalImage()。现在我们跟进入AnalyzeLocalImage()
镜像被解压到tmp目录下的目录结构如下。
func AnalyzeLocalImage(imageName string, minSeverity database.Severity, endpoint, myAddress, tmpPath string) error {
//保存镜像到tmp目录下
//调用save方法
//save方法的原理就是使用docker save 镜像名先将镜像打包成tar文件
//然后使用tar命令将文件再解压到tmp文件中。
err := save(imageName, tmpPath)
.......
//调用historyFromManifest方法,读取manifest.json文件获取每一层的id名,保存在layerIDs中。
//如果从manifest.json文件中获取不到,则读取历史记录
layerIDs, err := historyFromManifest(tmpPath)
if err != nil {
layerIDs, err = historyFromCommand(imageName)
}
......
//如果clair不在本机,则在analyze-local-images上开启HTTP服务,默认端口为9279
......
//分析每一层,既将每一层下的layer.tar文件发送到clair服务端
err = analyzeLayer(endpoint, tmpPath+"/"+layerIDs[i]+"/layer.tar", layerIDs[i], layerIDs[i-1])
......
}
到这里,程序的执行流程是main()->intMain()->AnalyzeLocalImage()—>analyzeLayer()->getLayer()
func AnalyzeLocalImage(imageName string, minSeverity database.Severity, endpoint, myAddress, tmpPath string) error {
......
//获取漏洞信息
layer, err := getLayer(endpoint, layerIDs[len(layerIDs)-1])
//打印漏洞报告
......
for _, feature := range layer.Features {
if len(feature.Vulnerabilities) > 0 {
for _, vulnerability := range feature.Vulnerabilities {
severity := database.Severity(vulnerability.Severity)
isSafe = false
if minSeverity.Compare(severity) > 0 {
continue
}
hasVisibleVulnerabilities = true
vulnerabilities = append(vulnerabilities, vulnerabilityInfo{vulnerability, feature, severity})
}
}
}
//排序输出报告美化
.....
}
通过源码分析可知,与clair后端进行交换的两个主要方法为analyzeLayer和getLayer。analyzeLayer向clair发送JSON格式的数据。而getLayer用来获取clair的请求。并将json格式数据解码后格式化输出。
至此,对analyze-local-images的源码已经分析完毕。从中可以可以看出。analyze-local-images做的事情很简单。
就是将layer.tar发送给clair。并将clair分析后的结果通过API接口获取到并在本地打印。那么下一篇文章,我们就深入剖析下clair的源码。