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  }