From 2385270b383ce4c7b6af6a955a17cdf3f2a44996 Mon Sep 17 00:00:00 2001 From: Mathieu Parent Date: Wed, 14 Jan 2026 20:45:37 +0100 Subject: [PATCH] feat: allow to remove fields from vulnerability reports --- deploy/helm/README.md | 2 +- deploy/helm/values.yaml | 3 +- docs/docs/vulnerability-scanning/trivy.md | 2 +- pkg/plugins/trivy/config.go | 37 +++++++--- pkg/plugins/trivy/config_test.go | 86 +++++++++++++++++++++-- pkg/plugins/trivy/plugin.go | 2 +- pkg/vulnerabilityreport/io.go | 83 +++++++++++++++------- 7 files changed, 172 insertions(+), 43 deletions(-) diff --git a/deploy/helm/README.md b/deploy/helm/README.md index c63c6c416..83a60f660 100644 --- a/deploy/helm/README.md +++ b/deploy/helm/README.md @@ -129,7 +129,7 @@ Keeps security report resources updated | targetNamespaces | string | `""` | targetNamespace defines where you want trivy-operator to operate. By default, it's a blank string to select all namespaces, but you can specify another namespace, or a comma separated list of namespaces. | | targetWorkloads | string | `"pod,replicaset,replicationcontroller,statefulset,daemonset,cronjob,job"` | targetWorkloads is a comma seperated list of Kubernetes workload resources to be included in the vulnerability and config-audit scans if left blank, all workload resources will be scanned | | tolerations | list | `[]` | tolerations set the operator tolerations | -| trivy.additionalVulnerabilityReportFields | string | `""` | additionalVulnerabilityReportFields is a comma separated list of additional fields which can be added to the VulnerabilityReport. Supported parameters: Description, Links, CVSS, Target, Class, PackagePath and PackageType | +| trivy.additionalVulnerabilityReportFields | string | `""` | additionalVulnerabilityReportFields is a comma separated list of additional fields which can be added to the VulnerabilityReport. Supported parameters: Description, Links, CVSS, Target, Class, PackagePath and PackageType. Fields can also be removed with a leading minus sign `-`. Supported parameters: -Resource, -InstalledVersion, -FixedVersion, -PublishedDate, -LastModifiedDate, -Title, -PrimaryLink, -Score and -PackagePURL. | | trivy.clientServerSkipUpdate | bool | `false` | clientServerSkipUpdate is the flag to enable skip databases update for Trivy client. Only applicable in ClientServer mode. | | trivy.command | string | `"image"` | command. One of `image`, `filesystem` or `rootfs` scanning, depending on the target type required for the scan. For 'filesystem' and `rootfs` scanning, ensure that the `trivyOperator.scanJobPodTemplateContainerSecurityContext` is configured to run as the root user (runAsUser = 0). | | trivy.configFile | string | `nil` | configFile can be used to tell Trivy to use specific options available only in the config file (e.g. Mirror registries). | diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index ebe8d20d0..2604b8974 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -398,7 +398,8 @@ trivy: priorityClassName: "" # -- additionalVulnerabilityReportFields is a comma separated list of additional fields which - # can be added to the VulnerabilityReport. Supported parameters: Description, Links, CVSS, Target, Class, PackagePath and PackageType + # can be added to the VulnerabilityReport. Supported parameters: Description, Links, CVSS, Target, Class, PackagePath and PackageType. + # Fields can also be removed with a leading minus sign `-`. Supported parameters: -Resource, -InstalledVersion, -FixedVersion, -PublishedDate, -LastModifiedDate, -Title, -PrimaryLink, -Score and -PackagePURL. additionalVulnerabilityReportFields: "" # -- httpProxy is the HTTP proxy used by Trivy to download the vulnerabilities database from GitHub. diff --git a/docs/docs/vulnerability-scanning/trivy.md b/docs/docs/vulnerability-scanning/trivy.md index 28c8cd546..06ed795bf 100644 --- a/docs/docs/vulnerability-scanning/trivy.md +++ b/docs/docs/vulnerability-scanning/trivy.md @@ -101,7 +101,7 @@ EOF | `trivy.javaDbRepository` | `mirror.gcr.io/aquasec/trivy-java-db` | External OCI Registry to download the vulnerability database for Java | | `trivy.dbRepositoryInsecure` | `false` | The Flag to enable insecure connection for downloading trivy-db via proxy (air-gaped env) | | `trivy.mode` | `Standalone` | Trivy client mode. Either `Standalone` or `ClientServer`. Depending on the active mode other settings might be applicable or required. | -| `additionalVulnerabilityReportFields` | N/A | A comma separated list of additional fields which can be added to the VulnerabilityReport. Possible values: `Description,Links,CVSS,Target,Class,PackagePath,PackageType`. Description will add more data about vulnerability. Links - all the references to a specific vulnerability. CVSS - data about CVSSv2/CVSSv3 scoring and vectors. Target - vulnerable element. Class - OS or library vulnerability | +| `additionalVulnerabilityReportFields` | N/A | A comma separated list of additional fields which can be added to the VulnerabilityReport. Possible values: `Description,Links,CVSS,Target,Class,PackagePath,PackageType`. Description will add more data about vulnerability. Links - all the references to a specific vulnerability. CVSS - data about CVSSv2/CVSSv3 scoring and vectors. Target - vulnerable element. Class - OS or library vulnerability. Fields can also be removed with a leading minus sign `-`. Possible values: `Resource,InstalledVersion,FixedVersion,PublishedDate,LastModifiedDate,Title,PrimaryLink,Score,PackagePURL` | | `trivy.command` | `image` | command. One of `image`, `filesystem` or `rootfs` scanning. Depending on the target type required for the scan. | | `trivy.slow` | `true` | This flag is to use less CPU/memory for scanning though it takes more time than normal scanning. It fits small-footprint | | `trivy.severity` | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | A comma separated list of severity levels reported by Trivy | diff --git a/pkg/plugins/trivy/config.go b/pkg/plugins/trivy/config.go index 6a743cb7d..eb3532bbf 100644 --- a/pkg/plugins/trivy/config.go +++ b/pkg/plugins/trivy/config.go @@ -80,28 +80,49 @@ type Config struct { } func (c Config) GetAdditionalVulnerabilityReportFields() vulnerabilityreport.AdditionalFields { - addFields := vulnerabilityreport.AdditionalFields{} + addFields := vulnerabilityreport.NewDefaultAdditionalFields() fields, ok := c.Data[keyTrivyAdditionalVulnerabilityReportFields] if !ok { return addFields } for _, field := range strings.Split(fields, ",") { + trimmed_field := strings.TrimSpace(field) + value := !strings.HasPrefix(trimmed_field, "-") + field = strings.TrimPrefix(trimmed_field, "-") switch strings.TrimSpace(field) { + case "Resource": + addFields.Resource = value + case "InstalledVersion": + addFields.InstalledVersion = value + case "FixedVersion": + addFields.FixedVersion = value + case "PublishedDate": + addFields.PublishedDate = value + case "LastModifiedDate": + addFields.LastModifiedDate = value + case "Title": + addFields.Title = value + case "PrimaryLink": + addFields.PrimaryLink = value + case "Score": + addFields.Score = value + case "PackagePURL": + addFields.PkgPURL = value case "Description": - addFields.Description = true + addFields.Description = value case "Links": - addFields.Links = true + addFields.Links = value case "CVSS": - addFields.CVSS = true + addFields.CVSS = value case "Target": - addFields.Target = true + addFields.Target = value case "Class": - addFields.Class = true + addFields.Class = value case "PackageType": - addFields.PackageType = true + addFields.PackageType = value case "PackagePath": - addFields.PkgPath = true + addFields.PkgPath = value } } return addFields diff --git a/pkg/plugins/trivy/config_test.go b/pkg/plugins/trivy/config_test.go index 817b629f9..34646f18d 100644 --- a/pkg/plugins/trivy/config_test.go +++ b/pkg/plugins/trivy/config_test.go @@ -88,7 +88,7 @@ func TestConfig_GetAdditionalVulnerabilityReportFields(t *testing.T) { { name: "no additional fields are set", configData: Config{PluginConfig: trivyoperator.PluginConfig{}}, - additionalFields: vulnerabilityreport.AdditionalFields{}, + additionalFields: vulnerabilityreport.NewDefaultAdditionalFields(), }, { name: "all additional fields are set", @@ -97,7 +97,25 @@ func TestConfig_GetAdditionalVulnerabilityReportFields(t *testing.T) { "trivy.additionalVulnerabilityReportFields": "PackageType,PkgPath,Class,Target,Links,Description,CVSS", }, }}, - additionalFields: vulnerabilityreport.AdditionalFields{Description: true, Links: true, CVSS: true, Class: true, PackageType: true, PkgPath: true, Target: true}, + additionalFields: vulnerabilityreport.AdditionalFields{ + // default + Resource: true, + InstalledVersion: true, + FixedVersion: true, + PublishedDate: true, + LastModifiedDate: true, + Title: true, + PrimaryLink: true, + Score: true, + PkgPURL: true, + // added + Description: true, + Links: true, + CVSS: true, + Class: true, + PackageType: true, + Target: true, + }, }, { name: "some additional fields are set", @@ -106,19 +124,77 @@ func TestConfig_GetAdditionalVulnerabilityReportFields(t *testing.T) { "trivy.additionalVulnerabilityReportFields": "PackageType,Target,Links,CVSS", }, }}, - additionalFields: vulnerabilityreport.AdditionalFields{Links: true, CVSS: true, PackageType: true, Target: true}, + additionalFields: vulnerabilityreport.AdditionalFields{ + // default + Resource: true, + InstalledVersion: true, + FixedVersion: true, + PublishedDate: true, + LastModifiedDate: true, + Title: true, + PrimaryLink: true, + Score: true, + PkgPURL: true, + // added + Links: true, + CVSS: true, + PackageType: true, + Target: true, + }, + }, + { + name: "all fields are removed", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.additionalVulnerabilityReportFields": " -Resource,- InstalledVersion, - FixedVersion, - PublishedDate,-LastModifiedDate,-Title,-PrimaryLink,-Score,-PackagePURL", + }, + }}, + additionalFields: vulnerabilityreport.AdditionalFields{}, + }, + { + name: "some fields are added and some are removed", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.additionalVulnerabilityReportFields": "CVSS,PackageType,-PublishedDate,-LastModifiedDate", + }, + }}, + additionalFields: vulnerabilityreport.AdditionalFields{ + // default + Resource: true, + InstalledVersion: true, + FixedVersion: true, + PublishedDate: false, // removed + LastModifiedDate: false, // removed + Title: true, + PrimaryLink: true, + Score: true, + PkgPURL: true, + // added + CVSS: true, + PackageType: true, + }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { addFields := tc.configData.GetAdditionalVulnerabilityReportFields() + assert.Equal(t, tc.additionalFields.Resource, addFields.Resource) + assert.Equal(t, tc.additionalFields.InstalledVersion, addFields.InstalledVersion) + assert.Equal(t, tc.additionalFields.FixedVersion, addFields.FixedVersion) + assert.Equal(t, tc.additionalFields.PublishedDate, addFields.PublishedDate) + assert.Equal(t, tc.additionalFields.LastModifiedDate, addFields.LastModifiedDate) + assert.Equal(t, tc.additionalFields.Title, addFields.Title) + assert.Equal(t, tc.additionalFields.PrimaryLink, addFields.PrimaryLink) + assert.Equal(t, tc.additionalFields.Score, addFields.Score) + assert.Equal(t, tc.additionalFields.PkgPURL, addFields.PkgPURL) assert.Equal(t, tc.additionalFields.Description, addFields.Description) + assert.Equal(t, tc.additionalFields.Links, addFields.Links) assert.Equal(t, tc.additionalFields.CVSS, addFields.CVSS) assert.Equal(t, tc.additionalFields.Target, addFields.Target) - assert.Equal(t, tc.additionalFields.PackageType, addFields.PackageType) assert.Equal(t, tc.additionalFields.Class, addFields.Class) - assert.Equal(t, tc.additionalFields.Links, addFields.Links) + assert.Equal(t, tc.additionalFields.PackageType, addFields.PackageType) + assert.Equal(t, tc.additionalFields.PkgPath, addFields.PkgPath) }) } } diff --git a/pkg/plugins/trivy/plugin.go b/pkg/plugins/trivy/plugin.go index a497580b1..9cac86527 100644 --- a/pkg/plugins/trivy/plugin.go +++ b/pkg/plugins/trivy/plugin.go @@ -184,8 +184,8 @@ func (p *plugin) ParseReportData(ctx trivyoperator.PluginContext, imageRef strin } vulnerabilities := make([]v1alpha1.Vulnerability, 0) secrets := make([]v1alpha1.ExposedSecret, 0) + addFields := config.GetAdditionalVulnerabilityReportFields() for _, report := range reports.Results { - addFields := config.GetAdditionalVulnerabilityReportFields() vulnerabilities = append(vulnerabilities, vulnerabilityreport.GetVulnerabilitiesFromScanResult(report, addFields)...) secrets = append(secrets, getExposedSecretsFromScanResult(report)...) } diff --git a/pkg/vulnerabilityreport/io.go b/pkg/vulnerabilityreport/io.go index 5ae4aa432..c4474bc65 100644 --- a/pkg/vulnerabilityreport/io.go +++ b/pkg/vulnerabilityreport/io.go @@ -129,41 +129,72 @@ func (r *readWriter) FindByOwner(ctx context.Context, owner kube.ObjectRef) ([]v } type AdditionalFields struct { - Description bool - Links bool - CVSS bool - Target bool - Class bool - PackageType bool - PkgPath bool + Resource bool + InstalledVersion bool + FixedVersion bool + PublishedDate bool + LastModifiedDate bool + Title bool + PrimaryLink bool + Score bool + PkgPURL bool + Description bool + Links bool + CVSS bool + Target bool + Class bool + PackageType bool + PkgPath bool +} + +func NewDefaultAdditionalFields() AdditionalFields { + return AdditionalFields{ + Resource: true, + InstalledVersion: true, + FixedVersion: true, + PublishedDate: true, + LastModifiedDate: true, + Title: true, + PrimaryLink: true, + Score: true, + PkgPURL: true, + } } func GetVulnerabilitiesFromScanResult(report ty.Result, addFields AdditionalFields) []v1alpha1.Vulnerability { vulnerabilities := make([]v1alpha1.Vulnerability, 0) for _, sr := range report.Vulnerabilities { - var pd, lmd string - if sr.PublishedDate != nil { - pd = sr.PublishedDate.Format(time.RFC3339) - } - if sr.LastModifiedDate != nil { - lmd = sr.LastModifiedDate.Format(time.RFC3339) - } vulnerability := v1alpha1.Vulnerability{ - VulnerabilityID: sr.VulnerabilityID, - Resource: sr.PkgName, - InstalledVersion: sr.InstalledVersion, - FixedVersion: sr.FixedVersion, - PublishedDate: pd, - LastModifiedDate: lmd, - Severity: v1alpha1.Severity(sr.Severity), - Title: sr.Title, - PrimaryLink: sr.PrimaryURL, - Links: []string{}, - Score: GetScoreFromCVSS(GetCvssV3(sr.CVSS)), + VulnerabilityID: sr.VulnerabilityID, + Severity: v1alpha1.Severity(sr.Severity), } - if sr.PkgIdentifier.PURL != nil { + if addFields.Resource { + vulnerability.Resource = sr.PkgName + } + if addFields.InstalledVersion { + vulnerability.InstalledVersion = sr.InstalledVersion + } + if addFields.FixedVersion { + vulnerability.FixedVersion = sr.FixedVersion + } + if addFields.PublishedDate && sr.PublishedDate != nil { + vulnerability.PublishedDate = sr.PublishedDate.Format(time.RFC3339) + } + if addFields.LastModifiedDate && sr.LastModifiedDate != nil { + vulnerability.LastModifiedDate = sr.LastModifiedDate.Format(time.RFC3339) + } + if addFields.Title { + vulnerability.Title = sr.Title + } + if addFields.PrimaryLink { + vulnerability.PrimaryLink = sr.PrimaryURL + } + if addFields.Score { + vulnerability.Score = GetScoreFromCVSS(GetCvssV3(sr.CVSS)) + } + if addFields.PkgPURL && sr.PkgIdentifier.PURL != nil { vulnerability.PkgPURL = sr.PkgIdentifier.PURL.String() }