github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/buildinfo/dockerfile.go (about)

     1  package buildinfo
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/moby/buildkit/frontend/dockerfile/instructions"
    10  	"github.com/moby/buildkit/frontend/dockerfile/parser"
    11  	"github.com/moby/buildkit/frontend/dockerfile/shell"
    12  	"golang.org/x/xerrors"
    13  
    14  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    15  	"github.com/devseccon/trivy/pkg/fanal/types"
    16  )
    17  
    18  func init() {
    19  	analyzer.RegisterAnalyzer(&dockerfileAnalyzer{})
    20  }
    21  
    22  const dockerfileAnalyzerVersion = 1
    23  
    24  // For Red Hat products
    25  type dockerfileAnalyzer struct{}
    26  
    27  func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
    28  	// ported from https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L73
    29  	dockerfile, err := parser.Parse(target.Content)
    30  	if err != nil {
    31  		return nil, xerrors.Errorf("dockerfile parse error: %w", err)
    32  	}
    33  
    34  	stages, metaArgs, err := instructions.Parse(dockerfile.AST)
    35  	if err != nil {
    36  		return nil, xerrors.Errorf("instruction parse error: %w", err)
    37  	}
    38  
    39  	var args []instructions.KeyValuePairOptional
    40  	for _, cmd := range metaArgs {
    41  		for _, metaArg := range cmd.Args {
    42  			args = append(args, setKVValue(metaArg, nil))
    43  		}
    44  	}
    45  
    46  	shlex := shell.NewLex(dockerfile.EscapeToken)
    47  	env := metaArgsToMap(args)
    48  
    49  	var component, arch string
    50  	for _, st := range stages {
    51  		for _, cmd := range st.Commands {
    52  			switch c := cmd.(type) {
    53  			case *instructions.EnvCommand:
    54  				for _, kvp := range c.Env {
    55  					env[kvp.Key] = kvp.Value
    56  				}
    57  			case *instructions.LabelCommand:
    58  				for _, kvp := range c.Labels {
    59  					key, err := shlex.ProcessWordWithMap(kvp.Key, env)
    60  					if err != nil {
    61  						return nil, xerrors.Errorf("unable to evaluate the label '%s': %w", kvp.Key, err)
    62  					}
    63  
    64  					key = strings.ToLower(key)
    65  					if key == "com.redhat.component" || key == "bzcomponent" {
    66  						component, err = shlex.ProcessWordWithMap(kvp.Value, env)
    67  					} else if key == "architecture" {
    68  						arch, err = shlex.ProcessWordWithMap(kvp.Value, env)
    69  					}
    70  
    71  					if err != nil {
    72  						return nil, xerrors.Errorf("failed to process the label '%s': %w", key, err)
    73  					}
    74  				}
    75  			}
    76  		}
    77  	}
    78  
    79  	if component == "" {
    80  		return nil, xerrors.New("no component found")
    81  	} else if arch == "" {
    82  		return nil, xerrors.New("no arch found")
    83  	}
    84  
    85  	return &analyzer.AnalysisResult{
    86  		BuildInfo: &types.BuildInfo{
    87  			Nvr:  component + "-" + parseVersion(target.FilePath),
    88  			Arch: arch,
    89  		},
    90  	}, nil
    91  }
    92  
    93  func (a dockerfileAnalyzer) Required(filePath string, _ os.FileInfo) bool {
    94  	dir, file := filepath.Split(filepath.ToSlash(filePath))
    95  	if dir != "root/buildinfo/" {
    96  		return false
    97  	}
    98  	return strings.HasPrefix(file, "Dockerfile")
    99  }
   100  
   101  func (a dockerfileAnalyzer) Type() analyzer.Type {
   102  	return analyzer.TypeRedHatDockerfileType
   103  }
   104  
   105  func (a dockerfileAnalyzer) Version() int {
   106  	return dockerfileAnalyzerVersion
   107  }
   108  
   109  // parseVersion parses version from a file name
   110  func parseVersion(nvr string) string {
   111  	releaseIndex := strings.LastIndex(nvr, "-")
   112  	if releaseIndex < 0 {
   113  		return ""
   114  	}
   115  	versionIndex := strings.LastIndex(nvr[:releaseIndex], "-")
   116  	version := nvr[versionIndex+1:]
   117  	return version
   118  }
   119  
   120  // https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L474-L482
   121  func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string {
   122  	m := make(map[string]string)
   123  
   124  	for _, arg := range metaArgs {
   125  		m[arg.Key] = arg.ValueString()
   126  	}
   127  
   128  	return m
   129  }
   130  
   131  func setKVValue(kvpo instructions.KeyValuePairOptional, values map[string]string) instructions.KeyValuePairOptional {
   132  	if v, ok := values[kvpo.Key]; ok {
   133  		kvpo.Value = &v
   134  	}
   135  	return kvpo
   136  }