github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/kubernetes/parser/parser.go (about) 1 package parser 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/fs" 10 "path/filepath" 11 "regexp" 12 "strings" 13 14 "gopkg.in/yaml.v3" 15 16 "github.com/aquasecurity/defsec/pkg/debug" 17 "github.com/aquasecurity/defsec/pkg/scanners/options" 18 "github.com/aquasecurity/trivy-iac/pkg/detection" 19 ) 20 21 var _ options.ConfigurableParser = (*Parser)(nil) 22 23 type Parser struct { 24 debug debug.Logger 25 skipRequired bool 26 } 27 28 func (p *Parser) SetDebugWriter(writer io.Writer) { 29 p.debug = debug.New(writer, "kubernetes", "parser") 30 } 31 32 func (p *Parser) SetSkipRequiredCheck(b bool) { 33 p.skipRequired = b 34 } 35 36 // New creates a new K8s parser 37 func New(options ...options.ParserOption) *Parser { 38 p := &Parser{} 39 for _, option := range options { 40 option(p) 41 } 42 return p 43 } 44 45 func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]interface{}, error) { 46 files := make(map[string][]interface{}) 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(target, path) { 60 return nil 61 } 62 parsed, err := p.ParseFile(ctx, target, path) 63 if err != nil { 64 p.debug.Log("Parse error in '%s': %s", path, err) 65 return nil 66 } 67 files[path] = parsed 68 return nil 69 }); err != nil { 70 return nil, err 71 } 72 return files, nil 73 } 74 75 // ParseFile parses Kubernetes manifest from the provided filesystem path. 76 func (p *Parser) ParseFile(_ context.Context, fs fs.FS, path string) ([]interface{}, 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(f, path) 83 } 84 85 func (p *Parser) required(fs fs.FS, path string) bool { 86 if p.skipRequired { 87 return true 88 } 89 f, err := fs.Open(filepath.ToSlash(path)) 90 if err != nil { 91 return false 92 } 93 defer func() { _ = f.Close() }() 94 if data, err := io.ReadAll(f); err == nil { 95 return detection.IsType(path, bytes.NewReader(data), detection.FileTypeKubernetes) 96 } 97 return false 98 } 99 100 func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) { 101 102 contents, err := io.ReadAll(r) 103 if err != nil { 104 return nil, err 105 } 106 107 if len(contents) == 0 { 108 return nil, nil 109 } 110 111 if strings.TrimSpace(string(contents))[0] == '{' { 112 var target interface{} 113 if err := json.Unmarshal(contents, &target); err != nil { 114 return nil, err 115 } 116 return []interface{}{target}, nil 117 } 118 119 var results []interface{} 120 121 re := regexp.MustCompile(`(?m:^---\r?\n)`) 122 pos := 0 123 for _, partial := range re.Split(string(contents), -1) { 124 var result Manifest 125 result.Path = path 126 if err := yaml.Unmarshal([]byte(partial), &result); err != nil { 127 return nil, fmt.Errorf("unmarshal yaml: %w", err) 128 } 129 if result.Content != nil { 130 result.Content.Offset = pos 131 results = append(results, result.ToRego()) 132 } 133 pos += len(strings.Split(partial, "\n")) 134 } 135 136 return results, nil 137 }