Address 6 critical Prowler IaC findings (mute + grafana RBAC tighten) #340
7 changed files with 93 additions and 3 deletions
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>
commit
0510a8151c
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
40
argocd/manifests/prowler/mutelist/iac.yaml
Normal file
40
argocd/manifests/prowler/mutelist/iac.yaml
Normal 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.
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
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/`. 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`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue