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