github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 9 "golang.org/x/xerrors" 10 11 "github.com/devseccon/trivy/pkg/fanal/analyzer" 12 "github.com/devseccon/trivy/pkg/fanal/image" 13 "github.com/devseccon/trivy/pkg/fanal/types" 14 "github.com/devseccon/trivy/pkg/mapfs" 15 "github.com/devseccon/trivy/pkg/misconf" 16 ) 17 18 const analyzerVersion = 1 19 20 func init() { 21 analyzer.RegisterConfigAnalyzer(analyzer.TypeHistoryDockerfile, newHistoryAnalyzer) 22 } 23 24 type historyAnalyzer struct { 25 scanner *misconf.Scanner 26 } 27 28 func newHistoryAnalyzer(opts analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { 29 s, err := misconf.NewDockerfileScanner(opts.FilePatterns, opts.MisconfScannerOption) 30 if err != nil { 31 return nil, xerrors.Errorf("misconfiguration scanner error: %w", err) 32 } 33 return &historyAnalyzer{ 34 scanner: s, 35 }, nil 36 } 37 38 func (a *historyAnalyzer) Analyze(ctx context.Context, input analyzer.ConfigAnalysisInput) (*analyzer. 39 ConfigAnalysisResult, error) { 40 if input.Config == nil { 41 return nil, nil 42 } 43 dockerfile := new(bytes.Buffer) 44 baseLayerIndex := image.GuessBaseImageIndex(input.Config.History) 45 for i := baseLayerIndex + 1; i < len(input.Config.History); i++ { 46 h := input.Config.History[i] 47 var createdBy string 48 switch { 49 case strings.HasPrefix(h.CreatedBy, "/bin/sh -c #(nop)"): 50 // Instruction other than RUN 51 createdBy = strings.TrimPrefix(h.CreatedBy, "/bin/sh -c #(nop)") 52 case strings.HasPrefix(h.CreatedBy, "/bin/sh -c"): 53 // RUN instruction 54 createdBy = strings.ReplaceAll(h.CreatedBy, "/bin/sh -c", "RUN") 55 case strings.HasPrefix(h.CreatedBy, "USER"): 56 // USER instruction 57 createdBy = h.CreatedBy 58 case strings.HasPrefix(h.CreatedBy, "HEALTHCHECK"): 59 // HEALTHCHECK instruction 60 var interval, timeout, startPeriod, retries, command string 61 if input.Config.Config.Healthcheck.Interval != 0 { 62 interval = fmt.Sprintf("--interval=%s ", input.Config.Config.Healthcheck.Interval) 63 } 64 if input.Config.Config.Healthcheck.Timeout != 0 { 65 timeout = fmt.Sprintf("--timeout=%s ", input.Config.Config.Healthcheck.Timeout) 66 } 67 if input.Config.Config.Healthcheck.StartPeriod != 0 { 68 startPeriod = fmt.Sprintf("--startPeriod=%s ", input.Config.Config.Healthcheck.StartPeriod) 69 } 70 if input.Config.Config.Healthcheck.Retries != 0 { 71 retries = fmt.Sprintf("--retries=%d ", input.Config.Config.Healthcheck.Retries) 72 } 73 command = strings.Join(input.Config.Config.Healthcheck.Test, " ") 74 command = strings.ReplaceAll(command, "CMD-SHELL", "CMD") 75 createdBy = fmt.Sprintf("HEALTHCHECK %s%s%s%s%s", interval, timeout, startPeriod, retries, command) 76 } 77 dockerfile.WriteString(strings.TrimSpace(createdBy) + "\n") 78 } 79 80 fsys := mapfs.New() 81 if err := fsys.WriteVirtualFile("Dockerfile", dockerfile.Bytes(), 0600); err != nil { 82 return nil, xerrors.Errorf("mapfs write error: %w", err) 83 } 84 85 misconfs, err := a.scanner.Scan(ctx, fsys) 86 if err != nil { 87 return nil, xerrors.Errorf("history scan error: %w", err) 88 } 89 // The result should be a single element as it passes one Dockerfile. 90 if len(misconfs) != 1 { 91 return nil, nil 92 } 93 94 return &analyzer.ConfigAnalysisResult{ 95 Misconfiguration: &misconfs[0], 96 }, nil 97 } 98 99 func (a *historyAnalyzer) Required(_ types.OS) bool { 100 return true 101 } 102 103 func (a *historyAnalyzer) Type() analyzer.Type { 104 return analyzer.TypeHistoryDockerfile 105 } 106 107 func (a *historyAnalyzer) Version() int { 108 return analyzerVersion 109 }