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