Files
trivy/integration/k8s_test.go
2025-11-24 16:39:22 +00:00

209 lines
5.1 KiB
Go

//go:build k8s_integration
package integration
import (
"encoding/json"
"os"
"path/filepath"
"sort"
"testing"
cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/aquasecurity/trivy/pkg/k8s/report"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestK8s tests Kubernetes cluster scanning.
//
// NOTE: This test CAN update golden files with the -update flag. The K8s-specific golden files
// are unique to this test and not shared with other tests.
// Requires k8s (kind) cluster installed. Run with "mage test:k8s".
func TestK8s(t *testing.T) {
// Set up testing DB
cacheDir := initDB(t)
t.Run("misconfig and vulnerability scan", func(t *testing.T) {
// Set up the output file
outputFile := filepath.Join(t.TempDir(), "output.json")
// it uses a fixed version of trivy-checks bundle - v1.11.2
// its hash is sha256:f3ea8227f838a985f0c884909e9d226362f5fc5ab6021310a179fbb24c5b57fd
osArgs := []string{
"--cache-dir",
cacheDir,
"k8s",
"kind-kind-test",
"--report",
"summary",
"--checks-bundle-repository",
"mirror.gcr.io/aquasec/trivy-checks:1.11.2@sha256:f3ea8227f838a985f0c884909e9d226362f5fc5ab6021310a179fbb24c5b57fd",
"--include-namespaces", "default",
"--debug",
"--timeout",
"5m0s",
"--format",
"json",
"--output",
outputFile,
}
// Run Trivy
err := execute(osArgs)
require.NoError(t, err)
var got report.ConsolidatedReport
f, err := os.Open(outputFile)
require.NoError(t, err)
defer f.Close()
err = json.NewDecoder(f).Decode(&got)
require.NoError(t, err)
// Flatten findings
results := lo.FlatMap(got.Findings, func(resource report.Resource, _ int) []types.Result {
return resource.Results
})
// 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)
}
}
}
// 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
outputFile := filepath.Join(t.TempDir(), "output.json")
osArgs := []string{
"k8s",
"kind-kind-test",
"--format",
"cyclonedx",
"-q",
"--output",
outputFile,
}
// Run Trivy
err := execute(osArgs)
require.NoError(t, err)
var got *cdx.BOM
f, err := os.Open(outputFile)
require.NoError(t, err)
defer f.Close()
err = json.NewDecoder(f).Decode(&got)
require.NoError(t, err)
assert.Equal(t, got.Metadata.Component.Name, "k8s.io/kubernetes")
assert.Equal(t, got.Metadata.Component.Type, cdx.ComponentType("platform"))
// Has components
assert.True(t, len(*got.Components) > 0)
// Has dependecies
assert.True(t, lo.SomeBy(*got.Dependencies, func(r cdx.Dependency) bool {
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")
osArgs := []string{
"--cache-dir",
cacheDir,
"k8s",
"limitedcontext",
"--kubeconfig",
"limitedconfig",
"--report",
"summary",
"-q",
"--timeout",
"5m0s",
"--include-namespaces",
"limitedns",
"--format",
"json",
"--output",
outputFile,
}
// Run Trivy
err := execute(osArgs)
require.NoError(t, err)
var got report.ConsolidatedReport
f, err := os.Open(outputFile)
require.NoError(t, err)
defer f.Close()
err = json.NewDecoder(f).Decode(&got)
require.NoError(t, err)
// Flatten findings
results := lo.FlatMap(got.Findings, func(resource report.Resource, _ int) []types.Result {
return resource.Results
})
// Has vulnerabilities
assert.True(t, lo.SomeBy(results, func(r types.Result) bool {
return len(r.Vulnerabilities) > 0
}))
// Has misconfigurations
assert.True(t, lo.SomeBy(results, func(r types.Result) bool {
return len(r.Misconfigurations) > 0
}))
})
}
// k8sFindingIDs is the structure saved into the golden file.
type k8sFindingIDs struct {
Vulnerabilities []string `json:"vulnerabilities"`
Misconfigurations []string `json:"misconfigurations"`
}