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 }