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 }