diff --git a/argocd/manifests/prowler/cronjob-iac-scan.yaml b/argocd/manifests/prowler/cronjob-iac-scan.yaml index 27c7c0b..c1303a5 100644 --- a/argocd/manifests/prowler/cronjob-iac-scan.yaml +++ b/argocd/manifests/prowler/cronjob-iac-scan.yaml @@ -19,13 +19,19 @@ spec: - name: prowler image: registry.ops.eblu.me/blumeops/prowler:kustomized command: ["/bin/sh", "-c"] + # Prowler's --mutelist-file is a no-op for the IaC provider + # (it delegates to Trivy). The Prowler image's trivy shim + # injects --ignorefile $TRIVY_IGNOREFILE when set; see + # containers/prowler/Dockerfile. + env: + - name: TRIVY_IGNOREFILE + value: /mutelist/trivyignore.yaml args: - | DATEDIR=/reports/prowler-iac/$(date +%Y-%m-%d) mkdir -p "$DATEDIR" prowler iac \ --scan-repository-url https://forge.ops.eblu.me/eblume/blumeops.git \ - --mutelist-file /mutelist/iac.yaml \ -z \ --output-formats html csv json-ocsf \ --output-directory "$DATEDIR" @@ -44,5 +50,5 @@ spec: configMap: name: prowler-mutelist items: - - key: iac.yaml - path: iac.yaml + - key: trivyignore.yaml + path: trivyignore.yaml diff --git a/argocd/manifests/prowler/kustomization.yaml b/argocd/manifests/prowler/kustomization.yaml index 0d40035..f1ed931 100644 --- a/argocd/manifests/prowler/kustomization.yaml +++ b/argocd/manifests/prowler/kustomization.yaml @@ -23,7 +23,7 @@ configMapGenerator: - mutelist/core-pod-security.yaml - mutelist/manual-node-checks.yaml - mutelist/rbac.yaml - - mutelist/iac.yaml + - mutelist/trivyignore.yaml images: - name: registry.ops.eblu.me/blumeops/prowler diff --git a/argocd/manifests/prowler/mutelist/iac.yaml b/argocd/manifests/prowler/mutelist/iac.yaml deleted file mode 100644 index a94f947..0000000 --- a/argocd/manifests/prowler/mutelist/iac.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# IaC scan mutes — Trivy KSV checks against argocd/manifests/. -# -# Check ID format: "KSV-XXXX" (Trivy Kubernetes Security check IDs). -# Region / Resource semantics for Prowler IaC: Region == repo path, -# Resource == manifest file path (relative to repo root). -Mutelist: - Accounts: - "*": - Checks: - "KSV-0041": - # Mutelist entries under one CHECK_ID share a Resources list. - # Each resource here justifies muting under a distinct CC; see - # the per-resource notes below. - Regions: ["*"] - Resources: - # CC: operator-purpose-bound-rbac. external-secrets-operator's - # entire function is to read and synthesize Secret objects; - # ClusterRole over secrets is its purpose. Both the controller - # and cert-controller are upstream-defined. - - "argocd/manifests/external-secrets/rbac.yaml" - # CC: kube-state-metrics-metadata-only. KSM exposes only - # Secret metadata (name, namespace, type, labels), never the - # data field. list/watch on secrets is required to expose - # kube_secret_info and kube_secret_labels. - - "argocd/manifests/kube-state-metrics/rbac.yaml" - - "argocd/manifests/kube-state-metrics-ringtail/rbac.yaml" - Description: >- - CC: operator-purpose-bound-rbac (external-secrets); - kube-state-metrics-metadata-only (kube-state-metrics). - "KSV-0114": - Regions: ["*"] - Resources: - - "argocd/manifests/external-secrets/rbac.yaml" - Description: >- - CC: operator-purpose-bound-rbac. cert-controller manages the - external-secrets validating webhook configurations to inject - its own rotating CA bundle. RBAC is scoped to two named - webhooks (secretstore-validate, externalsecret-validate) via - resourceNames; KSV-0114 doesn't see the resourceNames - restriction so reports the full ClusterRole. diff --git a/argocd/manifests/prowler/mutelist/trivyignore.yaml b/argocd/manifests/prowler/mutelist/trivyignore.yaml new file mode 100644 index 0000000..22c612a --- /dev/null +++ b/argocd/manifests/prowler/mutelist/trivyignore.yaml @@ -0,0 +1,39 @@ +# Trivy ignorefile for Prowler IaC scan. +# +# Prowler's `--mutelist-file` flag is a no-op for the IaC provider +# (iac_provider.py sets self._mutelist = None and delegates to Trivy). +# Trivy in turn does not auto-discover this YAML form from cwd, so the +# Prowler image ships a shim wrapper around `trivy` that injects +# --ignorefile $TRIVY_IGNOREFILE when the env var is set. The cronjob +# mounts this file and sets TRIVY_IGNOREFILE accordingly. +# +# Schema: https://trivy.dev/latest/docs/configuration/filtering/ +# IDs use the hyphenated form Trivy displays (KSV-0041, not KSV0041). +misconfigurations: + - id: KSV-0041 + paths: + - "argocd/manifests/external-secrets/rbac.yaml" + statement: >- + CC: operator-purpose-bound-rbac. external-secrets-operator's entire + function is to read and synthesize Secret objects; ClusterRole over + secrets is its purpose. Both the controller and cert-controller are + upstream-defined. + - id: KSV-0041 + paths: + - "argocd/manifests/kube-state-metrics/rbac.yaml" + - "argocd/manifests/kube-state-metrics-ringtail/rbac.yaml" + statement: >- + CC: kube-state-metrics-metadata-only. KSM exposes only Secret + metadata (name, namespace, type, labels), never the data field. + list/watch on secrets is required for kube_secret_info / + kube_secret_labels metrics. + - id: KSV-0114 + paths: + - "argocd/manifests/external-secrets/rbac.yaml" + statement: >- + CC: operator-purpose-bound-rbac. cert-controller manages the + external-secrets validating webhook configurations to inject its + own rotating CA bundle. RBAC is scoped to two named webhooks + (secretstore-validate, externalsecret-validate) via resourceNames; + KSV-0114 doesn't see the resourceNames restriction so reports the + full ClusterRole. diff --git a/containers/prowler/Dockerfile b/containers/prowler/Dockerfile index bd74bdb..c5157cb 100644 --- a/containers/prowler/Dockerfile +++ b/containers/prowler/Dockerfile @@ -44,10 +44,28 @@ RUN ARCH=$(dpkg --print-architecture) \ && apt-get update && apt-get install -y --no-install-recommends wget ca-certificates \ && wget -q "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" -O /tmp/trivy.tar.gz \ && tar xzf /tmp/trivy.tar.gz -C /usr/local/bin trivy \ - && chmod +x /usr/local/bin/trivy \ + && mv /usr/local/bin/trivy /usr/local/bin/trivy.real \ + && chmod +x /usr/local/bin/trivy.real \ && rm /tmp/trivy.tar.gz \ && apt-get purge -y wget && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* +# Shim: Prowler's IaC provider invokes `trivy fs` directly with no +# --ignorefile flag, so any TRIVY_IGNOREFILE the user sets is ignored. +# This wrapper injects --ignorefile when the env var points at a real +# file and the invocation is `trivy fs ...`. Other subcommands and +# global-only invocations (--version, --help) pass through unchanged. +# TODO(upstream): contribute --ignorefile plumbing to prowler-cloud/prowler +# iac_provider.py so this shim isn't necessary. +RUN printf '%s\n' \ + '#!/bin/sh' \ + 'if [ "${1:-}" = "fs" ] && [ -n "${TRIVY_IGNOREFILE:-}" ] && [ -f "${TRIVY_IGNOREFILE}" ]; then' \ + ' shift' \ + ' exec /usr/local/bin/trivy.real fs --ignorefile "${TRIVY_IGNOREFILE}" "$@"' \ + 'fi' \ + 'exec /usr/local/bin/trivy.real "$@"' \ + > /usr/local/bin/trivy \ + && chmod +x /usr/local/bin/trivy + RUN addgroup --gid 1000 prowler \ && adduser --uid 1000 --gid 1000 --disabled-password --gecos "" prowler \ && mkdir -p /tmp/.cache/trivy && chown prowler:prowler /tmp/.cache/trivy diff --git a/docs/changelog.d/prowler-iac-mutelist.infra.md b/docs/changelog.d/prowler-iac-mutelist.infra.md index ea573b9..793c1ec 100644 --- a/docs/changelog.d/prowler-iac-mutelist.infra.md +++ b/docs/changelog.d/prowler-iac-mutelist.infra.md @@ -1 +1 @@ -Address the 6 critical Prowler IaC findings against `argocd/manifests/`. The IaC cronjob now passes `--mutelist-file` (previously unused), fed from a new `mutelist/iac.yaml`. Two new compensating controls — `operator-purpose-bound-rbac` and `kube-state-metrics-metadata-only` — justify muting the `external-secrets` and `kube-state-metrics` Secret-access findings (KSV-0041, KSV-0114). Separately, `grafana-clusterrole` is tightened to remove `secrets` access entirely: the dashboard sidecar already only consumes ConfigMap-labeled dashboards, so its `RESOURCE` env var is now `configmap` instead of `both`. +Address the 6 critical Prowler IaC findings against `argocd/manifests/`. Prowler's IaC provider hardcodes `self._mutelist = None` and delegates filtering to Trivy, but doesn't plumb `--ignorefile` through — so the documented "use Trivy filtering" path is actually broken. Added a shim around `trivy` in the Prowler image that injects `--ignorefile $TRIVY_IGNOREFILE` for `trivy fs` invocations when the env var points at a real file. The IaC cronjob now mounts `mutelist/trivyignore.yaml` (Trivy's per-path schema) and sets the env var. Two new compensating controls — `operator-purpose-bound-rbac` and `kube-state-metrics-metadata-only` — justify muting the `external-secrets` and `kube-state-metrics` Secret-access findings (KSV-0041, KSV-0114). Separately, `grafana-clusterrole` is tightened to remove `secrets` access entirely: the dashboard sidecar already only consumes ConfigMap-labeled dashboards, so its `RESOURCE` env var is now `configmap` instead of `both`.