github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/cloudformation/parser/parser.go (about) 1 package parser 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/fs" 9 "path/filepath" 10 "strings" 11 12 "github.com/khulnasoft-lab/defsec/pkg/debug" 13 14 "github.com/khulnasoft-lab/defsec/pkg/detection" 15 "github.com/khulnasoft-lab/defsec/pkg/scanners/options" 16 17 "github.com/liamg/jfather" 18 "gopkg.in/yaml.v3" 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, "cloudformation", "parser") 30 } 31 32 func (p *Parser) SetSkipRequiredCheck(b bool) { 33 p.skipRequired = b 34 } 35 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, dir string) (FileContexts, error) { 45 var contexts FileContexts 46 if err := fs.WalkDir(target, filepath.ToSlash(dir), func(path string, entry fs.DirEntry, err error) error { 47 select { 48 case <-ctx.Done(): 49 return ctx.Err() 50 default: 51 } 52 if err != nil { 53 return err 54 } 55 if entry.IsDir() { 56 return nil 57 } 58 59 if !p.Required(target, path) { 60 p.debug.Log("not a CloudFormation file, skipping %s", path) 61 return nil 62 } 63 64 c, err := p.ParseFile(ctx, target, path) 65 if err != nil { 66 p.debug.Log("Error parsing file '%s': %s", path, err) 67 return nil 68 } 69 contexts = append(contexts, c) 70 return nil 71 }); err != nil { 72 return nil, err 73 } 74 return contexts, nil 75 } 76 77 func (p *Parser) Required(fs fs.FS, path string) bool { 78 if p.skipRequired { 79 return true 80 } 81 82 f, err := fs.Open(filepath.ToSlash(path)) 83 if err != nil { 84 return false 85 } 86 defer func() { _ = f.Close() }() 87 if data, err := io.ReadAll(f); err == nil { 88 return detection.IsType(path, bytes.NewReader(data), detection.FileTypeCloudFormation) 89 } 90 return false 91 92 } 93 94 func (p *Parser) ParseFile(ctx context.Context, fs fs.FS, path string) (context *FileContext, err error) { 95 96 defer func() { 97 if e := recover(); e != nil { 98 err = fmt.Errorf("panic during parse: %s", e) 99 } 100 }() 101 102 select { 103 case <-ctx.Done(): 104 return nil, ctx.Err() 105 default: 106 } 107 108 sourceFmt := YamlSourceFormat 109 if strings.HasSuffix(strings.ToLower(path), ".json") { 110 sourceFmt = JsonSourceFormat 111 } 112 113 f, err := fs.Open(filepath.ToSlash(path)) 114 if err != nil { 115 return nil, err 116 } 117 defer func() { _ = f.Close() }() 118 119 content, err := io.ReadAll(f) 120 if err != nil { 121 return nil, err 122 } 123 124 lines := strings.Split(string(content), "\n") 125 126 context = &FileContext{ 127 filepath: path, 128 lines: lines, 129 SourceFormat: sourceFmt, 130 } 131 132 if strings.HasSuffix(strings.ToLower(path), ".json") { 133 if err := jfather.Unmarshal(content, context); err != nil { 134 return nil, NewErrInvalidContent(path, err) 135 } 136 } else { 137 if err := yaml.Unmarshal(content, context); err != nil { 138 return nil, NewErrInvalidContent(path, err) 139 } 140 } 141 142 context.lines = lines 143 context.SourceFormat = sourceFmt 144 context.filepath = path 145 146 p.debug.Log("Context loaded from source %s", path) 147 148 // the context must be set to conditions before resources 149 for _, c := range context.Conditions { 150 c.setContext(context) 151 } 152 153 for name, r := range context.Resources { 154 r.ConfigureResource(name, fs, path, context) 155 } 156 157 return context, nil 158 }