github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/dockerfile/parser/parser.go (about) 1 package parser 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "path/filepath" 9 "strings" 10 11 "github.com/khulnasoft-lab/defsec/pkg/debug" 12 13 "github.com/khulnasoft-lab/defsec/pkg/detection" 14 "github.com/khulnasoft-lab/defsec/pkg/providers/dockerfile" 15 "github.com/khulnasoft-lab/defsec/pkg/scanners/options" 16 "github.com/moby/buildkit/frontend/dockerfile/instructions" 17 "github.com/moby/buildkit/frontend/dockerfile/parser" 18 ) 19 20 var _ options.ConfigurableParser = (*Parser)(nil) 21 22 type Parser struct { 23 debug debug.Logger 24 skipRequired bool 25 } 26 27 func (p *Parser) SetDebugWriter(writer io.Writer) { 28 p.debug = debug.New(writer, "dockerfile", "parser") 29 } 30 31 func (p *Parser) SetSkipRequiredCheck(b bool) { 32 p.skipRequired = b 33 } 34 35 // New creates a new Dockerfile parser 36 func New(options ...options.ParserOption) *Parser { 37 p := &Parser{} 38 for _, option := range options { 39 option(p) 40 } 41 return p 42 } 43 44 func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]*dockerfile.Dockerfile, error) { 45 46 files := make(map[string]*dockerfile.Dockerfile) 47 if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { 48 select { 49 case <-ctx.Done(): 50 return ctx.Err() 51 default: 52 } 53 if err != nil { 54 return err 55 } 56 if entry.IsDir() { 57 return nil 58 } 59 if !p.Required(path) { 60 return nil 61 } 62 df, err := p.ParseFile(ctx, target, path) 63 if err != nil { 64 // TODO add debug for parse errors 65 return nil 66 } 67 files[path] = df 68 return nil 69 }); err != nil { 70 return nil, err 71 } 72 return files, nil 73 } 74 75 // ParseFile parses Dockerfile content from the provided filesystem path. 76 func (p *Parser) ParseFile(_ context.Context, fs fs.FS, path string) (*dockerfile.Dockerfile, error) { 77 f, err := fs.Open(filepath.ToSlash(path)) 78 if err != nil { 79 return nil, err 80 } 81 defer func() { _ = f.Close() }() 82 return p.parse(path, f) 83 } 84 85 func (p *Parser) Required(path string) bool { 86 if p.skipRequired { 87 return true 88 } 89 return detection.IsType(path, nil, detection.FileTypeDockerfile) 90 } 91 92 func (p *Parser) parse(path string, r io.Reader) (*dockerfile.Dockerfile, error) { 93 parsed, err := parser.Parse(r) 94 if err != nil { 95 return nil, fmt.Errorf("dockerfile parse error: %w", err) 96 } 97 98 var parsedFile dockerfile.Dockerfile 99 var stage dockerfile.Stage 100 var stageIndex int 101 fromValue := "args" 102 for _, child := range parsed.AST.Children { 103 child.Value = strings.ToLower(child.Value) 104 105 instr, err := instructions.ParseInstruction(child) 106 if err != nil { 107 return nil, fmt.Errorf("process dockerfile instructions: %w", err) 108 } 109 110 if _, ok := instr.(*instructions.Stage); ok { 111 if len(stage.Commands) > 0 { 112 parsedFile.Stages = append(parsedFile.Stages, stage) 113 } 114 if fromValue != "args" { 115 stageIndex++ 116 } 117 fromValue = strings.TrimSpace(strings.TrimPrefix(child.Original, "FROM ")) 118 stage = dockerfile.Stage{ 119 Name: fromValue, 120 } 121 } 122 123 cmd := dockerfile.Command{ 124 Cmd: child.Value, 125 Original: child.Original, 126 Flags: child.Flags, 127 Stage: stageIndex, 128 Path: path, 129 StartLine: child.StartLine, 130 EndLine: child.EndLine, 131 } 132 133 if child.Next != nil && len(child.Next.Children) > 0 { 134 cmd.SubCmd = child.Next.Children[0].Value 135 child = child.Next.Children[0] 136 } 137 138 cmd.JSON = child.Attributes["json"] 139 for n := child.Next; n != nil; n = n.Next { 140 cmd.Value = append(cmd.Value, n.Value) 141 } 142 143 stage.Commands = append(stage.Commands, cmd) 144 145 } 146 if len(stage.Commands) > 0 { 147 parsedFile.Stages = append(parsedFile.Stages, stage) 148 } 149 150 return &parsedFile, nil 151 }