• trivy 获取基础镜像源码分析


    启动初始化代码:

    基础镜像的解析的初始化代码在analyzer包中。

    每种基础镜像通过调用RegisterAnalyzer来将自己的实现实例注册到analyzers哈希表中。

    代码如下:

    1. func RegisterAnalyzer(analyzer analyzer) {
    2. analyzers[analyzer.Type()] = analyzer
    3. }

    实现的接口为:

    1. type analyzer interface {
    2. Type() Type//类型
    3. Version() int//版本
    4. Analyze(ctx context.Context, input AnalysisInput) (*AnalysisResult, error)//解析函数
    5. Required(filePath string, info os.FileInfo) bool//基本检查,例如该文件是否为对应的os release文件名
    6. }

    以Ubuntu为例:

    它所对应的基础镜像信息位于/etc/lsb-release文件中。以ubuntu18为例,内容如下:

    1. DISTRIB_ID=Ubuntu
    2. DISTRIB_RELEASE=18.04
    3. DISTRIB_CODENAME=bionic
    4. DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS"

    对应到trivy的源码文件为:trivy/pkg/fanal/analyzer/os/ubuntu/ubuntu.go

    代码如下:

    1. package ubuntu
    2. import (
    3. "bufio"
    4. "context"
    5. "os"
    6. "strings"
    7. "golang.org/x/xerrors"
    8. "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
    9. aos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os"
    10. "github.com/aquasecurity/trivy/pkg/fanal/types"
    11. "github.com/aquasecurity/trivy/pkg/fanal/utils"
    12. )
    13. func init() {
    14. analyzer.RegisterAnalyzer(&ubuntuOSAnalyzer{})//程序启动时就会执行这里的注册代码,将Ubuntu的基础镜像解析器注册进去
    15. }
    16. const version = 1
    17. var requiredFiles = []string{"etc/lsb-release"}//对应的基础镜像的配置文件路径,可能会有多个文件,所以是个数组
    18. type ubuntuOSAnalyzer struct{}
    19. /*input 包含文件信息*/
    20. func (a ubuntuOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
    21. isUbuntu := false
    22. scanner := bufio.NewScanner(input.Content)//文件内容
    23. for scanner.Scan() {
    24. line := scanner.Text()
    25. if line == "DISTRIB_ID=Ubuntu" {//对照前面的文件内容,第一行就是DISTRIB_ID=Ubuntu,所以为真,继续解析后面的内容
    26. isUbuntu = true
    27. continue
    28. }
    29. //第二行的内容 "DISTRIB_RELEASE=18.04" ,两个条件同时满足,直接设置AnalysisResult中的OS信息返回
    30. if isUbuntu && strings.HasPrefix(line, "DISTRIB_RELEASE=") {
    31. return &analyzer.AnalysisResult{
    32. OS: &types.OS{
    33. Family: aos.Ubuntu,
    34. Name: strings.TrimSpace(line[16:]),//删除"DISTRIB_RELEASE="
    35. },
    36. }, nil
    37. }
    38. }
    39. return nil, xerrors.Errorf("ubuntu: %w", aos.AnalyzeOSError)
    40. }
    41. func (a ubuntuOSAnalyzer) Required(filePath string, _ os.FileInfo) bool {
    42. return utils.StringInSlice(filePath, requiredFiles)//对比文件是否为etc/lsb-release,对返回true
    43. }
    44. func (a ubuntuOSAnalyzer) Type() analyzer.Type {
    45. return analyzer.TypeUbuntu//返回对应的类型,用作解析器哈希表的key
    46. }
    47. func (a ubuntuOSAnalyzer) Version() int {
    48. return version
    49. }

    逻辑非常简单。

    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函数:

    1. func main() {
    2. if err := run(); err != nil {
    3. log.Fatal(err)
    4. }
    5. }

    main=>run

    1. func run() error {
    2. // Trivy behaves as the specified plugin.
    3. if runAsPlugin := os.Getenv("TRIVY_RUN_AS_PLUGIN"); runAsPlugin != "" {
    4. if !plugin.IsPredefined(runAsPlugin) {
    5. return xerrors.Errorf("unknown plugin: %s", runAsPlugin)
    6. }
    7. if err := plugin.RunWithArgs(context.Background(), runAsPlugin, os.Args[1:]); err != nil {
    8. return xerrors.Errorf("plugin error: %w", err)
    9. }
    10. return nil
    11. }
    12. app := commands.NewApp(version)//重点函数,这里做了一系列初始化和命令行参数解析
    13. if err := app.Execute(); err != nil {//执行命令
    14. return err
    15. }
    16. return nil
    17. }

    main=>run=>NewApp

    1. // NewApp is the factory method to return Trivy CLI
    2. func NewApp(version string) *cobra.Command {
    3. globalFlags := flag.NewGlobalFlagGroup()
    4. rootCmd := NewRootCommand(version, globalFlags)
    5. rootCmd.AddCommand(
    6. NewImageCommand(globalFlags),//本地镜像命令创建
    7. NewFilesystemCommand(globalFlags),
    8. NewRootfsCommand(globalFlags),
    9. NewRepositoryCommand(globalFlags),
    10. NewClientCommand(globalFlags),
    11. NewServerCommand(globalFlags),
    12. NewConfigCommand(globalFlags),
    13. NewPluginCommand(),
    14. NewModuleCommand(globalFlags),
    15. NewKubernetesCommand(globalFlags),
    16. NewSBOMCommand(globalFlags),
    17. NewVersionCommand(globalFlags),
    18. )
    19. rootCmd.AddCommand(loadPluginCommands()...)
    20. return rootCmd
    21. }

    main=>run=>NewApp=>NewImageCommand

    1. func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
    2. reportFlagGroup := flag.NewReportFlagGroup()
    3. reportFlagGroup.DependencyTree = nil // disable '--dependency-tree'
    4. reportFlagGroup.ReportFormat = nil // TODO: support --report summary
    5. imageFlags := &flag.Flags{
    6. CacheFlagGroup: flag.NewCacheFlagGroup(),
    7. DBFlagGroup: flag.NewDBFlagGroup(),
    8. ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific
    9. LicenseFlagGroup: flag.NewLicenseFlagGroup(),
    10. MisconfFlagGroup: flag.NewMisconfFlagGroup(),
    11. RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode
    12. ReportFlagGroup: reportFlagGroup,
    13. ScanFlagGroup: flag.NewScanFlagGroup(),
    14. SecretFlagGroup: flag.NewSecretFlagGroup(),
    15. VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
    16. }
    17. cmd := &cobra.Command{
    18. Use: "image [flags] IMAGE_NAME",
    19. Aliases: []string{"i"},
    20. Short: "Scan a container image",
    21. Example: ` # Scan a container image
    22. $ trivy image python:3.4-alpine
    23. # Scan a container image from a tar archive
    24. $ trivy image --input ruby-3.1.tar
    25. # Filter by severities
    26. $ trivy image --severity HIGH,CRITICAL alpine:3.15
    27. # Ignore unfixed/unpatched vulnerabilities
    28. $ trivy image --ignore-unfixed alpine:3.15
    29. # Scan a container image in client mode
    30. $ trivy image --server http://127.0.0.1:4954 alpine:latest
    31. # Generate json result
    32. $ trivy image --format json --output result.json alpine:3.15
    33. # Generate a report in the CycloneDX format
    34. $ trivy image --format cyclonedx --output result.cdx alpine:3.15`,
    35. // 'Args' cannot be used since it is called before PreRunE and viper is not configured yet.
    36. // cmd.Args -> cannot validate args here
    37. // cmd.PreRunE -> configure viper && validate args
    38. // cmd.RunE -> run the command
    39. PreRunE: func(cmd *cobra.Command, args []string) error {
    40. // viper.BindPFlag cannot be called in init(), so it is called in PreRunE.
    41. // cf. https://github.com/spf13/cobra/issues/875
    42. // https://github.com/spf13/viper/issues/233
    43. if err := imageFlags.Bind(cmd); err != nil {
    44. return xerrors.Errorf("flag bind error: %w", err)
    45. }
    46. return validateArgs(cmd, args)
    47. },
    48. RunE: func(cmd *cobra.Command, args []string) error {
    49. options, err := imageFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)//转换命令参数至options中,如果我们指定的是本地镜像名,最终会保存在options.Target中。
    50. if err != nil {
    51. return xerrors.Errorf("flag error: %w", err)
    52. }
    53. return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage)//对应的扫描执行函数
    54. },
    55. SilenceErrors: true,
    56. SilenceUsage: true,
    57. }
    58. imageFlags.AddFlags(cmd)
    59. cmd.SetFlagErrorFunc(flagErrorFunc)
    60. cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, imageFlags.Usages(cmd)))
    61. return cmd
    62. }

    main=>run=>NewApp=>NewImageCommand=>Run

    1. // Run performs artifact scanning
    2. func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) {
    3. ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
    4. defer cancel()
    5. defer func() {
    6. if xerrors.Is(err, context.DeadlineExceeded) {
    7. log.Logger.Warn("Increase --timeout value")
    8. }
    9. }()
    10. if opts.GenerateDefaultConfig {
    11. log.Logger.Info("Writing the default config to trivy-default.yaml...")
    12. return viper.SafeWriteConfigAs("trivy-default.yaml")
    13. }
    14. r, err := NewRunner(ctx, opts)//创建扫描器
    15. if err != nil {
    16. if errors.Is(err, SkipScan) {
    17. return nil
    18. }
    19. return xerrors.Errorf("init error: %w", err)
    20. }
    21. defer r.Close(ctx)
    22. var report types.Report
    23. switch targetKind {//根据参数类型选择执行的函数,我们走第一个分支
    24. case TargetContainerImage, TargetImageArchive:
    25. if report, err = r.ScanImage(ctx, opts); err != nil {//扫描镜像
    26. return xerrors.Errorf("image scan error: %w", err)
    27. }
    28. case TargetFilesystem:
    29. if report, err = r.ScanFilesystem(ctx, opts); err != nil {
    30. return xerrors.Errorf("filesystem scan error: %w", err)
    31. }
    32. case TargetRootfs:
    33. if report, err = r.ScanRootfs(ctx, opts); err != nil {
    34. return xerrors.Errorf("rootfs scan error: %w", err)
    35. }
    36. case TargetRepository:
    37. if report, err = r.ScanRepository(ctx, opts); err != nil {
    38. return xerrors.Errorf("repository scan error: %w", err)
    39. }
    40. case TargetSBOM:
    41. if report, err = r.ScanSBOM(ctx, opts); err != nil {
    42. return xerrors.Errorf("sbom scan error: %w", err)
    43. }
    44. }
    45. report, err = r.Filter(ctx, opts, report)
    46. if err != nil {
    47. return xerrors.Errorf("filter error: %w", err)
    48. }
    49. if err = r.Report(opts, report); err != nil {
    50. return xerrors.Errorf("report error: %w", err)
    51. }
    52. Exit(opts, report.Results.Failed())
    53. return nil
    54. }

    main=>run=>NewApp=>NewImageCommand=>Run=>NewRunner

    1. // NewRunner initializes Runner that provides scanning functionalities.
    2. // It is possible to return SkipScan and it must be handled by caller.
    3. func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) {
    4. r := &runner{}
    5. for _, opt := range opts {
    6. opt(r)
    7. }
    8. if err := r.initCache(cliOptions); err != nil {//初始化缓存,还未研究
    9. return nil, xerrors.Errorf("cache error: %w", err)
    10. }
    11. // Update the vulnerability database if needed.
    12. if err := r.initDB(cliOptions); err != nil {//更新漏洞库,还未研究
    13. return nil, xerrors.Errorf("DB error: %w", err)
    14. }
    15. // Initialize WASM modules
    16. m, err := module.NewManager(ctx)
    17. if err != nil {
    18. return nil, xerrors.Errorf("WASM module error: %w", err)
    19. }
    20. m.Register()
    21. r.module = m
    22. return r, nil
    23. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage

    1. func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
    2. // Disable the lock file scanning
    3. opts.DisabledAnalyzers = analyzer.TypeLockfiles
    4. var s InitializeScanner
    5. switch {
    6. case opts.Input != "" && opts.ServerAddr == "":
    7. // Scan image tarball in standalone mode
    8. s = archiveStandaloneScanner
    9. case opts.Input != "" && opts.ServerAddr != "":
    10. // Scan image tarball in client/server mode
    11. s = archiveRemoteScanner
    12. case opts.Input == "" && opts.ServerAddr == ""://我们没有指定镜像的tar包或者地址,走该分支
    13. // Scan container image in standalone mode
    14. s = imageStandaloneScanner
    15. case opts.Input == "" && opts.ServerAddr != "":
    16. // Scan container image in client/server mode
    17. s = imageRemoteScanner
    18. }
    19. return r.scanArtifact(ctx, opts, s)//初始化(加载镜像。。。),启动扫描
    20. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner

    1. // imageStandaloneScanner initializes a container image scanner in standalone mode
    2. // $ trivy image alpine:3.15
    3. func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
    4. dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)//docker选项解析
    5. if err != nil {
    6. return scanner.Scanner{}, nil, err
    7. }
    8. s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
    9. dockerOpt, conf.ArtifactOption)//加载镜像,conf.Target为镜像名或镜像id
    10. if err != nil {
    11. return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)//根据配置创建walker和analyzer(分析器)
    12. }
    13. return s, cleanup, nil
    14. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewContainerImage

    1. /*基于google go-containerregistry和 docker cli 进行操作*/
    2. func NewContainerImage(ctx context.Context, imageName string, option types.DockerOption, opts ...Option) (types.Image, func(), error) {
    3. o := &options{
    4. dockerd: true,
    5. podman: true,
    6. containerd: true,
    7. remote: true,
    8. }
    9. for _, opt := range opts {
    10. opt(o)
    11. }
    12. var errs error
    13. var nameOpts []name.Option
    14. if option.NonSSL {
    15. nameOpts = append(nameOpts, name.Insecure)
    16. }
    17. ref, err := name.ParseReference(imageName, nameOpts...)//解析镜像名
    18. if err != nil {
    19. return nil, func() {}, xerrors.Errorf("failed to parse the image name: %w", err)
    20. }
    21. // Try accessing Docker Daemon
    22. if o.dockerd {//连接docker daemon,如果访问成功直接返回对应的镜像结构,后面解析时会用到这个结构
    23. img, cleanup, err := tryDockerDaemon(imageName, ref)
    24. if err == nil {
    25. // Return v1.Image if the image is found in Docker Engine
    26. return img, cleanup, nil
    27. }
    28. errs = multierror.Append(errs, err)
    29. }
    30. // Try accessing Podman
    31. if o.podman {
    32. img, cleanup, err := tryPodmanDaemon(imageName)
    33. if err == nil {
    34. // Return v1.Image if the image is found in Podman
    35. return img, cleanup, nil
    36. }
    37. errs = multierror.Append(errs, err)
    38. }
    39. // Try containerd
    40. if o.containerd {
    41. img, cleanup, err := tryContainerdDaemon(ctx, imageName)
    42. if err == nil {
    43. // Return v1.Image if the image is found in containerd
    44. return img, cleanup, nil
    45. }
    46. errs = multierror.Append(errs, err)
    47. }
    48. // Try accessing Docker Registry
    49. if o.remote {
    50. img, err := tryRemote(ctx, imageName, ref, option)
    51. if err == nil {
    52. // Return v1.Image if the image is found in a remote registry
    53. return img, func() {}, nil
    54. }
    55. errs = multierror.Append(errs, err)
    56. }
    57. return nil, func() {}, errs
    58. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewArtifact

    1. func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) {
    2. misconf := opt.MisconfScannerOption
    3. // Register config analyzers
    4. if err := config.RegisterConfigAnalyzers(misconf.FilePatterns); err != nil {
    5. return nil, xerrors.Errorf("config scanner error: %w", err)
    6. }
    7. // Initialize handlers
    8. handlerManager, err := handler.NewManager(opt)
    9. if err != nil {
    10. return nil, xerrors.Errorf("handler init error: %w", err)
    11. }
    12. // Register secret analyzer
    13. if err = secret.RegisterSecretAnalyzer(opt.SecretScannerOption); err != nil {
    14. return nil, xerrors.Errorf("secret scanner error: %w", err)
    15. }
    16. return Artifact{
    17. image: img,
    18. cache: c,
    19. walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs),//遍历layer器
    20. analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers),//分析器
    21. handlerManager: handlerManager,
    22. artifactOption: opt,
    23. }, nil
    24. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>imageStandaloneScanner=>NewScanner

    1. // NewScanner is the factory method of Scanner
    2. //这里将前面创建的local scanner作为driver传进来,这个Driver定义了一个函数需要实现即Scan
    3. func NewScanner(driver Driver, ar artifact.Artifact) Scanner {
    4. return Scanner{driver: driver, artifact: ar}
    5. }
    6. //定义如下:
    7. // Driver defines operations of scanner
    8. type Driver interface {
    9. Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (
    10. results types.Results, osFound *ftypes.OS, err error)
    11. }

    我们回过头去看localscanner的Scan函数的定义:

    1. // Scan scans the artifact and return results.
    2. func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
    3. artifactDetail, err := s.applier.ApplyLayers(artifactKey, blobKeys)
    4. switch {
    5. case errors.Is(err, analyzer.ErrUnknownOS):
    6. log.Logger.Debug("OS is not detected.")
    7. // If OS is not detected and repositories are detected, we'll try to use repositories as OS.
    8. if artifactDetail.Repository != nil {
    9. log.Logger.Debugf("Package repository: %s %s", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
    10. log.Logger.Debugf("Assuming OS is %s %s.", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
    11. artifactDetail.OS = &ftypes.OS{
    12. Family: artifactDetail.Repository.Family,
    13. Name: artifactDetail.Repository.Release,
    14. }
    15. }
    16. case errors.Is(err, analyzer.ErrNoPkgsDetected):
    17. log.Logger.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.")
    18. log.Logger.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`)
    19. case err != nil:
    20. return nil, nil, xerrors.Errorf("failed to apply layers: %w", err)
    21. }
    22. var eosl bool
    23. var results types.Results
    24. // Scan OS packages and language-specific dependencies
    25. if slices.Contains(options.SecurityChecks, types.SecurityCheckVulnerability) {
    26. var vulnResults types.Results
    27. vulnResults, eosl, err = s.checkVulnerabilities(target, artifactDetail, options)
    28. if err != nil {
    29. return nil, nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
    30. }
    31. if artifactDetail.OS != nil {
    32. artifactDetail.OS.Eosl = eosl
    33. }
    34. results = append(results, vulnResults...)
    35. }
    36. // Scan IaC config files
    37. if shouldScanMisconfig(options.SecurityChecks) {
    38. configResults := s.misconfsToResults(artifactDetail.Misconfigurations)
    39. results = append(results, configResults...)
    40. }
    41. // Scan secrets
    42. if slices.Contains(options.SecurityChecks, types.SecurityCheckSecret) {
    43. secretResults := s.secretsToResults(artifactDetail.Secrets)
    44. results = append(results, secretResults...)
    45. }
    46. // For WASM plugins and custom analyzers
    47. if len(artifactDetail.CustomResources) != 0 {
    48. results = append(results, types.Result{
    49. Class: types.ClassCustom,
    50. CustomResources: artifactDetail.CustomResources,
    51. })
    52. }
    53. // Scan licenses
    54. if slices.Contains(options.SecurityChecks, types.SecurityCheckLicense) {
    55. licenseResults := s.scanLicenses(artifactDetail, options.LicenseCategories)
    56. results = append(results, licenseResults...)
    57. }
    58. for i := range results {
    59. // Fill vulnerability details
    60. s.vulnClient.FillInfo(results[i].Vulnerabilities)
    61. }
    62. // Post scanning
    63. results, err = post.Scan(ctx, results)
    64. if err != nil {
    65. return nil, nil, xerrors.Errorf("post scan error: %w", err)
    66. }
    67. return results, artifactDetail.OS, nil
    68. }

    我们接着往下看ScanImage,最后调用了r.scanArtifact:

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage

    1. func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
    2. // Disable the lock file scanning
    3. opts.DisabledAnalyzers = analyzer.TypeLockfiles
    4. var s InitializeScanner
    5. switch {
    6. case opts.Input != "" && opts.ServerAddr == "":
    7. // Scan image tarball in standalone mode
    8. s = archiveStandaloneScanner
    9. case opts.Input != "" && opts.ServerAddr != "":
    10. // Scan image tarball in client/server mode
    11. s = archiveRemoteScanner
    12. case opts.Input == "" && opts.ServerAddr == "":
    13. // Scan container image in standalone mode
    14. s = imageStandaloneScanner
    15. case opts.Input == "" && opts.ServerAddr != "":
    16. // Scan container image in client/server mode
    17. s = imageRemoteScanner
    18. }
    19. return r.scanArtifact(ctx, opts, s)
    20. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact

    1. func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) {
    2. report, err := scan(ctx, opts, initializeScanner, r.cache)
    3. if err != nil {
    4. return types.Report{}, xerrors.Errorf("scan error: %w", err)
    5. }
    6. return report, nil
    7. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan

    1. func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) (
    2. types.Report, error) {
    3. scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient)//解析配置,重点关注Target
    4. if err != nil {
    5. return types.Report{}, err
    6. }
    7. s, cleanup, err := initializeScanner(ctx, scannerConfig)//调用imageStandaloneScanner,我们前面已经分析过了
    8. if err != nil {
    9. return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
    10. }
    11. defer cleanup()
    12. report, err := s.ScanArtifact(ctx, scanOptions)//万事俱备,执行扫描
    13. if err != nil {
    14. return types.Report{}, xerrors.Errorf("image scan failed: %w", err)
    15. }
    16. return report, nil
    17. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact

    1. // ScanArtifact scans the artifacts and returns results
    2. func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (types.Report, error) {
    3. artifactInfo, err := s.artifact.Inspect(ctx)//执行扫描,基础镜像就通过此被执行的。
    4. if err != nil {
    5. return types.Report{}, xerrors.Errorf("failed analysis: %w", err)
    6. }
    7. defer func() {
    8. if err := s.artifact.Clean(artifactInfo); err != nil {
    9. log.Logger.Warnf("Failed to clean the artifact %q: %v", artifactInfo.Name, err)
    10. }
    11. }()
    12. results, osFound, err := s.driver.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)//前面的local scanner的Scan在此执行
    13. if err != nil {
    14. return types.Report{}, xerrors.Errorf("scan failed: %w", err)
    15. }
    16. if osFound != nil && osFound.Eosl {
    17. log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", osFound.Family, osFound.Name)
    18. log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
    19. }
    20. // Layer makes sense only when scanning container images
    21. if artifactInfo.Type != ftypes.ArtifactContainerImage {
    22. removeLayer(results)
    23. }
    24. return types.Report{
    25. SchemaVersion: report.SchemaVersion,
    26. ArtifactName: artifactInfo.Name,
    27. ArtifactType: artifactInfo.Type,
    28. Metadata: types.Metadata{
    29. OS: osFound,
    30. // Container image
    31. ImageID: artifactInfo.ImageMetadata.ID,
    32. DiffIDs: artifactInfo.ImageMetadata.DiffIDs,
    33. RepoTags: artifactInfo.ImageMetadata.RepoTags,
    34. RepoDigests: artifactInfo.ImageMetadata.RepoDigests,
    35. ImageConfig: artifactInfo.ImageMetadata.ConfigFile,
    36. },
    37. CycloneDX: artifactInfo.CycloneDX,
    38. Results: results,
    39. }, nil//返回报告
    40. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect

    1. func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) {
    2. imageID, err := a.image.ID()//镜像ID
    3. if err != nil {
    4. return types.ArtifactReference{}, xerrors.Errorf("unable to get the image ID: %w", err)
    5. }
    6. diffIDs, err := a.image.LayerIDs()
    7. if err != nil {
    8. return types.ArtifactReference{}, xerrors.Errorf("unable to get layer IDs: %w", err)
    9. }
    10. configFile, err := a.image.ConfigFile()
    11. if err != nil {
    12. return types.ArtifactReference{}, xerrors.Errorf("unable to get the image's config file: %w", err)
    13. }
    14. // Debug
    15. log.Logger.Debugf("Image ID: %s", imageID)
    16. log.Logger.Debugf("Diff IDs: %v", diffIDs)
    17. // Try to detect base layers.
    18. baseDiffIDs := a.guessBaseLayers(diffIDs, configFile)
    19. log.Logger.Debugf("Base Layers: %v", baseDiffIDs)
    20. // Convert image ID and layer IDs to cache keys
    21. imageKey, layerKeys, layerKeyMap, err := a.calcCacheKeys(imageID, diffIDs)
    22. if err != nil {
    23. return types.ArtifactReference{}, err
    24. }
    25. missingImage, missingLayers, err := a.cache.MissingBlobs(imageKey, layerKeys)
    26. if err != nil {
    27. return types.ArtifactReference{}, xerrors.Errorf("unable to get missing layers: %w", err)
    28. }
    29. missingImageKey := imageKey
    30. if missingImage {
    31. log.Logger.Debugf("Missing image ID in cache: %s", imageID)
    32. } else {
    33. missingImageKey = ""
    34. }
    35. //执行扫描
    36. if err = a.inspect(ctx, missingImageKey, missingLayers, baseDiffIDs, layerKeyMap); err != nil {
    37. return types.ArtifactReference{}, xerrors.Errorf("analyze error: %w", err)
    38. }
    39. return types.ArtifactReference{
    40. Name: a.image.Name(),
    41. Type: types.ArtifactContainerImage,
    42. ID: imageKey,
    43. BlobIDs: layerKeys,
    44. ImageMetadata: types.ImageMetadata{
    45. ID: imageID,
    46. DiffIDs: diffIDs,
    47. RepoTags: a.image.RepoTags(),
    48. RepoDigests: a.image.RepoDigests(),
    49. ConfigFile: *configFile,
    50. },
    51. }, nil
    52. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect

    1. func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, baseDiffIDs []string, layerKeyMap map[string]string) error {
    2. done := make(chan struct{})
    3. errCh := make(chan error)
    4. var osFound types.OS
    5. for _, k := range layerKeys {
    6. go func(ctx context.Context, layerKey string) {
    7. diffID := layerKeyMap[layerKey]
    8. // If it is a base layer, secret scanning should not be performed.
    9. var disabledAnalyers []analyzer.Type
    10. if slices.Contains(baseDiffIDs, diffID) {
    11. disabledAnalyers = append(disabledAnalyers, analyzer.TypeSecret)
    12. }
    13. layerInfo, err := a.inspectLayer(ctx, diffID, disabledAnalyers)//扫描
    14. if err != nil {
    15. errCh <- xerrors.Errorf("failed to analyze layer: %s : %w", diffID, err)
    16. return
    17. }
    18. if err = a.cache.PutBlob(layerKey, layerInfo); err != nil {
    19. errCh <- xerrors.Errorf("failed to store layer: %s in cache: %w", layerKey, err)
    20. return
    21. }
    22. if layerInfo.OS != nil {
    23. osFound = *layerInfo.OS
    24. }
    25. done <- struct{}{}
    26. }(ctx, k)
    27. }
    28. for range layerKeys {
    29. select {
    30. case <-done:
    31. case err := <-errCh:
    32. return err
    33. case <-ctx.Done():
    34. return xerrors.Errorf("timeout: %w", ctx.Err())
    35. }
    36. }
    37. if missingImage != "" {
    38. if err := a.inspectConfig(missingImage, osFound); err != nil {
    39. return xerrors.Errorf("unable to analyze config: %w", err)
    40. }
    41. }
    42. return nil
    43. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer

    1. func (a Artifact) inspectLayer(ctx context.Context, diffID string, disabled []analyzer.Type) (types.BlobInfo, error) {
    2. log.Logger.Debugf("Missing diff ID in cache: %s", diffID)
    3. layerDigest, r, err := a.uncompressedLayer(diffID)//解压镜像的层
    4. if err != nil {
    5. return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", diffID, err)
    6. }
    7. // Prepare variables
    8. var wg sync.WaitGroup
    9. opts := analyzer.AnalysisOptions{Offline: a.artifactOption.Offline}
    10. result := analyzer.NewAnalysisResult()
    11. limit := semaphore.NewWeighted(parallel)
    12. // Walk a tar layer
    13. opqDirs, whFiles, err := a.walker.Walk(r, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
    14. if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "", filePath, info, opener, disabled, opts); err != nil {//执行扫描
    15. return xerrors.Errorf("failed to analyze %s: %w", filePath, err)
    16. }
    17. return nil
    18. })
    19. if err != nil {
    20. return types.BlobInfo{}, xerrors.Errorf("walk error: %w", err)
    21. }
    22. // Wait for all the goroutine to finish.
    23. wg.Wait()
    24. // Sort the analysis result for consistent results
    25. result.Sort()
    26. blobInfo := types.BlobInfo{
    27. SchemaVersion: types.BlobJSONSchemaVersion,
    28. Digest: layerDigest,
    29. DiffID: diffID,
    30. OS: result.OS,
    31. Repository: result.Repository,
    32. PackageInfos: result.PackageInfos,
    33. Applications: result.Applications,
    34. Secrets: result.Secrets,
    35. Licenses: result.Licenses,
    36. OpaqueDirs: opqDirs,
    37. WhiteoutFiles: whFiles,
    38. CustomResources: result.CustomResources,
    39. // For Red Hat
    40. BuildInfo: result.BuildInfo,
    41. }
    42. // Call post handlers to modify blob info
    43. if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil {
    44. return types.BlobInfo{}, xerrors.Errorf("post handler error: %w", err)
    45. }
    46. return blobInfo, nil
    47. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk

    1. //analyzeFn 执行解析os release的函数指针
    2. func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, error) {
    3. var opqDirs, whFiles, skipDirs []string
    4. tr := tar.NewReader(layer)//读取tar包
    5. for {
    6. hdr, err := tr.Next()//遍历tar中的文件或目录
    7. if err == io.EOF {
    8. break
    9. } else if err != nil {
    10. return nil, nil, xerrors.Errorf("failed to extract the archive: %w", err)
    11. }
    12. filePath := hdr.Name
    13. filePath = strings.TrimLeft(filepath.Clean(filePath), "/")
    14. fileDir, fileName := filepath.Split(filePath)
    15. // e.g. etc/.wh..wh..opq
    16. if opq == fileName {
    17. opqDirs = append(opqDirs, fileDir)
    18. continue
    19. }
    20. // etc/.wh.hostname
    21. if strings.HasPrefix(fileName, wh) {
    22. name := strings.TrimPrefix(fileName, wh)
    23. fpath := filepath.Join(fileDir, name)
    24. whFiles = append(whFiles, fpath)
    25. continue
    26. }
    27. switch hdr.Typeflag {
    28. case tar.TypeDir:
    29. if w.shouldSkipDir(filePath) {
    30. skipDirs = append(skipDirs, filePath)
    31. continue
    32. }
    33. case tar.TypeSymlink, tar.TypeLink, tar.TypeReg:
    34. if w.shouldSkipFile(filePath) {
    35. continue
    36. }
    37. default:
    38. continue
    39. }
    40. if underSkippedDir(filePath, skipDirs) {
    41. continue
    42. }
    43. // A symbolic/hard link or regular file will reach here.
    44. if err = w.processFile(filePath, tr, hdr.FileInfo(), analyzeFn); err != nil {//处理文件
    45. return nil, nil, xerrors.Errorf("failed to process the file: %w", err)
    46. }
    47. }
    48. return opqDirs, whFiles, nil
    49. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk=>processFile

    1. func (w LayerTar) processFile(filePath string, tr *tar.Reader, fi fs.FileInfo, analyzeFn WalkFunc) error {
    2. tf := newTarFile(fi.Size(), tr)
    3. defer func() {
    4. // nolint
    5. _ = tf.Clean()
    6. }()
    7. //传入的函数指针为匿名哈数,里面调用的对应的是我们前面看到a.analyzer.AnalyzeFile,下面我们看看具体实现
    8. if err := analyzeFn(filePath, fi, tf.Open); err != nil {
    9. return xerrors.Errorf("failed to analyze file: %w", err)
    10. }
    11. return nil
    12. }

    main=>run=>NewApp=>NewImageCommand=>Run=>ScanImage=>scanArtifact=>scan=>ScanArtifact=>Inspect=>inspect=>inspectLayer=>Walk=>processFile=>AnalyzeFile

    1. func (ag AnalyzerGroup) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, limit *semaphore.Weighted, result *AnalysisResult,
    2. dir, filePath string, info os.FileInfo, opener Opener, disabled []Type, opts AnalysisOptions) error {
    3. if info.IsDir() {
    4. return nil
    5. }
    6. //遍历所有分析器,一个一个执行
    7. for _, a := range ag.analyzers {
    8. // Skip disabled analyzers
    9. if slices.Contains(disabled, a.Type()) {//是否禁用了某些类型,是则跳过
    10. continue
    11. }
    12. // filepath extracted from tar file doesn't have the prefix "/"
    13. if !a.Required(strings.TrimLeft(filePath, "/"), info) {//调用Required检查文件名是否是对应的分析器的所指定的文件,在我们的这个例子中为etc/lsb-release,检查通过,继续
    14. continue
    15. }
    16. rc, err := opener()
    17. if errors.Is(err, fs.ErrPermission) {
    18. log.Logger.Debugf("Permission error: %s", filePath)
    19. break
    20. } else if err != nil {
    21. return xerrors.Errorf("unable to open %s: %w", filePath, err)
    22. }
    23. if err = limit.Acquire(ctx, 1); err != nil {//获取信号量,进行并发控制
    24. return xerrors.Errorf("semaphore acquire: %w", err)
    25. }
    26. wg.Add(1)
    27. go func(a analyzer, rc dio.ReadSeekCloserAt) {//启动 goroutine,执行分析器
    28. defer limit.Release(1)
    29. defer wg.Done()
    30. defer rc.Close()
    31. ret, err := a.Analyze(ctx, AnalysisInput{
    32. Dir: dir,
    33. FilePath: filePath,
    34. Info: info,
    35. Content: rc,
    36. Options: opts,
    37. })//执行分析函数,对应我们的就是ubuntuOSAnalyzer.Analyze函数
    38. if err != nil && !xerrors.Is(err, aos.AnalyzeOSError) {
    39. log.Logger.Debugf("Analysis error: %s", err)
    40. return
    41. }
    42. if ret != nil {
    43. result.Merge(ret)
    44. }
    45. }(a, rc)
    46. }
    47. return nil
    48. }

    至此整个代码的基本流程就讲完了。

  • 相关阅读:
    数据采集的数据源有哪些?
    PDF怎么翻译成中文?这些方法值得收藏
    最佳策略app平台传出的绝密理财法,这是给散户们的好机会
    开关电源测试解决方案之浪涌电流测试 -纳米软件
    数据预处理 #数据挖掘 #python
    LLMs NLP模型评估Model evaluation ROUGE and BLEU SCORE
    SATA系列专题之四:4.1 Command Layer命令分类详细解析
    第十四届蓝桥杯模拟赛(第二场)题解·2022年·C/C++
    【HTTP】URL结构、HTTP请求和响应的报文格式、HTTP请求的方法、常见的状态码、GET和POST有什么区别、Cookie、Session等重点知识汇总
    大数据热点城市波动图案例【CSS3实现 + 原理分析 + 源码获取】
  • 原文地址:https://blog.csdn.net/guoguangwu/article/details/126085748