github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/terraform/executor/executor.go (about)

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