trivy 获取基础镜像源码分析

44 篇文章 0 订阅
26 篇文章 1 订阅

启动初始化代码:

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

每种基础镜像通过调用RegisterAnalyzer来将自己的实现实例注册到analyzers哈希表中。因此,如果同一种类型的Analyzer重复调用,前面注册的对象会被覆盖。

获取基础镜像的核心就是analyzer的Analyze函数,数据源来自于对应系统的配置文件,例如Ubuntuetc/lsb-release

代码如下:

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
}

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sa8295p 源码分析中的第二部分是关于镜像分析的。镜像是计算机系统中存储程序和数据的一种方式。镜像分析就是对程序的镜像进行深入的研究和分析。 首先,镜像分析可以帮助我们了解程序的内部结构和运行机制。通过反汇编器和调试器等工具,我们可以查看程序的汇编指令、变量和函数等信息,了解程序的执行流程和数据处理方式。这有助于我们理解程序的具体实现细节,为进一步的分析奠定基础。 其次,镜像分析可以帮助我们发现程序的潜在安全问题。通过分析程序的镜像,我们可以查找其中的漏洞、弱点和不安全的代片段。例如,我们可以检查是否存在缓冲区溢出、访问控制错误或者代注入等安全隐患。通过找出这些问题,我们可以及时修复并提高程序的安全性。 此外,镜像分析还可以帮助我们进行逆向工程。通过分析程序的镜像,我们可以还原出源代的大致结构和功能,进一步了解程序的运行逻辑和算法。这对于研究和分析复杂的程序、解密加密算法或者进行软件逆向等方面非常有帮助。 总之,镜像分析源码分析中重要的一环,它能帮助我们理解程序的内部结构和运行机制,发现潜在的安全问题,以及进行逆向工程。通过深入研究程序的镜像,我们可以全面了解并优化程序的功能和性能,提高软件的质量和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值