From 289f303a464d6022072133d2896063ae6186af83 Mon Sep 17 00:00:00 2001 From: chenk Date: Mon, 20 May 2024 10:50:37 +0300 Subject: [PATCH] feat: filter container by regex (#2080) * feat: filter container by regex Signed-off-by: chenk * feat: filter container by regex Signed-off-by: chenk * feat: filter container by regex Signed-off-by: chenk * feat: filter container by regex Signed-off-by: chenk * feat: filter container by regex Signed-off-by: chenk --------- Signed-off-by: chenk --- deploy/helm/README.md | 1 + .../helm/templates/configmaps/operator.yaml | 3 ++ deploy/helm/values.yaml | 4 ++ pkg/plugins/trivy/filesystem.go | 3 ++ pkg/plugins/trivy/image.go | 3 ++ pkg/plugins/trivy/plugin.go | 14 +++++++ pkg/plugins/trivy/plugin_test.go | 40 +++++++++++++++++++ pkg/trivyoperator/config.go | 15 +++++++ pkg/trivyoperator/config_test.go | 36 +++++++++++++++++ .../controller/workload.go | 5 +++ 10 files changed, 124 insertions(+) diff --git a/deploy/helm/README.md b/deploy/helm/README.md index daf11b967..794783fcf 100644 --- a/deploy/helm/README.md +++ b/deploy/helm/README.md @@ -182,6 +182,7 @@ Keeps security report resources updated | trivy.vulnType | string | `nil` | vulnType can be used to tell Trivy to filter vulnerabilities by a pkg-type (library, os) | | trivyOperator.additionalReportLabels | string | `""` | additionalReportLabels comma-separated representation of the labels which the user wants the scanner pods to be labeled with. Example: `foo=bar,env=stage` will labeled the reports with the labels `foo: bar` and `env: stage` | | trivyOperator.configAuditReportsPlugin | string | `"Trivy"` | configAuditReportsPlugin the name of the plugin that generates config audit reports. | +| trivyOperator.excludeImages | string | `""` | excludeImages is comma separated glob patterns for excluding images from scanning. Example: pattern: `k8s.gcr.io/*/*` will exclude image: `k8s.gcr.io/coredns/coredns:v1.8.0`. | | trivyOperator.metricsResourceLabelsPrefix | string | `"k8s_label_"` | metricsResourceLabelsPrefix Prefix that will be prepended to the labels names indicated in `reportResourceLabels` when including them in the Prometheus metrics | | trivyOperator.policiesConfig | string | `""` | policiesConfig Custom Rego Policies to be used by the config audit scanner See https://github.com/aquasecurity/trivy-operator/blob/main/docs/tutorials/writing-custom-configuration-audit-policies.md for more details. | | trivyOperator.reportRecordFailedChecksOnly | bool | `true` | reportRecordFailedChecksOnly flag is to record only failed checks on misconfiguration reports (config-audit and rbac assessment) | diff --git a/deploy/helm/templates/configmaps/operator.yaml b/deploy/helm/templates/configmaps/operator.yaml index c084ac807..47acf9140 100644 --- a/deploy/helm/templates/configmaps/operator.yaml +++ b/deploy/helm/templates/configmaps/operator.yaml @@ -36,6 +36,9 @@ data: {{- with .Values.trivyOperator.skipInitContainers }} scanJob.skipInitContainers: {{ . | quote }} {{- end }} + {{- with .Values.trivyOperator.excludeImages }} + scanJob.excludeImages: {{ . | quote }} + {{- end }} {{- with .Values.nodeCollector.excludeNodes }} nodeCollector.excludeNodes: {{ . | quote }} {{- end }} diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index d10fa4af5..a9ab30db3 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -317,6 +317,10 @@ trivyOperator: # See https://github.com/aquasecurity/trivy-operator/blob/main/docs/tutorials/writing-custom-configuration-audit-policies.md for more details. policiesConfig: "" + # -- excludeImages is comma separated glob patterns for excluding images from scanning. + # Example: pattern: `k8s.gcr.io/*/*` will exclude image: `k8s.gcr.io/coredns/coredns:v1.8.0`. + excludeImages: "" + trivy: # -- createConfig indicates whether to create config objects createConfig: true diff --git a/pkg/plugins/trivy/filesystem.go b/pkg/plugins/trivy/filesystem.go index 5f2ad4967..34d9fd1d2 100644 --- a/pkg/plugins/trivy/filesystem.go +++ b/pkg/plugins/trivy/filesystem.go @@ -380,6 +380,9 @@ func GetPodSpecForClientServerFSMode(ctx trivyoperator.PluginContext, config Con } for _, c := range getContainers(spec) { + if ExcludeImage(ctx.GetTrivyOperatorConfig().ExcludeImages(), c.Image) { + continue + } env := []corev1.EnvVar{ constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), diff --git a/pkg/plugins/trivy/image.go b/pkg/plugins/trivy/image.go index 7672ea91c..f3cd9e65b 100644 --- a/pkg/plugins/trivy/image.go +++ b/pkg/plugins/trivy/image.go @@ -157,6 +157,9 @@ func GetPodSpecForStandaloneMode(ctx trivyoperator.PluginContext, } for _, c := range containersSpec { + if ExcludeImage(ctx.GetTrivyOperatorConfig().ExcludeImages(), c.Image) { + continue + } env := []corev1.EnvVar{ constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", trivyConfigName, keyTrivyIgnoreUnfixed), diff --git a/pkg/plugins/trivy/plugin.go b/pkg/plugins/trivy/plugin.go index f4e9e69b1..90c446e0e 100644 --- a/pkg/plugins/trivy/plugin.go +++ b/pkg/plugins/trivy/plugin.go @@ -3,6 +3,7 @@ package trivy import ( "encoding/json" "io" + "path/filepath" "github.com/aquasecurity/trivy-operator/pkg/exposedsecretreport" "github.com/aquasecurity/trivy-operator/pkg/sbomreport" @@ -247,3 +248,16 @@ func (p *plugin) parseOSRef(reports ty.Report) v1alpha1.OS { return os } + +// ExcludeImage checks if the image should be excluded from scanning based on the excludeImagePattern (glob pattern) +func ExcludeImage(excludeImagePattern []string, imageName string) bool { + if len(excludeImagePattern) == 0 { + return false + } + for _, pattern := range excludeImagePattern { + if matched, err := filepath.Match(pattern, imageName); err == nil && matched { + return true + } + } + return false +} diff --git a/pkg/plugins/trivy/plugin_test.go b/pkg/plugins/trivy/plugin_test.go index 6db543a18..8258fbeaf 100644 --- a/pkg/plugins/trivy/plugin_test.go +++ b/pkg/plugins/trivy/plugin_test.go @@ -7458,3 +7458,43 @@ func TestGetFilesystemScanCacheDir(t *testing.T) { }) } } + +func TestExcludeImages(t *testing.T) { + testCases := []struct { + name string + excludePattern []string + imageName string + want bool + }{ + { + name: "exclude images single pattern match", + excludePattern: []string{"docker.io/*/*"}, + imageName: "docker.io/library/alpine:3.10.2", + want: true, + }, + { + name: "exclude images multi pattern match", + excludePattern: []string{"docker.io/*/*", "k8s.gcr.io/*/*"}, + imageName: "k8s.gcr.io/coredns/coredns:v1.8.0", + want: true, + }, + { + name: "exclude images multi pattern no match", + excludePattern: []string{"docker.io/*", "ecr.io/*/*"}, + imageName: "docker.io/library/alpine:3.10.2", + want: false, + }, + { + name: "exclude images no pattern", + excludePattern: []string{}, + imageName: "docker.io/library/alpine:3.10.2", + want: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := trivy.ExcludeImage(tc.excludePattern, tc.imageName) + assert.Equal(t, got, tc.want) + }) + } +} diff --git a/pkg/trivyoperator/config.go b/pkg/trivyoperator/config.go index 76dcd2fd0..4429d22d6 100644 --- a/pkg/trivyoperator/config.go +++ b/pkg/trivyoperator/config.go @@ -77,6 +77,7 @@ const ( KeyScanJobContainerSecurityContext = "scanJob.podTemplateContainerSecurityContext" keyScanJobPodSecurityContext = "scanJob.podTemplatePodSecurityContext" keyScanJobPodTemplateLabels = "scanJob.podTemplateLabels" + keyScanJobExcludeImags = "scanJob.excludeImages" KeyScanJobPodPriorityClassName = "scanJob.podPriorityClassName" keyComplianceFailEntriesLimit = "compliance.failEntriesLimit" keySkipResourceByLabels = "skipResourceByLabels" @@ -197,6 +198,20 @@ func (c ConfigData) GetScanJobTolerations() ([]corev1.Toleration, error) { return scanJobTolerations, err } +func (c ConfigData) ExcludeImages() []string { + patterns := make([]string, 0) + if excludeImagesPattern, ok := c[keyScanJobExcludeImags]; ok { + for _, s := range strings.Split(excludeImagesPattern, ",") { + if len(strings.TrimSpace(s)) == 0 { + continue + } + patterns = append(patterns, strings.TrimSpace(s)) + } + return patterns + } + return []string{} +} + func (c ConfigData) GetNodeCollectorTolerations() ([]corev1.Toleration, error) { var nodeCollectorTolerations []corev1.Toleration if c[keyNodeCollectorTolerations] == "" { diff --git a/pkg/trivyoperator/config_test.go b/pkg/trivyoperator/config_test.go index 1f0564cf3..315cbfdf7 100644 --- a/pkg/trivyoperator/config_test.go +++ b/pkg/trivyoperator/config_test.go @@ -1108,3 +1108,39 @@ func TestConfigData_ExposedSecretsScannerEnabled(t *testing.T) { }) } } + +func TestConfigData_GetExcludeImages(t *testing.T) { + testCases := []struct { + name string + key string + value string + expected []string + }{ + { + name: "multi pattern", + key: "scanJob.excludeImages", + value: "docker.io/*, ecr.io/*/*", + expected: []string{"docker.io/*", "ecr.io/*/*"}, + }, + { + name: "single pattern", + key: "scanJob.excludeImages", + value: "docker.io/*", + expected: []string{"docker.io/*"}, + }, + { + name: "no pattern", + key: "scanJob.excludeImages", + value: "", + expected: []string{}, + }, + } + for _, tc := range testCases { + configData := trivyoperator.ConfigData{} + t.Run(tc.name, func(t *testing.T) { + configData.Set(tc.key, tc.value) + got := configData.ExcludeImages() + assert.Equal(t, tc.expected, got) + }) + } +} diff --git a/pkg/vulnerabilityreport/controller/workload.go b/pkg/vulnerabilityreport/controller/workload.go index 1597add5a..919a23368 100644 --- a/pkg/vulnerabilityreport/controller/workload.go +++ b/pkg/vulnerabilityreport/controller/workload.go @@ -349,6 +349,11 @@ func (r *WorkloadController) SubmitScanJob(ctx context.Context, owner client.Obj } return fmt.Errorf("constructing scan job: %w", err) } + // If there are no containers in the pod spec, we don't need to create a scan job. + if len(scanJob.Spec.Template.Spec.Containers) == 0 { + log.V(1).Info("ignoring vulnerability scan", "no containers in the pod spec for resource", owner.GetName()) + return nil + } for _, secret := range secrets { err = r.Client.Create(ctx, secret)