github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/executor/executor.go (about)

     1  package executor
     2  
     3  import (
     4  	"runtime"
     5  	"sort"
     6  	"time"
     7  
     8  	"github.com/khulnasoft-lab/defsec/pkg/framework"
     9  
    10  	"github.com/khulnasoft-lab/defsec/pkg/debug"
    11  
    12  	"github.com/khulnasoft-lab/defsec/pkg/terraform"
    13  
    14  	"github.com/khulnasoft-lab/defsec/pkg/severity"
    15  
    16  	"github.com/khulnasoft-lab/defsec/pkg/state"
    17  
    18  	"github.com/khulnasoft-lab/defsec/pkg/scan"
    19  
    20  	"github.com/khulnasoft-lab/defsec/pkg/rules"
    21  
    22  	"github.com/khulnasoft-lab/defsec/pkg/rego"
    23  
    24  	adapter "github.com/khulnasoft-lab/defsec/internal/adapters/terraform"
    25  )
    26  
    27  // Executor scans HCL blocks by running all registered rules against them
    28  type Executor struct {
    29  	enableIgnores             bool
    30  	excludedRuleIDs           []string
    31  	excludeIgnoresIDs         []string
    32  	includedRuleIDs           []string
    33  	ignoreCheckErrors         bool
    34  	workspaceName             string
    35  	useSingleThread           bool
    36  	debug                     debug.Logger
    37  	resultsFilters            []func(scan.Results) scan.Results
    38  	alternativeIDProviderFunc func(string) []string
    39  	severityOverrides         map[string]string
    40  	regoScanner               *rego.Scanner
    41  	regoOnly                  bool
    42  	stateFuncs                []func(*state.State)
    43  	frameworks                []framework.Framework
    44  }
    45  
    46  type Metrics struct {
    47  	Timings struct {
    48  		Adaptation    time.Duration
    49  		RunningChecks time.Duration
    50  	}
    51  	Counts struct {
    52  		Ignored  int
    53  		Failed   int
    54  		Passed   int
    55  		Critical int
    56  		High     int
    57  		Medium   int
    58  		Low      int
    59  	}
    60  }
    61  
    62  // New creates a new Executor
    63  func New(options ...Option) *Executor {
    64  	s := &Executor{
    65  		ignoreCheckErrors: true,
    66  		enableIgnores:     true,
    67  		regoOnly:          false,
    68  	}
    69  	for _, option := range options {
    70  		option(s)
    71  	}
    72  	return s
    73  }
    74  
    75  // Find element in list
    76  func checkInList(id string, altIDs []string, list []string) bool {
    77  	for _, codeIgnored := range list {
    78  		if codeIgnored == id {
    79  			return true
    80  		}
    81  		for _, alt := range altIDs {
    82  			if alt == codeIgnored {
    83  				return true
    84  			}
    85  		}
    86  	}
    87  	return false
    88  }
    89  
    90  func (e *Executor) Execute(modules terraform.Modules) (scan.Results, Metrics, error) {
    91  
    92  	var metrics Metrics
    93  
    94  	e.debug.Log("Adapting modules...")
    95  	adaptationTime := time.Now()
    96  	infra := adapter.Adapt(modules)
    97  	metrics.Timings.Adaptation = time.Since(adaptationTime)
    98  	e.debug.Log("Adapted %d module(s) into defsec state data.", len(modules))
    99  
   100  	threads := runtime.NumCPU()
   101  	if threads > 1 {
   102  		threads--
   103  	}
   104  	if e.useSingleThread {
   105  		threads = 1
   106  	}
   107  	e.debug.Log("Using max routines of %d", threads)
   108  
   109  	e.debug.Log("Applying state modifier functions...")
   110  	for _, f := range e.stateFuncs {
   111  		f(infra)
   112  	}
   113  
   114  	checksTime := time.Now()
   115  	registeredRules := rules.GetRegistered(e.frameworks...)
   116  	e.debug.Log("Initialised %d rule(s).", len(registeredRules))
   117  
   118  	pool := NewPool(threads, registeredRules, modules, infra, e.ignoreCheckErrors, e.regoScanner, e.regoOnly)
   119  	e.debug.Log("Created pool with %d worker(s) to apply rules.", threads)
   120  	results, err := pool.Run()
   121  	if err != nil {
   122  		return nil, metrics, err
   123  	}
   124  	metrics.Timings.RunningChecks = time.Since(checksTime)
   125  	e.debug.Log("Finished applying rules.")
   126  
   127  	if e.enableIgnores {
   128  		e.debug.Log("Applying ignores...")
   129  		var ignores terraform.Ignores
   130  		for _, module := range modules {
   131  			ignores = append(ignores, module.Ignores()...)
   132  		}
   133  
   134  		ignores = e.removeExcludedIgnores(ignores)
   135  
   136  		for i, result := range results {
   137  			allIDs := []string{
   138  				result.Rule().LongID(),
   139  				result.Rule().AVDID,
   140  				result.Rule().ShortCode,
   141  			}
   142  			allIDs = append(allIDs, result.Rule().Aliases...)
   143  
   144  			if e.alternativeIDProviderFunc != nil {
   145  				allIDs = append(allIDs, e.alternativeIDProviderFunc(result.Rule().LongID())...)
   146  			}
   147  			if ignores.Covering(
   148  				modules,
   149  				result.Metadata(),
   150  				e.workspaceName,
   151  				allIDs...,
   152  			) != nil {
   153  				e.debug.Log("Ignored '%s' at '%s'.", result.Rule().LongID(), result.Range())
   154  				results[i].OverrideStatus(scan.StatusIgnored)
   155  			}
   156  		}
   157  	} else {
   158  		e.debug.Log("Ignores are disabled.")
   159  	}
   160  
   161  	results = e.updateSeverity(results)
   162  	results = e.filterResults(results)
   163  	metrics.Counts.Ignored = len(results.GetIgnored())
   164  	metrics.Counts.Passed = len(results.GetPassed())
   165  	metrics.Counts.Failed = len(results.GetFailed())
   166  
   167  	for _, res := range results.GetFailed() {
   168  		switch res.Severity() {
   169  		case severity.Critical:
   170  			metrics.Counts.Critical++
   171  		case severity.High:
   172  			metrics.Counts.High++
   173  		case severity.Medium:
   174  			metrics.Counts.Medium++
   175  		case severity.Low:
   176  			metrics.Counts.Low++
   177  		}
   178  	}
   179  
   180  	e.sortResults(results)
   181  	return results, metrics, nil
   182  }
   183  
   184  func (e *Executor) removeExcludedIgnores(ignores terraform.Ignores) terraform.Ignores {
   185  	var filteredIgnores terraform.Ignores
   186  	for _, ignore := range ignores {
   187  		if !contains(e.excludeIgnoresIDs, ignore.RuleID) {
   188  			filteredIgnores = append(filteredIgnores, ignore)
   189  		}
   190  	}
   191  	return filteredIgnores
   192  }
   193  
   194  func contains(arr []string, s string) bool {
   195  	for _, elem := range arr {
   196  		if elem == s {
   197  			return true
   198  		}
   199  	}
   200  	return false
   201  }
   202  
   203  func (e *Executor) updateSeverity(results []scan.Result) scan.Results {
   204  	if len(e.severityOverrides) == 0 {
   205  		return results
   206  	}
   207  
   208  	var overriddenResults scan.Results
   209  	for _, res := range results {
   210  		for code, sev := range e.severityOverrides {
   211  
   212  			var altMatch bool
   213  			if e.alternativeIDProviderFunc != nil {
   214  				alts := e.alternativeIDProviderFunc(res.Rule().LongID())
   215  				for _, alt := range alts {
   216  					if alt == code {
   217  						altMatch = true
   218  						break
   219  					}
   220  				}
   221  			}
   222  
   223  			if altMatch || res.Rule().LongID() == code {
   224  				overrides := scan.Results([]scan.Result{res})
   225  				override := res.Rule()
   226  				override.Severity = severity.Severity(sev)
   227  				overrides.SetRule(override)
   228  				res = overrides[0]
   229  			}
   230  		}
   231  		overriddenResults = append(overriddenResults, res)
   232  	}
   233  
   234  	return overriddenResults
   235  }
   236  
   237  func (e *Executor) filterResults(results scan.Results) scan.Results {
   238  	includedOnly := len(e.includedRuleIDs) > 0
   239  	for i, result := range results {
   240  		id := result.Rule().LongID()
   241  		var altIDs []string
   242  		if e.alternativeIDProviderFunc != nil {
   243  			altIDs = e.alternativeIDProviderFunc(id)
   244  		}
   245  		if (includedOnly && !checkInList(id, altIDs, e.includedRuleIDs)) || checkInList(id, altIDs, e.excludedRuleIDs) {
   246  			e.debug.Log("Excluding '%s' at '%s'.", result.Rule().LongID(), result.Range())
   247  			results[i].OverrideStatus(scan.StatusIgnored)
   248  		}
   249  	}
   250  
   251  	if len(e.resultsFilters) > 0 && len(results) > 0 {
   252  		before := len(results.GetIgnored())
   253  		e.debug.Log("Applying %d results filters to %d results...", len(results), before)
   254  		for _, filter := range e.resultsFilters {
   255  			results = filter(results)
   256  		}
   257  		e.debug.Log("Filtered out %d results.", len(results.GetIgnored())-before)
   258  	}
   259  
   260  	return results
   261  }
   262  
   263  func (e *Executor) sortResults(results []scan.Result) {
   264  	sort.Slice(results, func(i, j int) bool {
   265  		switch {
   266  		case results[i].Rule().LongID() < results[j].Rule().LongID():
   267  			return true
   268  		case results[i].Rule().LongID() > results[j].Rule().LongID():
   269  			return false
   270  		default:
   271  			return results[i].Range().String() > results[j].Range().String()
   272  		}
   273  	})
   274  }