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 }