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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -25,14 +25,24 @@ spec:
|
|||
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"
|
||||
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: iac.yaml
|
||||
path: iac.yaml
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ configMapGenerator:
|
|||
- mutelist/core-pod-security.yaml
|
||||
- mutelist/manual-node-checks.yaml
|
||||
- mutelist/rbac.yaml
|
||||
- mutelist/iac.yaml
|
||||
|
||||
images:
|
||||
- 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
|
||||
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
|
||||
|
|
|
|||
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