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