diff --git a/pkg/metrics/collector.go b/pkg/metrics/collector.go index 29b996ad5..9a5715625 100644 --- a/pkg/metrics/collector.go +++ b/pkg/metrics/collector.go @@ -3,6 +3,7 @@ package metrics import ( "context" "strconv" + "strings" "github.com/aquasecurity/trivy-operator/pkg/kube" "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" @@ -543,6 +544,36 @@ func getDynamicConfigLabels(config trivyoperator.ConfigData) []string { return labels } +// constructVulnKey constructs a unique key for a vulnerability based on its ID, target, and package path. +// The key is used to ensure that each vulnerability is uniquely identified even if it appears in multiple +// binaries or paths. +// +// Parameters: +// - vulnID: The unique identifier for the vulnerability (e.g., CVE ID). +// - target: The target location of the vulnerability (e.g., binary file path). +// - pkgPath: The package path of the vulnerability (e.g., library or module path). +// +// Returns: +// - A string representing the unique key for the vulnerability. +// +// The key is constructed by concatenating the non-empty components (vulnID, target, pkgPath) with a "|" separator. +// This approach ensures that even if target and pkgPath have identical names or are empty, the key remains unique and valid. +// +// Example usage: +// key := constructVulnKey("CVE-2024-1234", "usr/local/bin", "package/path") +// This will return: "CVE-2024-1234-P:usr/local/bin-T:package/path" +func constructVulnKey(vulnID, target, pkgPath string) string { + var parts []string + parts = append(parts, vulnID) + if target != "" { + parts = append(parts, "T:"+target) + } + if pkgPath != "" { + parts = append(parts, "P:"+pkgPath) + } + return strings.Join(parts, "-") +} + func (c *ResourcesMetricsCollector) SetupWithManager(mgr ctrl.Manager) error { return mgr.Add(c) } @@ -641,7 +672,7 @@ func (c ResourcesMetricsCollector) collectVulnerabilityIdReports(ctx context.Con } var vulnList = make(map[string]bool) for _, vuln := range r.Report.Vulnerabilities { - vulnKey := vuln.VulnerabilityID + "|" + vuln.Target + "|" + vuln.PkgPath + vulnKey := constructVulnKey(vuln.VulnerabilityID, vuln.Target, vuln.PkgPath) if vulnList[vulnKey] { continue } diff --git a/pkg/metrics/collector_test.go b/pkg/metrics/collector_test.go index 171eba766..054b48f9f 100644 --- a/pkg/metrics/collector_test.go +++ b/pkg/metrics/collector_test.go @@ -42,6 +42,20 @@ var _ = Describe("ResourcesMetricsCollector", func() { }) } + Context("constructVulnKey", func() { + DescribeTable("should construct the correct key", + func(vulnID, target, pkgPath, expectedKey string) { + key := constructVulnKey(vulnID, target, pkgPath) + Expect(key).To(Equal(expectedKey)) + }, + Entry("when all parameters are provided", "CVE-2024-1234", "usr/local/bin", "package/path", "CVE-2024-1234-T:usr/local/bin-P:package/path"), + Entry("when target is empty", "CVE-2024-1234", "", "package/path", "CVE-2024-1234-P:package/path"), + Entry("when pkgPath is empty", "CVE-2024-1234", "usr/local/bin", "", "CVE-2024-1234-T:usr/local/bin"), + Entry("when both target and pkgPath are empty", "CVE-2024-1234", "", "", "CVE-2024-1234"), + Entry("when target and pkgPath have identical names", "CVE-2024-1234", "identical", "identical", "CVE-2024-1234-T:identical-P:identical"), + ) + }) + Context("VulnerabilityReport", func() { BeforeEach(func() { vr1 := &v1alpha1.VulnerabilityReport{}