github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/cloudformation/scanner.go (about) 1 package cloudformation 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "sort" 9 "sync" 10 11 "github.com/aquasecurity/defsec/pkg/debug" 12 "github.com/aquasecurity/defsec/pkg/framework" 13 "github.com/aquasecurity/defsec/pkg/rego" 14 "github.com/aquasecurity/defsec/pkg/rules" 15 "github.com/aquasecurity/defsec/pkg/scan" 16 "github.com/aquasecurity/defsec/pkg/scanners/options" 17 "github.com/aquasecurity/defsec/pkg/types" 18 19 adapter "github.com/aquasecurity/trivy-iac/internal/adapters/cloudformation" 20 "github.com/aquasecurity/trivy-iac/pkg/scanners" 21 "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" 22 ) 23 24 func WithParameters(params map[string]any) options.ScannerOption { 25 return func(cs options.ConfigurableScanner) { 26 if s, ok := cs.(*Scanner); ok { 27 s.addParserOptions(parser.WithParameters(params)) 28 } 29 } 30 } 31 32 func WithParameterFiles(files ...string) options.ScannerOption { 33 return func(cs options.ConfigurableScanner) { 34 if s, ok := cs.(*Scanner); ok { 35 s.addParserOptions(parser.WithParameterFiles(files...)) 36 } 37 } 38 } 39 40 func WithConfigsFS(fsys fs.FS) options.ScannerOption { 41 return func(cs options.ConfigurableScanner) { 42 if s, ok := cs.(*Scanner); ok { 43 s.addParserOptions(parser.WithConfigsFS(fsys)) 44 } 45 } 46 } 47 48 var _ scanners.FSScanner = (*Scanner)(nil) 49 var _ options.ConfigurableScanner = (*Scanner)(nil) 50 51 type Scanner struct { 52 debug debug.Logger 53 policyDirs []string 54 policyReaders []io.Reader 55 parser *parser.Parser 56 regoScanner *rego.Scanner 57 skipRequired bool 58 regoOnly bool 59 loadEmbeddedPolicies bool 60 loadEmbeddedLibraries bool 61 options []options.ScannerOption 62 parserOptions []options.ParserOption 63 frameworks []framework.Framework 64 spec string 65 sync.Mutex 66 } 67 68 func (s *Scanner) addParserOptions(opt options.ParserOption) { 69 s.parserOptions = append(s.parserOptions, opt) 70 } 71 72 func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { 73 s.frameworks = frameworks 74 } 75 76 func (s *Scanner) SetSpec(spec string) { 77 s.spec = spec 78 } 79 80 func (s *Scanner) SetUseEmbeddedPolicies(b bool) { 81 s.loadEmbeddedPolicies = b 82 } 83 84 func (s *Scanner) SetUseEmbeddedLibraries(b bool) { 85 s.loadEmbeddedLibraries = b 86 } 87 88 func (s *Scanner) SetRegoOnly(regoOnly bool) { 89 s.regoOnly = regoOnly 90 } 91 92 func (s *Scanner) Name() string { 93 return "CloudFormation" 94 } 95 96 func (s *Scanner) SetPolicyReaders(readers []io.Reader) { 97 s.policyReaders = readers 98 } 99 100 func (s *Scanner) SetSkipRequiredCheck(skip bool) { 101 s.skipRequired = skip 102 } 103 104 func (s *Scanner) SetDebugWriter(writer io.Writer) { 105 s.debug = debug.New(writer, "cloudformation", "scanner") 106 } 107 108 func (s *Scanner) SetPolicyDirs(dirs ...string) { 109 s.policyDirs = dirs 110 } 111 112 func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { 113 // handled by rego when option is passed on 114 } 115 116 func (s *Scanner) SetDataFilesystem(_ fs.FS) { 117 // handled by rego when option is passed on 118 } 119 func (s *Scanner) SetRegoErrorLimit(_ int) {} 120 121 func (s *Scanner) SetTraceWriter(_ io.Writer) {} 122 func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} 123 func (s *Scanner) SetDataDirs(_ ...string) {} 124 func (s *Scanner) SetPolicyNamespaces(_ ...string) {} 125 126 // New creates a new Scanner 127 func New(opts ...options.ScannerOption) *Scanner { 128 s := &Scanner{ 129 options: opts, 130 } 131 for _, opt := range opts { 132 opt(s) 133 } 134 s.addParserOptions(options.ParserWithSkipRequiredCheck(s.skipRequired)) 135 s.parser = parser.New(s.parserOptions...) 136 return s 137 } 138 139 func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { 140 s.Lock() 141 defer s.Unlock() 142 if s.regoScanner != nil { 143 return s.regoScanner, nil 144 } 145 regoScanner := rego.NewScanner(types.SourceCloud, s.options...) 146 regoScanner.SetParentDebugLogger(s.debug) 147 if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { 148 return nil, err 149 } 150 s.regoScanner = regoScanner 151 return regoScanner, nil 152 } 153 154 func (s *Scanner) ScanFS(ctx context.Context, fs fs.FS, dir string) (results scan.Results, err error) { 155 156 contexts, err := s.parser.ParseFS(ctx, fs, dir) 157 if err != nil { 158 return nil, err 159 } 160 161 if len(contexts) == 0 { 162 return nil, nil 163 } 164 165 regoScanner, err := s.initRegoScanner(fs) 166 if err != nil { 167 return nil, err 168 } 169 170 for _, cfCtx := range contexts { 171 if cfCtx == nil { 172 continue 173 } 174 fileResults, err := s.scanFileContext(ctx, regoScanner, cfCtx, fs) 175 if err != nil { 176 return nil, err 177 } 178 results = append(results, fileResults...) 179 } 180 sort.Slice(results, func(i, j int) bool { 181 return results[i].Rule().AVDID < results[j].Rule().AVDID 182 }) 183 return results, nil 184 } 185 186 func (s *Scanner) ScanFile(ctx context.Context, fs fs.FS, path string) (scan.Results, error) { 187 188 cfCtx, err := s.parser.ParseFile(ctx, fs, path) 189 if err != nil { 190 return nil, err 191 } 192 193 regoScanner, err := s.initRegoScanner(fs) 194 if err != nil { 195 return nil, err 196 } 197 198 results, err := s.scanFileContext(ctx, regoScanner, cfCtx, fs) 199 if err != nil { 200 return nil, err 201 } 202 results.SetSourceAndFilesystem("", fs, false) 203 204 sort.Slice(results, func(i, j int) bool { 205 return results[i].Rule().AVDID < results[j].Rule().AVDID 206 }) 207 return results, nil 208 } 209 210 func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner, cfCtx *parser.FileContext, fs fs.FS) (results scan.Results, err error) { 211 state := adapter.Adapt(*cfCtx) 212 if state == nil { 213 return nil, nil 214 } 215 if !s.regoOnly { 216 for _, rule := range rules.GetRegistered(s.frameworks...) { 217 select { 218 case <-ctx.Done(): 219 return nil, ctx.Err() 220 default: 221 } 222 if rule.GetRule().RegoPackage != "" { 223 continue 224 } 225 evalResult := rule.Evaluate(state) 226 if len(evalResult) > 0 { 227 s.debug.Log("Found %d results for %s", len(evalResult), rule.GetRule().AVDID) 228 for _, scanResult := range evalResult { 229 230 ref := scanResult.Metadata().Reference() 231 232 if ref == "" && scanResult.Metadata().Parent() != nil { 233 ref = scanResult.Metadata().Parent().Reference() 234 } 235 236 description := getDescription(scanResult, ref) 237 scanResult.OverrideDescription(description) 238 results = append(results, scanResult) 239 } 240 } 241 } 242 } 243 regoResults, err := regoScanner.ScanInput(ctx, rego.Input{ 244 Path: cfCtx.Metadata().Range().GetFilename(), 245 FS: fs, 246 Contents: state.ToRego(), 247 }) 248 if err != nil { 249 return nil, fmt.Errorf("rego scan error: %w", err) 250 } 251 return append(results, regoResults...), nil 252 } 253 254 func getDescription(scanResult scan.Result, ref string) string { 255 switch scanResult.Status() { 256 case scan.StatusPassed: 257 return fmt.Sprintf("Resource '%s' passed check: %s", ref, scanResult.Rule().Summary) 258 case scan.StatusIgnored: 259 return fmt.Sprintf("Resource '%s' had check ignored: %s", ref, scanResult.Rule().Summary) 260 default: 261 return scanResult.Description() 262 } 263 }