diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index e655932e01fd..3f1a0d2fbb65 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -7,6 +7,7 @@ import ( "io/fs" "path/filepath" "strings" + "sync" "github.com/liamg/memoryfs" @@ -38,6 +39,8 @@ type Scanner struct { skipRequired bool frameworks []framework.Framework spec string + regoScanner *rego.Scanner + mu sync.Mutex } func (s *Scanner) SetSpec(spec string) { @@ -120,6 +123,10 @@ func (s *Scanner) SetRegoErrorLimit(_ int) {} func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, path string) (scan.Results, error) { + if err := s.initRegoScanner(target); err != nil { + return nil, fmt.Errorf("failed to init rego scanner: %w", err) + } + var results []scan.Result if err := fs.WalkDir(target, path, func(path string, d fs.DirEntry, err error) error { select { @@ -150,6 +157,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, path string) (scan.R } else { results = append(results, scanResults...) } + return fs.SkipDir } return nil @@ -174,14 +182,6 @@ func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) return nil, nil } - regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) - policyFS := target - if s.policyFS != nil { - policyFS = s.policyFS - } - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, policyFS, s.policyDirs, s.policyReaders); err != nil { - return nil, fmt.Errorf("policies load: %w", err) - } for _, file := range chartFiles { file := file s.debug.Log("Processing rendered chart file: %s", file.TemplateFilePath) @@ -191,7 +191,7 @@ func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) return nil, fmt.Errorf("unmarshal yaml: %w", err) } for _, manifest := range manifests { - fileResults, err := regoScanner.ScanInput(ctx, rego.Input{ + fileResults, err := s.regoScanner.ScanInput(ctx, rego.Input{ Path: file.TemplateFilePath, Contents: manifest, FS: target, @@ -219,3 +219,18 @@ func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) } return results, nil } + +func (s *Scanner) initRegoScanner(srcFS fs.FS) error { + s.mu.Lock() + defer s.mu.Unlock() + if s.regoScanner != nil { + return nil + } + regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return err + } + s.regoScanner = regoScanner + return nil +} diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index 67099af2bb36..a46031a8fb98 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -318,3 +318,44 @@ deny[res] { require.NoError(t, err) assert.NotNil(t, code) } + +func TestScanSubchartOnce(t *testing.T) { + check := `# METADATA +# title: "Test rego" +# description: "Test rego" +# scope: package +# schemas: +# - input: schema["kubernetes"] +# custom: +# id: ID001 +# avd_id: AVD-USR-ID001 +# severity: LOW +# input: +# selector: +# - type: kubernetes +# subtypes: +# - kind: pod +package user.kubernetes.ID001 + +import data.lib.kubernetes + +deny[res] { + container := kubernetes.containers[_] + container.securityContext.readOnlyRootFilesystem == false + res := result.new("set 'securityContext.readOnlyRootFilesystem' to true", container) +} +` + + scanner := helm.New( + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithPolicyReader(strings.NewReader(check)), + ) + + results, err := scanner.ScanFS(context.TODO(), os.DirFS("testdata/with-subchart"), ".") + require.NoError(t, err) + require.Len(t, results, 1) + + assert.Len(t, results.GetFailed(), 0) +} diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml new file mode 100644 index 000000000000..3c8c9b71ae45 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: test +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml new file mode 100644 index 000000000000..45cdc636218e --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: nginx +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml new file mode 100644 index 000000000000..70b3a84a8130 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 8080 + securityContext: + readOnlyRootFilesystem: {{ .Values.readOnlyFs }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml new file mode 100644 index 000000000000..ff3cff9db1e3 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml @@ -0,0 +1 @@ +readOnlyFs: false diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml new file mode 100644 index 000000000000..1e51a8fed1da --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml @@ -0,0 +1,2 @@ +nginx: + readOnlyFs: true