启动初始化代码:
基础镜像的解析的初始化代码在analyzer包中。
每种基础镜像通过调用RegisterAnalyzer来将自己的实现实例注册到analyzers哈希表中。
代码如下:
- func RegisterAnalyzer(analyzer analyzer) {
- analyzers[analyzer.Type()] = analyzer
- }
实现的接口为:
- type analyzer interface {
- Type() Type//类型
- Version() int//版本
- Analyze(ctx context.Context, input AnalysisInput) (*AnalysisResult, error)//解析函数
- Required(filePath string, info os.FileInfo) bool//基本检查,例如该文件是否为对应的os release文件名
- }
以Ubuntu为例:
它所对应的基础镜像信息位于/etc/lsb-release文件中。以ubuntu18为例,内容如下:
- DISTRIB_ID=Ubuntu
- DISTRIB_RELEASE=18.04
- DISTRIB_CODENAME=bionic
- DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS"
对应到trivy的源码文件为:trivy/pkg/fanal/analyzer/os/ubuntu/ubuntu.go
代码如下:
- package ubuntu
-
- import (
- "bufio"
- "context"
- "os"
- "strings"
-
- "golang.org/x/xerrors"
-
- "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
- aos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os"
- "github.com/aquasecurity/trivy/pkg/fanal/types"
- "github.com/aquasecurity/trivy/pkg/fanal/utils"
- )
-
- func init() {
- analyzer.RegisterAnalyzer(&ubuntuOSAnalyzer{})//程序启动时就会执行这里的注册代码,将Ubuntu的基础镜像解析器注册进去
- }
-
- const version = 1
-
- var requiredFiles = []string{"etc/lsb-release"}//对应的基础镜像的配置文件路径,可能会有多个文件,所以是个数组
-
- type ubuntuOSAnalyzer struct{}
-
- /*input 包含文件信息*/
- func (a ubuntuOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
- isUbuntu := false
- scanner := bufio.NewScanner(input.Content)//文件内容
- for scanner.Scan() {
- line := scanner.Text()
- if line == "DISTRIB_ID=Ubuntu" {//对照前面的文件内容,第一行就是DISTRIB_ID=Ubuntu,所以为真,继续解析后面的内容
- isUbuntu = true
- continue
- }
- //第二行的内容 "DISTRIB_RELEASE=18.04" ,两个条件同时满足,直接设置AnalysisResult中的OS信息返回
- if isUbuntu && strings.HasPrefix(line, "DISTRIB_RELEASE=") {
- return &analyzer.AnalysisResult{
- OS: &types.OS{
- Family: aos.Ubuntu,
- Name: strings.TrimSpace(line[16:]),//删除"DISTRIB_RELEASE="
- },
- }, nil
- }
- }
- return nil, xerrors.Errorf("ubuntu: %w", aos.AnalyzeOSError)
- }
-
- func (a ubuntuOSAnalyzer) Required(filePath string, _ os.FileInfo) bool {
- return utils.StringInSlice(filePath, requiredFiles)//对比文件是否为etc/lsb-release,对返回true
- }
-
- func (a ubuntuOSAnalyzer) Type() analyzer.Type {
- return analyzer.TypeUbuntu//返回对应的类型,用作解析器哈希表的key
- }
-
- func (a ubuntuOSAnalyzer) Version() int {
- return version
- }
逻辑非常简单。
trivy的运行模式有很多种,我前面的一个例子使用的是Standalone模式。如果我们对docker镜像进行扫描的话,最后调用imageStandaloneScanner进行扫描。
整体扫描逻辑为:
创建扫描器:run=>NewImageCommand=>artifact.Run=>artifact.NewRunner
执行扫描:run=>NewImageCommand=>artifact.Run=>artifact.ScanImage=>scanArtifact=>ScanArtifact=>artifact.Inspect=>image.inspect=>image.inspectLayer=>analyzer.AnalyzeFile=>analyzer.Required=>analyzer.Analyze
这样就会调用Ubuntu的检查和分析函数最终解析到对应的基础镜像信息。
我们知道docker镜像可以有很多基础镜像,所以这些,会有很多基础镜像解析器注册进来,同时trivy是一个漏扫工具,所以有很多包管理器也会注册进来,所以这个哈希表实际上种类繁多,并不是每次都要用到,所以trivy提供了一个NewAnalyzerGroup接口给我们进行定制化扫描。
下面我们把整体代码过一遍:
入口文件trivy/cmd/trivy/main.go
main函数:
- func main() {
- if err := run(); err != nil {
- log.Fatal(err)
- }
- }
main=>run
- func run() error {
- // Trivy behaves as the specified plugin.
- if runAsPlugin := os.Getenv("TRIVY_RUN_AS_PLUGIN"); runAsPlugin != "" {
- if !plugin.IsPredefined(runAsPlugin) {
- return xerrors.Errorf("unknown plugin: %s", runAsPlugin)
- }
- if err := plugin.RunWithArgs(context.Background(), runAsPlugin, os.Args[1:]); err != nil {
- return xerrors.Errorf("plugin error: %w", err)
- }
- return nil
- }
-
- app := commands.NewApp(version)//重点函数,这里做了一系列初始化和命令行参数解析
- if err := app.Execute(); err != nil {//执行命令
- return err
- }
- return nil
- }
main=>run=>NewApp
- // NewApp is the factory method to return Trivy CLI
- func NewApp(version string) *cobra.Command {
- globalFlags := flag.NewGlobalFlagGroup()
- rootCmd := NewRootCommand(version, globalFlags)
- rootCmd.AddCommand(
- NewImageCommand(globalFlags),//本地镜像命令创建
- NewFilesystemCommand(globalFlags),
- NewRootfsCommand(globalFlags),
- NewRepositoryCommand(globalFlags),
- NewClientCommand(globalFlags),
- NewServerCommand(globalFlags),
- NewConfigCommand(globalFlags),
- NewPluginCommand(),
- NewModuleCommand(globalFlags),
- NewKubernetesCommand(globalFlags),
- NewSBOMCommand(globalFlags),
- NewVersionCommand(globalFlags),
- )
- rootCmd.AddCommand(loadPluginCommands()...)
-
- return rootCmd
- }
main=>run=>NewApp=>NewImageCommand
-
- func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
- reportFlagGroup := flag.NewReportFlagGroup()
- reportFlagGroup.DependencyTree = nil // disable '--dependency-tree'
- reportFlagGroup.ReportFormat = nil // TODO: support --report summary
-
- imageFlags := &flag.Flags{
- CacheFlagGroup: flag.NewCacheFlagGroup(),
- DBFlagGroup: flag.NewDBFlagGroup(),
- ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific
- LicenseFlagGroup: flag.NewLicenseFlagGroup(),
- MisconfFlagGroup: flag.NewMisconfFlagGroup(),
- RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode
- ReportFlagGroup: reportFlagGroup,
- ScanFlagGroup: flag.NewScanFlagGroup(),
- SecretFlagGroup: flag.NewSecretFlagGroup(),
- VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
- }
-
- cmd := &cobra.Command{
- Use: "image [flags] IMAGE_NAME",
- Aliases: []string{"i"},
- Short: "Scan a container image",
- Example: ` # Scan a container image
- $ trivy image python:3.4-alpine
- # Scan a container image from a tar archive
- $ trivy image --input ruby-3.1.tar
- # Filter by severities
- $ trivy image --severity HIGH,CRITICAL alpine:3.15
- # Ignore unfixed/unpatched vulnerabilities
- $ trivy image --ignore-unfixed alpine:3.15
- # Scan a container image in client mode
- $ trivy image --server http://127.0.0.1:4954 alpine:latest
- # Generate json result
- $ trivy image --format json --output result.json alpine:3.15
- # Generate a report in the CycloneDX format
- $ trivy image --format cyclonedx --output result.cdx alpine:3.15`,
-
- // 'Args' cannot be used since it is called before PreRunE and viper is not configured yet.
- // cmd.Args -> cannot validate args here
- // cmd.PreRunE -> configure viper && validate args
- // cmd.RunE -> run the command
- PreRunE: func(cmd *cobra.Command, args []string) error {
- // viper.BindPFlag cannot be called in init(), so it is called in PreRunE.
- // cf. https://github.com/spf13/cobra/issues/875
- // https://github.com/spf13/viper/issues/233
- if err := imageFlags.Bind(cmd); err != nil {
- return xerrors.Errorf("flag bind error: %w", err)
- }
- return validateArgs(cmd, args)
- },
- RunE: func(cmd *cobra.Command, args []string) error {
- options, err := imageFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)//转换命令参数至options中,如果我们指定的是本地镜像名,最终会保存在options.Target中。
- if err != nil {
- return xerrors.Errorf("flag error: %w", err)
- }
- return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage)//对应的扫描执行函数
- },
- SilenceErrors: true,
- SilenceUsage: true,
- }
-
- imageFlags.AddFlags(cmd)
- cmd.SetFlagErrorFunc(flagErrorFunc)
- cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, imageFlags.Usages(cmd)))
-
- return cmd
- }
main=>run=>NewApp=>NewImageCommand=>Run
- // Run performs artifact scanning
- func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) {
- ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
- defer cancel()
-
- defer func() {
- if xerrors.Is(err, context.DeadlineExceeded) {
- log.Logger.Warn("Increase --timeout value")
- }
- }()
-
- if opts.GenerateDefaultConfig {
- log.Logger.Info("Writing the default config to trivy-default.yaml...")
- return viper.SafeWriteConfigAs("trivy-default.yaml")
- }
-
- r, err := NewRunner(ctx, opts)//创建扫描器
- if err != nil {
- if errors.Is(err, SkipScan) {
- return nil
- }
- return xerrors.Errorf("init error: %w", err)
- }
- defer r.Close(ctx)
-
- var report types.Report
- switch targetKind {//根据参数类型选择执行的函数,我们走第一个分支
- case TargetContainerImage, TargetImageArchive:
- if report, err = r.ScanImage(ctx, opts); err != nil {//扫描镜像
- return xerrors.Errorf("image scan error: %w", err)
- }
- case TargetFilesystem:
- if report, err = r.ScanFilesystem(ctx, opts); err != nil {
- return xerrors.Errorf("filesystem scan error: %w", err)
- }
- case TargetRootfs:
- if report, err = r.ScanRootfs(ctx, opts); err != nil {
- return xerrors.Errorf("rootfs scan error: %w", err)
- }
- case TargetRepository:
- if report, err = r.ScanRepository(ctx, opts); err != nil {
- return xerrors.Errorf("repository scan error: %w", err)
- }
- case TargetSBOM:
- if report, err = r.ScanSBOM(ctx, opts); err != nil {
- return xerrors.Errorf("sbom scan error: %w", err)
- }
- }
-
- report, err = r.Filter(ctx, opts, report)
- if err != nil {
- return xerrors.Errorf("filter error: %w", err)
- }
-
- if err = r.Report(opts, report); err != nil {
- return xerrors.Errorf("report error: %w", err)
- }
-
- Exit(opts, report.Results.Failed())
-
- return nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>NewRunner
- // NewRunner initializes Runner that provides scanning functionalities.
- // It is possible to return SkipScan and it must be handled by caller.
- func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) {
- r := &runner{}
- for _, opt := range opts {
- opt(r)
- }
-
- if err := r.initCache(cliOptions); err != nil {//初始化缓存,还未研究
- return nil, xerrors.Errorf("cache error: %w", err)
- }
-
- // Update the vulnerability database if needed.
- if err := r.initDB(cliOptions); err != nil {//更新漏洞库,还未研究
- return nil, xerrors.Errorf("DB error: %w", err)
- }
-
- // Initialize WASM modules
- m, err := module.NewManager(ctx)
- if err != nil {
- return nil, xerrors.Errorf("WASM module error: %w", err)
- }
- m.Register()
- r.module = m
-
- return r, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage
- func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
- // Disable the lock file scanning
- opts.DisabledAnalyzers = analyzer.TypeLockfiles
-
- var s InitializeScanner
- switch {
- case opts.Input != "" && opts.ServerAddr == "":
- // Scan image tarball in standalone mode
- s = archiveStandaloneScanner
- case opts.Input != "" && opts.ServerAddr != "":
- // Scan image tarball in client/server mode
- s = archiveRemoteScanner
- case opts.Input == "" && opts.ServerAddr == ""://我们没有指定镜像的tar包或者地址,走该分支
- // Scan container image in standalone mode
- s = imageStandaloneScanner
- case opts.Input == "" && opts.ServerAddr != "":
- // Scan container image in client/server mode
- s = imageRemoteScanner
- }
-
- return r.scanArtifact(ctx, opts, s)//初始化(加载镜像。。。),启动扫描
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner
- // imageStandaloneScanner initializes a container image scanner in standalone mode
- // $ trivy image alpine:3.15
- func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
- dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)//docker选项解析
- if err != nil {
- return scanner.Scanner{}, nil, err
- }
- s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
- dockerOpt, conf.ArtifactOption)//加载镜像,conf.Target为镜像名或镜像id
- if err != nil {
- return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)//根据配置创建walker和analyzer(分析器)
- }
- return s, cleanup, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewContainerImage
- /*基于google go-containerregistry和 docker cli 进行操作*/
- func NewContainerImage(ctx context.Context, imageName string, option types.DockerOption, opts ...Option) (types.Image, func(), error) {
- o := &options{
- dockerd: true,
- podman: true,
- containerd: true,
- remote: true,
- }
- for _, opt := range opts {
- opt(o)
- }
-
- var errs error
- var nameOpts []name.Option
- if option.NonSSL {
- nameOpts = append(nameOpts, name.Insecure)
- }
- ref, err := name.ParseReference(imageName, nameOpts...)//解析镜像名
- if err != nil {
- return nil, func() {}, xerrors.Errorf("failed to parse the image name: %w", err)
- }
-
- // Try accessing Docker Daemon
- if o.dockerd {//连接docker daemon,如果访问成功直接返回对应的镜像结构,后面解析时会用到这个结构
- img, cleanup, err := tryDockerDaemon(imageName, ref)
- if err == nil {
- // Return v1.Image if the image is found in Docker Engine
- return img, cleanup, nil
- }
- errs = multierror.Append(errs, err)
- }
-
- // Try accessing Podman
- if o.podman {
- img, cleanup, err := tryPodmanDaemon(imageName)
- if err == nil {
- // Return v1.Image if the image is found in Podman
- return img, cleanup, nil
- }
- errs = multierror.Append(errs, err)
- }
-
- // Try containerd
- if o.containerd {
- img, cleanup, err := tryContainerdDaemon(ctx, imageName)
- if err == nil {
- // Return v1.Image if the image is found in containerd
- return img, cleanup, nil
- }
- errs = multierror.Append(errs, err)
- }
-
- // Try accessing Docker Registry
- if o.remote {
- img, err := tryRemote(ctx, imageName, ref, option)
- if err == nil {
- // Return v1.Image if the image is found in a remote registry
- return img, func() {}, nil
- }
- errs = multierror.Append(errs, err)
- }
-
- return nil, func() {}, errs
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewArtifact
- func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) {
- misconf := opt.MisconfScannerOption
- // Register config analyzers
- if err := config.RegisterConfigAnalyzers(misconf.FilePatterns); err != nil {
- return nil, xerrors.Errorf("config scanner error: %w", err)
- }
-
- // Initialize handlers
- handlerManager, err := handler.NewManager(opt)
- if err != nil {
- return nil, xerrors.Errorf("handler init error: %w", err)
- }
-
- // Register secret analyzer
- if err = secret.RegisterSecretAnalyzer(opt.SecretScannerOption); err != nil {
- return nil, xerrors.Errorf("secret scanner error: %w", err)
- }
-
- return Artifact{
- image: img,
- cache: c,
- walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs),//遍历layer器
- analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers),//分析器
- handlerManager: handlerManager,
-
- artifactOption: opt,
- }, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewScanner
- // NewScanner is the factory method of Scanner
- //这里将前面创建的local scanner作为driver传进来,这个Driver定义了一个函数需要实现即Scan
- func NewScanner(driver Driver, ar artifact.Artifact) Scanner {
- return Scanner{driver: driver, artifact: ar}
- }
-
- //定义如下:
- // Driver defines operations of scanner
- type Driver interface {
- Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (
- results types.Results, osFound *ftypes.OS, err error)
- }
我们回过头去看localscanner的Scan函数的定义:
-
- // Scan scans the artifact and return results.
- func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
- artifactDetail, err := s.applier.ApplyLayers(artifactKey, blobKeys)
- switch {
- case errors.Is(err, analyzer.ErrUnknownOS):
- log.Logger.Debug("OS is not detected.")
-
- // If OS is not detected and repositories are detected, we'll try to use repositories as OS.
- if artifactDetail.Repository != nil {
- log.Logger.Debugf("Package repository: %s %s", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
- log.Logger.Debugf("Assuming OS is %s %s.", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
- artifactDetail.OS = &ftypes.OS{
- Family: artifactDetail.Repository.Family,
- Name: artifactDetail.Repository.Release,
- }
- }
- case errors.Is(err, analyzer.ErrNoPkgsDetected):
- log.Logger.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.")
- log.Logger.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`)
- case err != nil:
- return nil, nil, xerrors.Errorf("failed to apply layers: %w", err)
- }
-
- var eosl bool
- var results types.Results
-
- // Scan OS packages and language-specific dependencies
- if slices.Contains(options.SecurityChecks, types.SecurityCheckVulnerability) {
- var vulnResults types.Results
- vulnResults, eosl, err = s.checkVulnerabilities(target, artifactDetail, options)
- if err != nil {
- return nil, nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
- }
- if artifactDetail.OS != nil {
- artifactDetail.OS.Eosl = eosl
- }
- results = append(results, vulnResults...)
- }
-
- // Scan IaC config files
- if shouldScanMisconfig(options.SecurityChecks) {
- configResults := s.misconfsToResults(artifactDetail.Misconfigurations)
- results = append(results, configResults...)
- }
-
- // Scan secrets
- if slices.Contains(options.SecurityChecks, types.SecurityCheckSecret) {
- secretResults := s.secretsToResults(artifactDetail.Secrets)
- results = append(results, secretResults...)
- }
-
- // For WASM plugins and custom analyzers
- if len(artifactDetail.CustomResources) != 0 {
- results = append(results, types.Result{
- Class: types.ClassCustom,
- CustomResources: artifactDetail.CustomResources,
- })
- }
-
- // Scan licenses
- if slices.Contains(options.SecurityChecks, types.SecurityCheckLicense) {
- licenseResults := s.scanLicenses(artifactDetail, options.LicenseCategories)
- results = append(results, licenseResults...)
- }
-
- for i := range results {
- // Fill vulnerability details
- s.vulnClient.FillInfo(results[i].Vulnerabilities)
- }
-
- // Post scanning
- results, err = post.Scan(ctx, results)
- if err != nil {
- return nil, nil, xerrors.Errorf("post scan error: %w", err)
- }
-
- return results, artifactDetail.OS, nil
- }
我们接着往下看ScanImage,最后调用了r.scanArtifact:
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage
- func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
- // Disable the lock file scanning
- opts.DisabledAnalyzers = analyzer.TypeLockfiles
-
- var s InitializeScanner
- switch {
- case opts.Input != "" && opts.ServerAddr == "":
- // Scan image tarball in standalone mode
- s = archiveStandaloneScanner
- case opts.Input != "" && opts.ServerAddr != "":
- // Scan image tarball in client/server mode
- s = archiveRemoteScanner
- case opts.Input == "" && opts.ServerAddr == "":
- // Scan container image in standalone mode
- s = imageStandaloneScanner
- case opts.Input == "" && opts.ServerAddr != "":
- // Scan container image in client/server mode
- s = imageRemoteScanner
- }
-
- return r.scanArtifact(ctx, opts, s)
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact
- func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) {
- report, err := scan(ctx, opts, initializeScanner, r.cache)
- if err != nil {
- return types.Report{}, xerrors.Errorf("scan error: %w", err)
- }
-
- return report, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan
- func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) (
- types.Report, error) {
-
- scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient)//解析配置,重点关注Target
- if err != nil {
- return types.Report{}, err
- }
-
- s, cleanup, err := initializeScanner(ctx, scannerConfig)//调用imageStandaloneScanner,我们前面已经分析过了
- if err != nil {
- return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
- }
- defer cleanup()
-
- report, err := s.ScanArtifact(ctx, scanOptions)//万事俱备,执行扫描
- if err != nil {
- return types.Report{}, xerrors.Errorf("image scan failed: %w", err)
- }
- return report, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact
- // ScanArtifact scans the artifacts and returns results
- func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (types.Report, error) {
- artifactInfo, err := s.artifact.Inspect(ctx)//执行扫描,基础镜像就通过此被执行的。
- if err != nil {
- return types.Report{}, xerrors.Errorf("failed analysis: %w", err)
- }
- defer func() {
- if err := s.artifact.Clean(artifactInfo); err != nil {
- log.Logger.Warnf("Failed to clean the artifact %q: %v", artifactInfo.Name, err)
- }
- }()
-
- results, osFound, err := s.driver.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)//前面的local scanner的Scan在此执行
- if err != nil {
- return types.Report{}, xerrors.Errorf("scan failed: %w", err)
- }
-
- if osFound != nil && osFound.Eosl {
- log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", osFound.Family, osFound.Name)
- log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
- }
-
- // Layer makes sense only when scanning container images
- if artifactInfo.Type != ftypes.ArtifactContainerImage {
- removeLayer(results)
- }
-
- return types.Report{
- SchemaVersion: report.SchemaVersion,
- ArtifactName: artifactInfo.Name,
- ArtifactType: artifactInfo.Type,
- Metadata: types.Metadata{
- OS: osFound,
-
- // Container image
- ImageID: artifactInfo.ImageMetadata.ID,
- DiffIDs: artifactInfo.ImageMetadata.DiffIDs,
- RepoTags: artifactInfo.ImageMetadata.RepoTags,
- RepoDigests: artifactInfo.ImageMetadata.RepoDigests,
- ImageConfig: artifactInfo.ImageMetadata.ConfigFile,
- },
- CycloneDX: artifactInfo.CycloneDX,
- Results: results,
- }, nil//返回报告
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect
- func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) {
- imageID, err := a.image.ID()//镜像ID
- if err != nil {
- return types.ArtifactReference{}, xerrors.Errorf("unable to get the image ID: %w", err)
- }
-
- diffIDs, err := a.image.LayerIDs()
- if err != nil {
- return types.ArtifactReference{}, xerrors.Errorf("unable to get layer IDs: %w", err)
- }
-
- configFile, err := a.image.ConfigFile()
- if err != nil {
- return types.ArtifactReference{}, xerrors.Errorf("unable to get the image's config file: %w", err)
- }
-
- // Debug
- log.Logger.Debugf("Image ID: %s", imageID)
- log.Logger.Debugf("Diff IDs: %v", diffIDs)
-
- // Try to detect base layers.
- baseDiffIDs := a.guessBaseLayers(diffIDs, configFile)
- log.Logger.Debugf("Base Layers: %v", baseDiffIDs)
-
- // Convert image ID and layer IDs to cache keys
- imageKey, layerKeys, layerKeyMap, err := a.calcCacheKeys(imageID, diffIDs)
- if err != nil {
- return types.ArtifactReference{}, err
- }
-
- missingImage, missingLayers, err := a.cache.MissingBlobs(imageKey, layerKeys)
- if err != nil {
- return types.ArtifactReference{}, xerrors.Errorf("unable to get missing layers: %w", err)
- }
-
- missingImageKey := imageKey
- if missingImage {
- log.Logger.Debugf("Missing image ID in cache: %s", imageID)
- } else {
- missingImageKey = ""
- }
-
- //执行扫描
- if err = a.inspect(ctx, missingImageKey, missingLayers, baseDiffIDs, layerKeyMap); err != nil {
- return types.ArtifactReference{}, xerrors.Errorf("analyze error: %w", err)
- }
-
- return types.ArtifactReference{
- Name: a.image.Name(),
- Type: types.ArtifactContainerImage,
- ID: imageKey,
- BlobIDs: layerKeys,
- ImageMetadata: types.ImageMetadata{
- ID: imageID,
- DiffIDs: diffIDs,
- RepoTags: a.image.RepoTags(),
- RepoDigests: a.image.RepoDigests(),
- ConfigFile: *configFile,
- },
- }, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect
- func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, baseDiffIDs []string, layerKeyMap map[string]string) error {
- done := make(chan struct{})
- errCh := make(chan error)
-
- var osFound types.OS
- for _, k := range layerKeys {
- go func(ctx context.Context, layerKey string) {
- diffID := layerKeyMap[layerKey]
-
- // If it is a base layer, secret scanning should not be performed.
- var disabledAnalyers []analyzer.Type
- if slices.Contains(baseDiffIDs, diffID) {
- disabledAnalyers = append(disabledAnalyers, analyzer.TypeSecret)
- }
-
- layerInfo, err := a.inspectLayer(ctx, diffID, disabledAnalyers)//扫描
- if err != nil {
- errCh <- xerrors.Errorf("failed to analyze layer: %s : %w", diffID, err)
- return
- }
- if err = a.cache.PutBlob(layerKey, layerInfo); err != nil {
- errCh <- xerrors.Errorf("failed to store layer: %s in cache: %w", layerKey, err)
- return
- }
- if layerInfo.OS != nil {
- osFound = *layerInfo.OS
- }
- done <- struct{}{}
- }(ctx, k)
- }
-
- for range layerKeys {
- select {
- case <-done:
- case err := <-errCh:
- return err
- case <-ctx.Done():
- return xerrors.Errorf("timeout: %w", ctx.Err())
- }
- }
-
- if missingImage != "" {
- if err := a.inspectConfig(missingImage, osFound); err != nil {
- return xerrors.Errorf("unable to analyze config: %w", err)
- }
- }
-
- return nil
-
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer
- func (a Artifact) inspectLayer(ctx context.Context, diffID string, disabled []analyzer.Type) (types.BlobInfo, error) {
- log.Logger.Debugf("Missing diff ID in cache: %s", diffID)
-
- layerDigest, r, err := a.uncompressedLayer(diffID)//解压镜像的层
- if err != nil {
- return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", diffID, err)
- }
-
- // Prepare variables
- var wg sync.WaitGroup
- opts := analyzer.AnalysisOptions{Offline: a.artifactOption.Offline}
- result := analyzer.NewAnalysisResult()
- limit := semaphore.NewWeighted(parallel)
-
- // Walk a tar layer
- opqDirs, whFiles, err := a.walker.Walk(r, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
- if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "", filePath, info, opener, disabled, opts); err != nil {//执行扫描
- return xerrors.Errorf("failed to analyze %s: %w", filePath, err)
- }
- return nil
- })
- if err != nil {
- return types.BlobInfo{}, xerrors.Errorf("walk error: %w", err)
- }
-
- // Wait for all the goroutine to finish.
- wg.Wait()
-
- // Sort the analysis result for consistent results
- result.Sort()
-
- blobInfo := types.BlobInfo{
- SchemaVersion: types.BlobJSONSchemaVersion,
- Digest: layerDigest,
- DiffID: diffID,
- OS: result.OS,
- Repository: result.Repository,
- PackageInfos: result.PackageInfos,
- Applications: result.Applications,
- Secrets: result.Secrets,
- Licenses: result.Licenses,
- OpaqueDirs: opqDirs,
- WhiteoutFiles: whFiles,
- CustomResources: result.CustomResources,
-
- // For Red Hat
- BuildInfo: result.BuildInfo,
- }
-
- // Call post handlers to modify blob info
- if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil {
- return types.BlobInfo{}, xerrors.Errorf("post handler error: %w", err)
- }
-
- return blobInfo, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk
- //analyzeFn 执行解析os release的函数指针
- func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, error) {
- var opqDirs, whFiles, skipDirs []string
- tr := tar.NewReader(layer)//读取tar包
- for {
- hdr, err := tr.Next()//遍历tar中的文件或目录
- if err == io.EOF {
- break
- } else if err != nil {
- return nil, nil, xerrors.Errorf("failed to extract the archive: %w", err)
- }
-
- filePath := hdr.Name
- filePath = strings.TrimLeft(filepath.Clean(filePath), "/")
- fileDir, fileName := filepath.Split(filePath)
-
- // e.g. etc/.wh..wh..opq
- if opq == fileName {
- opqDirs = append(opqDirs, fileDir)
- continue
- }
- // etc/.wh.hostname
- if strings.HasPrefix(fileName, wh) {
- name := strings.TrimPrefix(fileName, wh)
- fpath := filepath.Join(fileDir, name)
- whFiles = append(whFiles, fpath)
- continue
- }
-
- switch hdr.Typeflag {
- case tar.TypeDir:
- if w.shouldSkipDir(filePath) {
- skipDirs = append(skipDirs, filePath)
- continue
- }
- case tar.TypeSymlink, tar.TypeLink, tar.TypeReg:
- if w.shouldSkipFile(filePath) {
- continue
- }
- default:
- continue
- }
-
- if underSkippedDir(filePath, skipDirs) {
- continue
- }
-
- // A symbolic/hard link or regular file will reach here.
- if err = w.processFile(filePath, tr, hdr.FileInfo(), analyzeFn); err != nil {//处理文件
- return nil, nil, xerrors.Errorf("failed to process the file: %w", err)
- }
- }
- return opqDirs, whFiles, nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk=>processFile
- func (w LayerTar) processFile(filePath string, tr *tar.Reader, fi fs.FileInfo, analyzeFn WalkFunc) error {
- tf := newTarFile(fi.Size(), tr)
- defer func() {
- // nolint
- _ = tf.Clean()
- }()
-
- //传入的函数指针为匿名哈数,里面调用的对应的是我们前面看到a.analyzer.AnalyzeFile,下面我们看看具体实现
- if err := analyzeFn(filePath, fi, tf.Open); err != nil {
- return xerrors.Errorf("failed to analyze file: %w", err)
- }
-
- return nil
- }
main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk=>processFile=>AnalyzeFile
- func (ag AnalyzerGroup) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, limit *semaphore.Weighted, result *AnalysisResult,
- dir, filePath string, info os.FileInfo, opener Opener, disabled []Type, opts AnalysisOptions) error {
- if info.IsDir() {
- return nil
- }
-
- //遍历所有分析器,一个一个执行
- for _, a := range ag.analyzers {
- // Skip disabled analyzers
- if slices.Contains(disabled, a.Type()) {//是否禁用了某些类型,是则跳过
- continue
- }
-
- // filepath extracted from tar file doesn't have the prefix "/"
- if !a.Required(strings.TrimLeft(filePath, "/"), info) {//调用Required检查文件名是否是对应的分析器的所指定的文件,在我们的这个例子中为etc/lsb-release,检查通过,继续
- continue
- }
- rc, err := opener()
- if errors.Is(err, fs.ErrPermission) {
- log.Logger.Debugf("Permission error: %s", filePath)
- break
- } else if err != nil {
- return xerrors.Errorf("unable to open %s: %w", filePath, err)
- }
-
- if err = limit.Acquire(ctx, 1); err != nil {//获取信号量,进行并发控制
- return xerrors.Errorf("semaphore acquire: %w", err)
- }
- wg.Add(1)
-
- go func(a analyzer, rc dio.ReadSeekCloserAt) {//启动 goroutine,执行分析器
- defer limit.Release(1)
- defer wg.Done()
- defer rc.Close()
-
- ret, err := a.Analyze(ctx, AnalysisInput{
- Dir: dir,
- FilePath: filePath,
- Info: info,
- Content: rc,
- Options: opts,
- })//执行分析函数,对应我们的就是ubuntuOSAnalyzer.Analyze函数
- if err != nil && !xerrors.Is(err, aos.AnalyzeOSError) {
- log.Logger.Debugf("Analysis error: %s", err)
- return
- }
- if ret != nil {
- result.Merge(ret)
- }
- }(a, rc)
- }
-
- return nil
- }
至此整个代码的基本流程就讲完了。