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  }