From c0c7a6bf1b92c868ed44172b3cd15c51667b8a6e Mon Sep 17 00:00:00 2001 From: afdesk Date: Tue, 30 Sep 2025 07:44:51 +0600 Subject: [PATCH] fix(k8s): disable parallel traversal with fs cache for k8s images (#9534) --- integration/k8s_test.go | 71 +++-- .../fixtures/k8s/summary-ids.json.golden | 290 ++++++++++++++++++ magefiles/magefile.go | 2 +- pkg/k8s/scanner/scanner.go | 7 +- 4 files changed, 349 insertions(+), 21 deletions(-) create mode 100644 integration/testdata/fixtures/k8s/summary-ids.json.golden diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 109c4b7b2a..1d551ad21d 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "os" "path/filepath" + "sort" "testing" cdx "github.com/CycloneDX/cyclonedx-go" @@ -28,19 +29,14 @@ func TestK8s(t *testing.T) { outputFile := filepath.Join(t.TempDir(), "output.json") osArgs := []string{ - "--cache-dir", - cacheDir, + "--cache-dir", cacheDir, "k8s", "kind-kind-test", - "--report", - "summary", + "--report", "summary", "-q", - "--timeout", - "5m0s", - "--format", - "json", - "--output", - outputFile, + "--timeout", "5m0s", + "--format", "json", + "--output", outputFile, } // Run Trivy @@ -60,15 +56,47 @@ func TestK8s(t *testing.T) { return resource.Results }) - // Has vulnerabilities - assert.True(t, lo.SomeBy(results, func(r types.Result) bool { - return len(r.Vulnerabilities) > 0 - })) + // Collect IDs (CVEs for vulns, IDs for failed misconfigs), allowing duplicates. + ids := k8sFindingIDs{} + for _, r := range results { + for _, v := range r.Vulnerabilities { + if v.VulnerabilityID != "" { + ids.Vulnerabilities = append(ids.Vulnerabilities, v.VulnerabilityID) + } + } + for _, m := range r.Misconfigurations { + if m.Status == types.MisconfStatusFailure && m.ID != "" { + ids.Misconfigurations = append(ids.Misconfigurations, m.ID) + } + } + } - // Has misconfigurations - assert.True(t, lo.SomeBy(results, func(r types.Result) bool { - return len(r.Misconfigurations) > 0 - })) + // Sort for deterministic golden files + sort.Strings(ids.Vulnerabilities) + sort.Strings(ids.Misconfigurations) + + fixture := filepath.Join("testdata", "fixtures", "k8s", "summary-ids.json.golden") + if *update { + // Update fixture with current IDs (duplicates kept, sorted) + // Note: mage test:k8s may create additional k8s artifacts. + f, err := os.Create(fixture) + require.NoError(t, err) + defer f.Close() + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + require.NoError(t, enc.Encode(ids)) + t.Logf("updated fixture: %s", fixture) + return + } + + // Read expected IDs from fixture and compare + ef, err := os.Open(fixture) + require.NoError(t, err) + defer ef.Close() + + var want k8sFindingIDs + require.NoError(t, json.NewDecoder(ef).Decode(&want)) + assert.Equal(t, want, ids) }) t.Run("kbom cycloneDx", func(t *testing.T) { // Set up the output file @@ -106,7 +134,6 @@ func TestK8s(t *testing.T) { return len(*r.Dependencies) > 0 })) }) - t.Run("limited user test", func(t *testing.T) { // Set up the output file outputFile := filepath.Join(t.TempDir(), "output.json") @@ -158,3 +185,9 @@ func TestK8s(t *testing.T) { }) } + +// k8sFindingIDs is the structure saved into the golden file. +type k8sFindingIDs struct { + Vulnerabilities []string `json:"vulnerabilities"` + Misconfigurations []string `json:"misconfigurations"` +} diff --git a/integration/testdata/fixtures/k8s/summary-ids.json.golden b/integration/testdata/fixtures/k8s/summary-ids.json.golden new file mode 100644 index 0000000000..9c1f870b7a --- /dev/null +++ b/integration/testdata/fixtures/k8s/summary-ids.json.golden @@ -0,0 +1,290 @@ +{ + "vulnerabilities": [ + "CVE-2019-1551", + "CVE-2019-1551", + "CVE-2019-1563", + "CVE-2019-1563", + "CVE-2019-18276", + "CVE-2019-18276", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094", + "CVE-2019-5094" + ], + "misconfigurations": [ + "AVD-KSV-01010", + "KCV0001", + "KCV0006", + "KCV0010", + "KCV0018", + "KCV0019", + "KCV0020", + "KCV0021", + "KCV0022", + "KCV0030", + "KCV0033", + "KCV0038", + "KCV0059", + "KCV0069", + "KCV0075", + "KCV0077", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV001", + "KSV0012", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV003", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV004", + "KSV009", + "KSV009", + "KSV009", + "KSV009", + "KSV009", + "KSV009", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV011", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV012", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV0125", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV014", + "KSV015", + "KSV015", + "KSV015", + "KSV015", + "KSV016", + "KSV016", + "KSV016", + "KSV016", + "KSV016", + "KSV016", + "KSV016", + "KSV017", + "KSV018", + "KSV018", + "KSV018", + "KSV018", + "KSV018", + "KSV018", + "KSV018", + "KSV018", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV020", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV021", + "KSV022", + "KSV022", + "KSV023", + "KSV023", + "KSV023", + "KSV023", + "KSV023", + "KSV023", + "KSV030", + "KSV030", + "KSV030", + "KSV030", + "KSV030", + "KSV030", + "KSV036", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV041", + "KSV044", + "KSV045", + "KSV046", + "KSV046", + "KSV046", + "KSV046", + "KSV046", + "KSV046", + "KSV046", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV048", + "KSV049", + "KSV049", + "KSV049", + "KSV049", + "KSV049", + "KSV049", + "KSV049", + "KSV049", + "KSV050", + "KSV050", + "KSV053", + "KSV053", + "KSV053", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV056", + "KSV104", + "KSV104", + "KSV104", + "KSV104", + "KSV104", + "KSV104", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV106", + "KSV110", + "KSV111", + "KSV112", + "KSV112", + "KSV113", + "KSV113", + "KSV117", + "KSV117", + "KSV117", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV118", + "KSV119", + "KSV122", + "no-user-pods-in-system-namespace", + "no-user-pods-in-system-namespace", + "no-user-pods-in-system-namespace", + "no-user-pods-in-system-namespace" + ] +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 9fed6ed482..15f0a481c8 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -227,7 +227,7 @@ func (t Test) Integration() error { // K8s runs k8s integration tests func (t Test) K8s() error { mg.Deps(Tool{}.Install) // Install kind - err := sh.RunWithV(ENV, "kind", "create", "cluster", "--name", "kind-test") + err := sh.RunWithV(ENV, "kind", "create", "cluster", "--name", "kind-test", "--image", "kindest/node:v1.27.1@sha256:b7d12ed662b873bd8510879c1846e87c7e676a79fefc93e17b2a52989d3ff42b") if err != nil { return err } diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index b1b0fa0cd9..cceec470c6 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -120,8 +120,13 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) resources = append(resources, result...) return nil } + workers := s.opts.Parallel + if s.opts.CacheBackend == string(cache.TypeFS) { + // To avoid lock contention in bbolt, we limit the number of workers to 1 when using FS cache. + workers = 1 + } - p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, resourceArtifacts, onItem, onResult) + p := parallel.NewPipeline(workers, !s.opts.Quiet, resourceArtifacts, onItem, onResult) if err := p.Do(ctx); err != nil { return report.Report{}, err }