github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/terraform/executor/pool.go (about) 1 package executor 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 runtimeDebug "runtime/debug" 9 "strings" 10 "sync" 11 12 "github.com/aquasecurity/defsec/pkg/rego" 13 "github.com/aquasecurity/defsec/pkg/scan" 14 "github.com/aquasecurity/defsec/pkg/state" 15 "github.com/aquasecurity/defsec/pkg/terraform" 16 types "github.com/aquasecurity/defsec/pkg/types/rules" 17 ) 18 19 type Pool struct { 20 size int 21 modules terraform.Modules 22 state *state.State 23 rules []types.RegisteredRule 24 ignoreErrors bool 25 rs *rego.Scanner 26 regoOnly bool 27 } 28 29 func NewPool(size int, rules []types.RegisteredRule, modules terraform.Modules, state *state.State, ignoreErrors bool, regoScanner *rego.Scanner, regoOnly bool) *Pool { 30 return &Pool{ 31 size: size, 32 rules: rules, 33 state: state, 34 modules: modules, 35 ignoreErrors: ignoreErrors, 36 rs: regoScanner, 37 regoOnly: regoOnly, 38 } 39 } 40 41 // Run runs the job in the pool - this will only return an error if a job panics 42 func (p *Pool) Run() (scan.Results, error) { 43 44 outgoing := make(chan Job, p.size*2) 45 46 var workers []*Worker 47 for i := 0; i < p.size; i++ { 48 worker := NewWorker(outgoing) 49 go worker.Start() 50 workers = append(workers, worker) 51 } 52 53 if p.rs != nil { 54 var basePath string 55 if len(p.modules) > 0 { 56 basePath = p.modules[0].RootPath() 57 } 58 outgoing <- ®oJob{ 59 state: p.state, 60 scanner: p.rs, 61 basePath: basePath, 62 } 63 } 64 65 if !p.regoOnly { 66 for _, r := range p.rules { 67 if r.GetRule().CustomChecks.Terraform != nil && r.GetRule().CustomChecks.Terraform.Check != nil { 68 // run local hcl rule 69 for _, module := range p.modules { 70 mod := *module 71 outgoing <- &hclModuleRuleJob{ 72 module: &mod, 73 rule: r, 74 ignoreErrors: p.ignoreErrors, 75 } 76 } 77 } else { 78 // run defsec rule 79 outgoing <- &infraRuleJob{ 80 state: p.state, 81 rule: r, 82 ignoreErrors: p.ignoreErrors, 83 } 84 } 85 } 86 } 87 88 close(outgoing) 89 90 var results scan.Results 91 for _, worker := range workers { 92 results = append(results, worker.Wait()...) 93 if err := worker.Error(); err != nil { 94 return nil, err 95 } 96 } 97 98 return results, nil 99 } 100 101 type Job interface { 102 Run() (scan.Results, error) 103 } 104 105 type infraRuleJob struct { 106 state *state.State 107 rule types.RegisteredRule 108 109 ignoreErrors bool 110 } 111 112 type hclModuleRuleJob struct { 113 module *terraform.Module 114 rule types.RegisteredRule 115 ignoreErrors bool 116 } 117 118 type regoJob struct { 119 state *state.State 120 scanner *rego.Scanner 121 basePath string 122 } 123 124 func (h *infraRuleJob) Run() (_ scan.Results, err error) { 125 if h.ignoreErrors { 126 defer func() { 127 if panicErr := recover(); panicErr != nil { 128 err = fmt.Errorf("%s\n%s", panicErr, string(runtimeDebug.Stack())) 129 } 130 }() 131 } 132 return h.rule.Evaluate(h.state), err 133 } 134 135 func (h *hclModuleRuleJob) Run() (results scan.Results, err error) { 136 if h.ignoreErrors { 137 defer func() { 138 if panicErr := recover(); panicErr != nil { 139 err = fmt.Errorf("%s\n%s", panicErr, string(runtimeDebug.Stack())) 140 } 141 }() 142 } 143 customCheck := h.rule.GetRule().CustomChecks.Terraform 144 for _, block := range h.module.GetBlocks() { 145 if !isCustomCheckRequiredForBlock(customCheck, block) { 146 continue 147 } 148 results = append(results, customCheck.Check(block, h.module)...) 149 } 150 results.SetRule(h.rule.GetRule()) 151 return 152 } 153 154 func (h *regoJob) Run() (results scan.Results, err error) { 155 regoResults, err := h.scanner.ScanInput(context.TODO(), rego.Input{ 156 Contents: h.state.ToRego(), 157 Path: h.basePath, 158 }) 159 if err != nil { 160 return nil, fmt.Errorf("rego scan error: %w", err) 161 } 162 return regoResults, nil 163 } 164 165 // nolint 166 func isCustomCheckRequiredForBlock(custom *scan.TerraformCustomCheck, b *terraform.Block) bool { 167 168 var found bool 169 for _, requiredType := range custom.RequiredTypes { 170 if b.Type() == requiredType { 171 found = true 172 break 173 } 174 } 175 if !found && len(custom.RequiredTypes) > 0 { 176 return false 177 } 178 179 found = false 180 for _, requiredLabel := range custom.RequiredLabels { 181 if requiredLabel == "*" || (len(b.Labels()) > 0 && wildcardMatch(requiredLabel, b.TypeLabel())) { 182 found = true 183 break 184 } 185 } 186 if !found && len(custom.RequiredLabels) > 0 { 187 return false 188 } 189 190 found = false 191 if len(custom.RequiredSources) > 0 && b.Type() == terraform.TypeModule.Name() { 192 if sourceAttr := b.GetAttribute("source"); sourceAttr.IsNotNil() { 193 values := sourceAttr.AsStringValues().AsStrings() 194 if len(values) == 0 { 195 return false 196 } 197 sourcePath := values[0] 198 199 // resolve module source path to path relative to cwd 200 if strings.HasPrefix(sourcePath, ".") { 201 sourcePath = cleanPathRelativeToWorkingDir(filepath.Dir(b.GetMetadata().Range().GetFilename()), sourcePath) 202 } 203 204 for _, requiredSource := range custom.RequiredSources { 205 if requiredSource == "*" || wildcardMatch(requiredSource, sourcePath) { 206 found = true 207 break 208 } 209 } 210 } 211 return found 212 } 213 214 return true 215 } 216 217 func cleanPathRelativeToWorkingDir(dir, path string) string { 218 absPath := filepath.Clean(filepath.Join(dir, path)) 219 wDir, err := os.Getwd() 220 if err != nil { 221 return absPath 222 } 223 relPath, err := filepath.Rel(wDir, absPath) 224 if err != nil { 225 return absPath 226 } 227 return relPath 228 } 229 230 func wildcardMatch(pattern string, subject string) bool { 231 if pattern == "" { 232 return false 233 } 234 parts := strings.Split(pattern, "*") 235 var lastIndex int 236 for i, part := range parts { 237 if part == "" { 238 continue 239 } 240 if i == 0 { 241 if !strings.HasPrefix(subject, part) { 242 return false 243 } 244 } 245 if i == len(parts)-1 { 246 if !strings.HasSuffix(subject, part) { 247 return false 248 } 249 } 250 newIndex := strings.Index(subject, part) 251 if newIndex < lastIndex { 252 return false 253 } 254 lastIndex = newIndex 255 } 256 return true 257 } 258 259 type Worker struct { 260 incoming <-chan Job 261 mu sync.Mutex 262 results scan.Results 263 panic interface{} 264 } 265 266 func NewWorker(incoming <-chan Job) *Worker { 267 w := &Worker{ 268 incoming: incoming, 269 } 270 w.mu.Lock() 271 return w 272 } 273 274 func (w *Worker) Start() { 275 defer w.mu.Unlock() 276 w.results = nil 277 for job := range w.incoming { 278 func() { 279 results, err := job.Run() 280 if err != nil { 281 w.panic = err 282 } 283 w.results = append(w.results, results...) 284 }() 285 } 286 } 287 288 func (w *Worker) Wait() scan.Results { 289 w.mu.Lock() 290 defer w.mu.Unlock() 291 return w.results 292 } 293 294 func (w *Worker) Error() error { 295 if w.panic == nil { 296 return nil 297 } 298 return fmt.Errorf("job failed: %s", w.panic) 299 }