From 30cce58da794c0f2aabc87bead374c298f04d325 Mon Sep 17 00:00:00 2001 From: Matheus Moraes Date: Wed, 3 Apr 2024 14:22:21 -0300 Subject: [PATCH] Add persistence to trivy plugin for caching databases (#266) * enable trivy db persistence * set securityContext.privileged to false in trivy plugin * apply a Job for downloading vulnerability database * add flag to download java db on job * fix trivy job command * bump chart version to 0.8.4-rc4 --- charts/zora/Chart.yaml | 4 +- charts/zora/README.md | 9 ++- .../zora/templates/operator/deployment.yaml | 1 + charts/zora/templates/plugins/trivy-job.yaml | 63 +++++++++++++++++++ charts/zora/templates/plugins/trivy-pvc.yaml | 29 +++++++++ charts/zora/templates/plugins/trivy.yaml | 1 + charts/zora/values.yaml | 12 ++++ cmd/main.go | 3 + .../controller/zora/clusterscan_controller.go | 2 + pkg/plugins/cronjob.go | 16 +++++ 10 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 charts/zora/templates/plugins/trivy-job.yaml create mode 100644 charts/zora/templates/plugins/trivy-pvc.yaml diff --git a/charts/zora/Chart.yaml b/charts/zora/Chart.yaml index 885958ef..c0de9a90 100644 --- a/charts/zora/Chart.yaml +++ b/charts/zora/Chart.yaml @@ -17,7 +17,7 @@ name: zora description: A multi-plugin solution that reports misconfigurations and vulnerabilities by scanning your cluster at scheduled times. icon: https://zora-docs.undistro.io/v0.7/assets/logo.svg type: application -version: 0.8.4-rc3 -appVersion: "v0.8.4-rc3" +version: 0.8.4-rc4 +appVersion: "v0.8.4-rc4" sources: - https://github.com/undistro/zora diff --git a/charts/zora/README.md b/charts/zora/README.md index 0df13eab..7ae60c76 100644 --- a/charts/zora/README.md +++ b/charts/zora/README.md @@ -1,6 +1,6 @@ # Zora Helm Chart -![Version: 0.8.4-rc3](https://img.shields.io/badge/Version-0.8.4--rc3-informational?style=flat-square&color=3CA9DD) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square&color=3CA9DD) ![AppVersion: v0.8.4-rc3](https://img.shields.io/badge/AppVersion-v0.8.4--rc3-informational?style=flat-square&color=3CA9DD) +![Version: 0.8.4-rc4](https://img.shields.io/badge/Version-0.8.4--rc4-informational?style=flat-square&color=3CA9DD) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square&color=3CA9DD) ![AppVersion: v0.8.4-rc4](https://img.shields.io/badge/AppVersion-v0.8.4--rc4-informational?style=flat-square&color=3CA9DD) A multi-plugin solution that reports misconfigurations and vulnerabilities by scanning your cluster at scheduled times. @@ -13,7 +13,7 @@ helm repo add undistro https://charts.undistro.io --force-update helm repo update undistro helm upgrade --install zora undistro/zora \ -n zora-system \ - --version 0.8.4-rc3 \ + --version 0.8.4-rc4 \ --create-namespace \ --wait \ --set clusterName="$(kubectl config current-context)" @@ -120,6 +120,11 @@ The following table lists the configurable parameters of the Zora chart and thei | scan.plugins.trivy.envFrom | list | `[]` | List of sources to populate environment variables in trivy container. | | scan.plugins.trivy.timeout | string | `"10m"` | Trivy timeout | | scan.plugins.trivy.insecure | bool | `false` | Allow insecure server connections for Trivy | +| scan.plugins.trivy.persistence.enabled | bool | `true` | Specifies whether Trivy vulnerabilities database should be persisted between the scans, using PersistentVolumeClaim | +| scan.plugins.trivy.persistence.accessMode | string | `"ReadWriteOnce"` | Persistence access mode | +| scan.plugins.trivy.persistence.storageClass | string | `""` | Persistence storage class. Let it empty for default storage class | +| scan.plugins.trivy.persistence.storageRequest | string | `"1Gi"` | Persistence storage size | +| scan.plugins.trivy.persistence.downloadJavaDB | bool | `false` | Specifies whether Java vulnerability database should be downloaded on helm install/upgrade | | scan.plugins.popeye.skipInternalResources | bool | `false` | Specifies whether the following resources should be skipped by `popeye` scans. 1. resources from `kube-system`, `kube-public` and `kube-node-lease` namespaces; 2. kubernetes system reserved RBAC (prefixed with `system:`); 3. `kube-root-ca.crt` configmaps; 4. `default` namespace; 5. `default` serviceaccounts; 6. Helm secrets (prefixed with `sh.helm.release`); 7. Zora components. See `popeye` configuration file that is used for this case: https://github.com/undistro/zora/blob/main/charts/zora/templates/plugins/popeye-config.yaml | | scan.plugins.popeye.resources | object | `{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"250m","memory":"256Mi"}}` | [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers) to add to `popeye` container | | scan.plugins.popeye.podAnnotations | object | `{}` | Annotations added to the popeye pods | diff --git a/charts/zora/templates/operator/deployment.yaml b/charts/zora/templates/operator/deployment.yaml index 0e629e8c..d6725157 100644 --- a/charts/zora/templates/operator/deployment.yaml +++ b/charts/zora/templates/operator/deployment.yaml @@ -81,6 +81,7 @@ spec: - --worker-image={{ printf "%s:%s" .Values.scan.worker.image.repository (.Values.scan.worker.image.tag | default .Chart.AppVersion) }} - --cronjob-clusterrolebinding-name=zora-plugins-rolebinding - --cronjob-serviceaccount-name=zora-plugins + - --trivy-db-pvc={{- if .Values.scan.plugins.trivy.persistence.enabled }}trivy-db{{- else }}""{{- end }} {{- if .Values.scan.plugins.annotations}} - --cronjob-serviceaccount-annotations={{ $first := true }}{{- range $key, $value := .Values.scan.plugins.annotations }}{{if not $first}},{{else}}{{$first = false}}{{end}}{{ $key }}={{$value}}{{- end }} {{- end }} diff --git a/charts/zora/templates/plugins/trivy-job.yaml b/charts/zora/templates/plugins/trivy-job.yaml new file mode 100644 index 00000000..a2f6824f --- /dev/null +++ b/charts/zora/templates/plugins/trivy-job.yaml @@ -0,0 +1,63 @@ +# Copyright 2024 Undistro Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.scan.plugins.trivy.persistence.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: trivy-download-db +spec: + ttlSecondsAfterFinished: 0 + template: + spec: + volumes: + - name: trivy-db + persistentVolumeClaim: + claimName: trivy-db + containers: + - name: trivy-download-db + image: "{{ .Values.scan.plugins.trivy.image.repository }}:{{ .Values.scan.plugins.trivy.image.tag }}" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + privileged: false + volumeMounts: + - mountPath: /tmp/trivy-cache + name: trivy-db + command: + - /bin/sh + - -c + - | + time trivy image \ + --debug \ + --no-progress \ + --cache-dir=/tmp/trivy-cache \ + {{- if .Values.scan.plugins.trivy.insecure }} + --insecure \ + {{- end }} + {{- if .Values.scan.plugins.trivy.persistence.downloadJavaDB }} + --download-java-db-only \ + {{- end }} + --download-db-only + env: + - name: SSL_CERT_DIR + value: "/etc/ssl/:/run/secrets/kubernetes.io/serviceaccount/" + {{- if .Values.httpsProxy }} + - name: HTTPS_PROXY + value: {{ .Values.httpsProxy | quote }} + - name: NO_PROXY + value: {{ .Values.noProxy | quote }} + {{- end }} + restartPolicy: OnFailure +{{- end }} \ No newline at end of file diff --git a/charts/zora/templates/plugins/trivy-pvc.yaml b/charts/zora/templates/plugins/trivy-pvc.yaml new file mode 100644 index 00000000..3b1100d4 --- /dev/null +++ b/charts/zora/templates/plugins/trivy-pvc.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 Undistro Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.scan.plugins.trivy.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: trivy-db +spec: + {{- if .Values.scan.plugins.trivy.persistence.storageClass }} + storageClassName: {{ .Values.scan.plugins.trivy.persistence.storageClass | quote }} + {{- end }} + accessModes: + - {{ .Values.scan.plugins.trivy.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.scan.plugins.trivy.persistence.storageRequest | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/zora/templates/plugins/trivy.yaml b/charts/zora/templates/plugins/trivy.yaml index c3082359..22a2d497 100644 --- a/charts/zora/templates/plugins/trivy.yaml +++ b/charts/zora/templates/plugins/trivy.yaml @@ -28,6 +28,7 @@ spec: mountCustomChecksVolume: false securityContext: allowPrivilegeEscalation: false + privileged: false {{- with .Values.scan.plugins.trivy.envFrom }} envFrom: {{- toYaml . | nindent 4}} diff --git a/charts/zora/values.yaml b/charts/zora/values.yaml index d8dceb1e..c7766032 100644 --- a/charts/zora/values.yaml +++ b/charts/zora/values.yaml @@ -229,6 +229,18 @@ scan: # -- Allow insecure server connections for Trivy insecure: false + persistence: + # -- Specifies whether Trivy vulnerabilities database should be persisted between the scans, using PersistentVolumeClaim + enabled: true + # -- Persistence access mode + accessMode: ReadWriteOnce + # -- Persistence storage class. Let it empty for default storage class + storageClass: "" + # -- Persistence storage size + storageRequest: 1Gi + # -- Specifies whether Java vulnerability database should be downloaded on helm install/upgrade + downloadJavaDB: false + popeye: # -- Specifies whether the following resources should be skipped by `popeye` scans. # 1. resources from `kube-system`, `kube-public` and `kube-node-lease` namespaces; diff --git a/cmd/main.go b/cmd/main.go index 675c4027..53212cdd 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -68,6 +68,7 @@ func main() { var checksConfigMapNamespace string var checksConfigMapName string var kubexnsImage string + var trivyPVC string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -86,6 +87,7 @@ func main() { flag.StringVar(&checksConfigMapNamespace, "checks-configmap-namespace", "zora-system", "Namespace of custom checks ConfigMap") flag.StringVar(&checksConfigMapName, "checks-configmap-name", "zora-custom-checks", "Name of custom checks ConfigMap") flag.StringVar(&kubexnsImage, "kubexns-image", "ghcr.io/undistro/kubexns:latest", "kubexns image") + flag.StringVar(&trivyPVC, "trivy-db-pvc", "", "PersistentVolumeClaim name for Trivy DB") opts := zap.Options{ Development: true, @@ -161,6 +163,7 @@ func main() { OnUpdate: onClusterScanUpdate, OnDelete: onClusterScanDelete, KubexnsImage: kubexnsImage, + TrivyPVC: trivyPVC, ChecksConfigMap: fmt.Sprintf("%s/%s", checksConfigMapNamespace, checksConfigMapName), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterScan") diff --git a/internal/controller/zora/clusterscan_controller.go b/internal/controller/zora/clusterscan_controller.go index 67d1cad6..1c897071 100644 --- a/internal/controller/zora/clusterscan_controller.go +++ b/internal/controller/zora/clusterscan_controller.go @@ -63,6 +63,7 @@ type ClusterScanReconciler struct { ServiceAccountName string KubexnsImage string ChecksConfigMap string + TrivyPVC string Annotations map[string]string OnUpdate saas.ClusterScanHook OnDelete saas.ClusterScanHook @@ -217,6 +218,7 @@ func (r *ClusterScanReconciler) reconcile(ctx context.Context, clusterscan *v1al Suspend: notReadyErr != nil, KubexnsImage: r.KubexnsImage, ChecksConfigMap: r.ChecksConfigMap, + TrivyPVC: r.TrivyPVC, ClusterUID: cluster.UID, } diff --git a/pkg/plugins/cronjob.go b/pkg/plugins/cronjob.go index 0fb83237..b2736e4e 100644 --- a/pkg/plugins/cronjob.go +++ b/pkg/plugins/cronjob.go @@ -41,6 +41,7 @@ const ( labelClusterScan = "zora.undistro.io/cluster-scan" labelPlugin = "zora.undistro.io/plugin" annotationDefaultContainer = "kubectl.kubernetes.io/default-container" + trivyDBVolumeName = "trivy-db" ) var ( @@ -75,6 +76,8 @@ var ( // customChecksVolume represents the volume mount to be used in the init container customChecksVolume = corev1.VolumeMount{Name: checksVolumeName, MountPath: checksPath} + + trivyDBVolumeMount = corev1.VolumeMount{Name: trivyDBVolumeName, MountPath: "/tmp/trivy-cache"} ) func NewCronJob(name, namespace string) *batchv1.CronJob { @@ -93,6 +96,7 @@ type CronJobMutator struct { Suspend bool KubexnsImage string ChecksConfigMap string + TrivyPVC string ClusterUID types.UID } @@ -140,6 +144,15 @@ func (r *CronJobMutator) Mutate() error { }) } + if r.Plugin.Name == "trivy" && r.TrivyPVC != "" { + r.Existing.Spec.JobTemplate.Spec.Template.Spec.Volumes = append(r.Existing.Spec.JobTemplate.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: trivyDBVolumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: r.TrivyPVC}, + }, + }) + } + if pointer.BoolDeref(r.Plugin.Spec.MountCustomChecksVolume, false) { initContainer := r.initContainer() if len(r.Existing.Spec.JobTemplate.Spec.Template.Spec.InitContainers) == 0 { @@ -224,6 +237,9 @@ func (r *CronJobMutator) pluginContainer() corev1.Container { if pointer.BoolDeref(r.Plugin.Spec.MountCustomChecksVolume, false) { c.VolumeMounts = append(c.VolumeMounts, customChecksVolume) } + if r.Plugin.Name == "trivy" { + c.VolumeMounts = append(c.VolumeMounts, trivyDBVolumeMount) + } return c }