Compare commits
3 commits
main
...
prowler-ia
| Author | SHA1 | Date | |
|---|---|---|---|
| b8d2c3c1ed | |||
| 2daf6291b7 | |||
| 0510a8151c |
8 changed files with 118 additions and 5 deletions
|
|
@ -156,7 +156,9 @@ spec:
|
|||
- name: FOLDER
|
||||
value: /tmp/dashboards
|
||||
- name: RESOURCE
|
||||
value: both
|
||||
# ConfigMap-only — no dashboards are sourced from Secrets,
|
||||
# so the ServiceAccount has no read access to secrets.
|
||||
value: configmap
|
||||
- name: FOLDER_ANNOTATION
|
||||
value: grafana_folder
|
||||
securityContext:
|
||||
|
|
@ -183,7 +185,7 @@ spec:
|
|||
- name: FOLDER
|
||||
value: /tmp/dashboards
|
||||
- name: RESOURCE
|
||||
value: both
|
||||
value: configmap
|
||||
- name: FOLDER_ANNOTATION
|
||||
value: grafana_folder
|
||||
- name: REQ_USERNAME
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ metadata:
|
|||
app.kubernetes.io/instance: grafana
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps", "secrets"]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ 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)
|
||||
|
|
@ -31,8 +38,17 @@ spec:
|
|||
volumeMounts:
|
||||
- name: reports
|
||||
mountPath: /reports
|
||||
- name: mutelist
|
||||
mountPath: /mutelist
|
||||
readOnly: true
|
||||
restartPolicy: OnFailure
|
||||
volumes:
|
||||
- name: reports
|
||||
persistentVolumeClaim:
|
||||
claimName: prowler-reports
|
||||
- name: mutelist
|
||||
configMap:
|
||||
name: prowler-mutelist
|
||||
items:
|
||||
- key: trivyignore.yaml
|
||||
path: trivyignore.yaml
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ configMapGenerator:
|
|||
- mutelist/core-pod-security.yaml
|
||||
- mutelist/manual-node-checks.yaml
|
||||
- mutelist/rbac.yaml
|
||||
- mutelist/trivyignore.yaml
|
||||
|
||||
images:
|
||||
- name: registry.ops.eblu.me/blumeops/prowler
|
||||
newTag: v5.23.0-7c1cd11
|
||||
newTag: v5.23.0-2daf629
|
||||
|
|
|
|||
39
argocd/manifests/prowler/mutelist/trivyignore.yaml
Normal file
39
argocd/manifests/prowler/mutelist/trivyignore.yaml
Normal file
|
|
@ -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.
|
||||
|
|
@ -139,6 +139,42 @@ controls:
|
|||
MANUAL findings appear in Prowler, add corresponding verification
|
||||
logic to the script and update the mutelist.
|
||||
|
||||
- id: operator-purpose-bound-rbac
|
||||
description: >-
|
||||
Operators whose entire function is to manage a sensitive resource
|
||||
legitimately need RBAC over that resource. external-secrets-operator
|
||||
manages Secret objects (its purpose) and the cert-controller mutates
|
||||
its own ValidatingWebhookConfigurations to inject rotating CA bundles.
|
||||
Risk is bounded by: (1) the operator code being upstream open-source
|
||||
and reviewed; (2) RBAC scoped to specific named webhooks where
|
||||
possible; (3) supply chain controls on the operator image (mirrored
|
||||
to local registry, version tracked in service-versions.yaml).
|
||||
created: 2026-04-27
|
||||
last-reviewed: 2026-04-27
|
||||
notes: >-
|
||||
Verify by checking that the operators in question still match their
|
||||
stated purpose (i.e. external-secrets is still the only consumer of
|
||||
these ClusterRoles) and that upstream hasn't published advisories
|
||||
for credential-handling bugs. Re-evaluate if a non-secrets-managing
|
||||
ClusterRole appears under this control.
|
||||
|
||||
- id: kube-state-metrics-metadata-only
|
||||
description: >-
|
||||
kube-state-metrics holds list/watch on Secrets cluster-wide but only
|
||||
exposes Secret object *metadata* (name, namespace, type, creation
|
||||
timestamp, labels) via the kube_secret_info / kube_secret_labels
|
||||
metrics. Secret data fields are never read into KSM's exposed
|
||||
metrics by upstream design. Mitigation rests on KSM's metric
|
||||
schema, the version pin in service-versions.yaml, and the metrics
|
||||
endpoint being reachable only on the cluster network.
|
||||
created: 2026-04-27
|
||||
last-reviewed: 2026-04-27
|
||||
notes: >-
|
||||
Verify by inspecting the /metrics endpoint output for any series
|
||||
that include secret data (only *_info and *_labels metrics should
|
||||
reference secrets, and labels should be limited to user-applied
|
||||
labels — never the data:). Re-evaluate on KSM version bumps.
|
||||
|
||||
- id: observability-stack-audit
|
||||
description: >-
|
||||
Alloy collects pod logs and ships them to Loki, providing an
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
docs/changelog.d/prowler-iac-mutelist.infra.md
Normal file
1
docs/changelog.d/prowler-iac-mutelist.infra.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue