Address 6 critical Prowler IaC findings (mute + grafana RBAC tighten) #340

Merged
eblume merged 3 commits from prowler-iac-mutelist into main 2026-04-29 10:43:33 -07:00
7 changed files with 93 additions and 3 deletions
Showing only changes of commit 0510a8151c - Show all commits

Address critical Prowler IaC findings via mute + RBAC tightening

Six critical IaC findings against argocd/manifests/ broke into two
patterns: legitimate-by-design RBAC (mute) and over-broad RBAC (fix).

Plumbing:
  - cronjob-iac-scan.yaml now passes --mutelist-file (previously
    unused, which is why all IaC findings reported as unmuted)
  - new mutelist/iac.yaml is bundled into the prowler-mutelist
    ConfigMap and mounted into the IaC cronjob via items: selector

Compensating controls (in compensating-controls.yaml):
  - operator-purpose-bound-rbac — external-secrets-operator's whole
    function is to manage Secret objects; ClusterRole over secrets
    matches its purpose. cert-controller mutates its own validating
    webhooks to inject a rotating CA bundle.
  - kube-state-metrics-metadata-only — KSM exposes only Secret
    metadata via kube_secret_info / kube_secret_labels; the data
    field is never read into exposed metrics.

Mutes (mutelist/iac.yaml):
  - KSV-0041 for external-secrets/rbac.yaml,
    kube-state-metrics/rbac.yaml,
    kube-state-metrics-ringtail/rbac.yaml
  - KSV-0114 for external-secrets/rbac.yaml

Real fix:
  - grafana-clusterrole no longer reads secrets. The dashboard sidecar
    (RESOURCE=both → configmap, both init and watch instances) only
    needs ConfigMap-labeled dashboards; no Secrets are labeled
    grafana_dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Erich Blume 2026-04-27 12:48:54 -07:00

View file

@ -156,7 +156,9 @@ spec:
- name: FOLDER - name: FOLDER
value: /tmp/dashboards value: /tmp/dashboards
- name: RESOURCE - 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 - name: FOLDER_ANNOTATION
value: grafana_folder value: grafana_folder
securityContext: securityContext:
@ -183,7 +185,7 @@ spec:
- name: FOLDER - name: FOLDER
value: /tmp/dashboards value: /tmp/dashboards
- name: RESOURCE - name: RESOURCE
value: both value: configmap
- name: FOLDER_ANNOTATION - name: FOLDER_ANNOTATION
value: grafana_folder value: grafana_folder
- name: REQ_USERNAME - name: REQ_USERNAME

View file

@ -7,7 +7,7 @@ metadata:
app.kubernetes.io/instance: grafana app.kubernetes.io/instance: grafana
rules: rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["configmaps", "secrets"] resources: ["configmaps"]
verbs: ["get", "watch", "list"] verbs: ["get", "watch", "list"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1

View file

@ -25,14 +25,24 @@ spec:
mkdir -p "$DATEDIR" mkdir -p "$DATEDIR"
prowler iac \ prowler iac \
--scan-repository-url https://forge.ops.eblu.me/eblume/blumeops.git \ --scan-repository-url https://forge.ops.eblu.me/eblume/blumeops.git \
--mutelist-file /mutelist/iac.yaml \
-z \ -z \
--output-formats html csv json-ocsf \ --output-formats html csv json-ocsf \
--output-directory "$DATEDIR" --output-directory "$DATEDIR"
volumeMounts: volumeMounts:
- name: reports - name: reports
mountPath: /reports mountPath: /reports
- name: mutelist
mountPath: /mutelist
readOnly: true
restartPolicy: OnFailure restartPolicy: OnFailure
volumes: volumes:
- name: reports - name: reports
persistentVolumeClaim: persistentVolumeClaim:
claimName: prowler-reports claimName: prowler-reports
- name: mutelist
configMap:
name: prowler-mutelist
items:
- key: iac.yaml
path: iac.yaml

View file

@ -23,6 +23,7 @@ configMapGenerator:
- mutelist/core-pod-security.yaml - mutelist/core-pod-security.yaml
- mutelist/manual-node-checks.yaml - mutelist/manual-node-checks.yaml
- mutelist/rbac.yaml - mutelist/rbac.yaml
- mutelist/iac.yaml
images: images:
- name: registry.ops.eblu.me/blumeops/prowler - name: registry.ops.eblu.me/blumeops/prowler

View file

@ -0,0 +1,40 @@
# 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.

View file

@ -139,6 +139,42 @@ controls:
MANUAL findings appear in Prowler, add corresponding verification MANUAL findings appear in Prowler, add corresponding verification
logic to the script and update the mutelist. 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 - id: observability-stack-audit
description: >- description: >-
Alloy collects pod logs and ships them to Loki, providing an Alloy collects pod logs and ships them to Loki, providing an

View file

@ -0,0 +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`.