Add compensating controls framework and date-based report dirs (#320)
## Summary - Add `compensating-controls.yaml` tracking 9 named controls that justify suppressed security findings - Update all Prowler mutelist descriptions with `CC: <id>` references to named controls - Add `mise run review-compensating-controls` task — surfaces stalest control with all codebase references - Add [[review-compensating-controls]] how-to doc - Organize Prowler and Kingfisher reports into `YYYY-MM-DD` subdirectories ### Compensating controls | ID | Mitigates | |----|-----------| | `single-user-cluster` | Image cache abuse, RBAC breadth, system pod privileges | | `tailscale-network-isolation` | Profiling endpoints, weak TLS, debug ports | | `local-registry` | AlwaysPullImages gap | | `sso-gated-admin-tools` | ArgoCD wildcard RBAC | | `operator-managed-pods` | Tailscale proxy pod security settings | | `ephemeral-privileged-jobs` | Prowler hostPID exposure | | `trusted-ci-only` | Forgejo runner DinD | | `init-container-isolation` | Grafana root init container | | `observability-stack-audit` | Missing apiserver audit logging | ## Test plan - [ ] `mise run review-compensating-controls` shows table and references - [ ] `kubectl kustomize argocd/manifests/prowler/` renders correctly - [ ] Sync prowler and kingfisher, verify next scan writes to dated subdirectory - [ ] Grep for `CC:` in mutelist files — every muted finding should have at least one 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #320
This commit is contained in:
parent
a76e471d54
commit
4059b3d27b
13 changed files with 516 additions and 77 deletions
|
|
@ -23,7 +23,7 @@ spec:
|
|||
- |
|
||||
set -e
|
||||
STAMP=$(date +%Y%m%d-%H%M%S)
|
||||
OUTDIR=/reports/kingfisher
|
||||
OUTDIR=/reports/kingfisher/$(date +%Y-%m-%d)
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# Exit codes: 0=clean, 200=findings, 205=validated findings.
|
||||
|
|
|
|||
|
|
@ -18,17 +18,16 @@ spec:
|
|||
containers:
|
||||
- name: prowler
|
||||
image: registry.ops.eblu.me/blumeops/prowler:kustomized
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- iac
|
||||
- --scan-repository-url
|
||||
- https://forge.ops.eblu.me/eblume/blumeops.git
|
||||
- -z
|
||||
- --output-formats
|
||||
- html
|
||||
- csv
|
||||
- json-ocsf
|
||||
- --output-directory
|
||||
- /reports/prowler-iac
|
||||
- |
|
||||
DATEDIR=/reports/prowler-iac/$(date +%Y-%m-%d)
|
||||
mkdir -p "$DATEDIR"
|
||||
prowler iac \
|
||||
--scan-repository-url https://forge.ops.eblu.me/eblume/blumeops.git \
|
||||
-z \
|
||||
--output-formats html csv json-ocsf \
|
||||
--output-directory "$DATEDIR"
|
||||
volumeMounts:
|
||||
- name: reports
|
||||
mountPath: /reports
|
||||
|
|
|
|||
|
|
@ -48,17 +48,16 @@ spec:
|
|||
containers:
|
||||
- name: prowler
|
||||
image: registry.ops.eblu.me/blumeops/prowler:kustomized
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- image
|
||||
- --image-list
|
||||
- /shared/images.txt
|
||||
- -z
|
||||
- --output-formats
|
||||
- html
|
||||
- csv
|
||||
- json-ocsf
|
||||
- --output-directory
|
||||
- /reports/prowler-images
|
||||
- |
|
||||
DATEDIR=/reports/prowler-images/$(date +%Y-%m-%d)
|
||||
mkdir -p "$DATEDIR"
|
||||
prowler image \
|
||||
--image-list /shared/images.txt \
|
||||
-z \
|
||||
--output-formats html csv json-ocsf \
|
||||
--output-directory "$DATEDIR"
|
||||
volumeMounts:
|
||||
- name: reports
|
||||
mountPath: /reports
|
||||
|
|
|
|||
|
|
@ -40,19 +40,17 @@ spec:
|
|||
containers:
|
||||
- name: prowler
|
||||
image: registry.ops.eblu.me/blumeops/prowler:kustomized
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- kubernetes
|
||||
- --compliance
|
||||
- cis_1.11_kubernetes
|
||||
- --mutelist-file
|
||||
- /tmp/mutelist/mutelist.yaml
|
||||
- -z
|
||||
- --output-formats
|
||||
- html
|
||||
- csv
|
||||
- json-ocsf
|
||||
- --output-directory
|
||||
- /reports/prowler
|
||||
- |
|
||||
DATEDIR=/reports/prowler/$(date +%Y-%m-%d)
|
||||
mkdir -p "$DATEDIR"
|
||||
prowler kubernetes \
|
||||
--compliance cis_1.11_kubernetes \
|
||||
--mutelist-file /tmp/mutelist/mutelist.yaml \
|
||||
-z \
|
||||
--output-formats html csv json-ocsf \
|
||||
--output-directory "$DATEDIR"
|
||||
volumeMounts:
|
||||
- name: reports
|
||||
mountPath: /reports
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Minikube apiserver — flags managed by static pod manifests.
|
||||
# Compensating control: cluster not internet-exposed; access via Tailscale ACLs.
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"*":
|
||||
|
|
@ -7,48 +6,48 @@ Mutelist:
|
|||
"apiserver_always_pull_images_plugin":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default; AlwaysPullImages not enabled."
|
||||
Description: "CC: single-user-cluster, local-registry. Only the operator has cluster access; all images pulled from private zot registry."
|
||||
"apiserver_audit_log_maxage_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube does not configure audit logging."
|
||||
Description: "CC: observability-stack-audit. Alloy/Loki provides pod-level audit trail."
|
||||
"apiserver_audit_log_maxbackup_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube does not configure audit logging."
|
||||
Description: "CC: observability-stack-audit. Alloy/Loki provides pod-level audit trail."
|
||||
"apiserver_audit_log_maxsize_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube does not configure audit logging."
|
||||
Description: "CC: observability-stack-audit. Alloy/Loki provides pod-level audit trail."
|
||||
"apiserver_audit_log_path_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube does not configure audit logging."
|
||||
Description: "CC: observability-stack-audit. Alloy/Loki provides pod-level audit trail."
|
||||
"apiserver_deny_service_external_ips":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default; no external IPs in use."
|
||||
Description: "CC: tailscale-network-isolation. No external IPs routable; cluster only reachable via tailnet."
|
||||
"apiserver_disable_profiling":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default; profiling endpoint not exposed."
|
||||
Description: "CC: tailscale-network-isolation. Profiling endpoint unreachable from public internet."
|
||||
"apiserver_encryption_provider_config_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube does not configure etcd encryption at rest."
|
||||
Description: "CC: tailscale-network-isolation, single-user-cluster. Etcd not network-exposed; only operator has node access."
|
||||
"apiserver_kubelet_cert_auth":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube manages kubelet certificates automatically."
|
||||
Description: "CC: tailscale-network-isolation. Kubelet API not exposed outside the node; minikube auto-generates certificates."
|
||||
"apiserver_request_timeout_set":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default; using K8s default timeout."
|
||||
Description: "CC: tailscale-network-isolation. API server only reachable via tailnet; DoS risk limited to trusted clients."
|
||||
"apiserver_service_account_lookup_true":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default."
|
||||
Description: "CC: single-user-cluster. Only operator manages service accounts; no revoked tokens in circulation."
|
||||
"apiserver_strong_ciphers_only":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-apiserver-minikube$"]
|
||||
Description: "Minikube default TLS cipher suite."
|
||||
Description: "CC: tailscale-network-isolation. API server traffic encrypted by WireGuard at the network layer."
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Minikube control-plane components — managed by static pod manifests.
|
||||
# Compensating control: cluster not internet-exposed; access via Tailscale ACLs.
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"*":
|
||||
|
|
@ -7,12 +6,12 @@ Mutelist:
|
|||
"controllermanager_disable_profiling":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-controller-manager-minikube$"]
|
||||
Description: "Minikube default; profiling endpoint not exposed outside tailnet."
|
||||
Description: "CC: tailscale-network-isolation. Profiling endpoint unreachable from public internet."
|
||||
"scheduler_profiling":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kube-scheduler-minikube$"]
|
||||
Description: "Minikube default; profiling endpoint not exposed outside tailnet."
|
||||
Description: "CC: tailscale-network-isolation. Profiling endpoint unreachable from public internet."
|
||||
"kubelet_tls_cert_and_key":
|
||||
Regions: ["*"]
|
||||
Resources: ["^kubelet-config$"]
|
||||
Description: "Minikube uses auto-generated kubelet certificates."
|
||||
Description: "CC: tailscale-network-isolation, single-user-cluster. Kubelet API not exposed outside node; minikube auto-generates certificates."
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Mutelist:
|
|||
"core_minimize_hostNetwork_containers":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
# Minikube control plane — requires hostNetwork by design
|
||||
# Minikube control plane
|
||||
- "^etcd-minikube$"
|
||||
- "^kube-apiserver-minikube$"
|
||||
- "^kube-controller-manager-minikube$"
|
||||
|
|
@ -17,8 +17,9 @@ Mutelist:
|
|||
- "^kindnet-"
|
||||
- "^storage-provisioner$"
|
||||
Description: >-
|
||||
Control-plane and networking pods require hostNetwork.
|
||||
All managed by minikube.
|
||||
CC: tailscale-network-isolation. Control-plane and networking
|
||||
pods require hostNetwork by design. Host network itself is
|
||||
only reachable via tailnet.
|
||||
"core_minimize_privileged_containers":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
|
|
@ -27,12 +28,13 @@ Mutelist:
|
|||
# Tailscale operator-managed proxies
|
||||
- "^ts-"
|
||||
- "^ingress-"
|
||||
# Forgejo runner — Docker-in-Docker for CI builds
|
||||
# Forgejo runner
|
||||
- "^forgejo-runner-"
|
||||
Description: >-
|
||||
kube-proxy: iptables (minikube). ts-*/ingress-*: network
|
||||
namespace manipulation (Tailscale operator). forgejo-runner:
|
||||
Docker-in-Docker for CI.
|
||||
CC: single-user-cluster, operator-managed-pods, trusted-ci-only.
|
||||
kube-proxy: system pod, single-user cluster. ts-*/ingress-*:
|
||||
Tailscale operator-managed. forgejo-runner: DinD limited to
|
||||
trusted private forge repos.
|
||||
"core_seccomp_profile_docker_default":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
|
|
@ -47,34 +49,38 @@ Mutelist:
|
|||
- "^nameserver-"
|
||||
- "^ingress-"
|
||||
Description: >-
|
||||
System pods (minikube) and Tailscale operator pods — seccomp
|
||||
profiles set by upstream/operator, not user manifests.
|
||||
CC: single-user-cluster, operator-managed-pods. System pods
|
||||
managed by minikube and Tailscale operator; seccomp profiles
|
||||
set by upstream. Single-user cluster limits exploit surface.
|
||||
"core_minimize_hostPID_containers":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
- "^prowler-"
|
||||
Description: >-
|
||||
Prowler CIS scanner requires hostPID to check file
|
||||
permissions on kubelet and etcd data directories.
|
||||
CC: ephemeral-privileged-jobs. Prowler CIS scanner requires
|
||||
hostPID for file permission checks. Runs as CronJob with
|
||||
7-day TTL, not a persistent workload.
|
||||
"core_minimize_root_containers_admission":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
- "^grafana-"
|
||||
Description: >-
|
||||
Grafana init-chown-data runs as root to fix PVC ownership.
|
||||
Main containers run as UID 472. Standard pattern.
|
||||
CC: init-container-isolation. Root limited to init-chown-data
|
||||
container; all runtime containers run as UID 472 with caps
|
||||
dropped.
|
||||
"core_minimize_containers_added_capabilities":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
# Minikube system pods
|
||||
- "^coredns-"
|
||||
- "^kindnet-"
|
||||
# Grafana init-chown-data (CHOWN capability)
|
||||
# Grafana init-chown-data
|
||||
- "^grafana-"
|
||||
Description: >-
|
||||
System pods: NET_BIND_SERVICE/NET_RAW required by function
|
||||
(minikube). Grafana: CHOWN for PVC init; all other
|
||||
containers drop ALL.
|
||||
CC: single-user-cluster, init-container-isolation. System
|
||||
pods: capabilities required by function (minikube-managed).
|
||||
Grafana: CHOWN limited to init phase; runtime containers
|
||||
drop ALL.
|
||||
"core_minimize_containers_capabilities_assigned":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
|
|
@ -82,5 +88,5 @@ Mutelist:
|
|||
- "^kindnet-"
|
||||
- "^grafana-"
|
||||
Description: >-
|
||||
System pods (minikube) and Grafana init-chown-data.
|
||||
See core_minimize_containers_added_capabilities.
|
||||
CC: single-user-cluster, init-container-isolation. See
|
||||
core_minimize_containers_added_capabilities.
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ Mutelist:
|
|||
# Built-in Kubernetes roles
|
||||
- "^cluster-admin$"
|
||||
- "^system:"
|
||||
# ArgoCD — requires broad access for deployment management;
|
||||
# ArgoCD itself is SSO-gated via Authentik
|
||||
# ArgoCD
|
||||
- "^argocd-"
|
||||
Description: >-
|
||||
Built-in K8s roles and ArgoCD. ArgoCD access is SSO-gated
|
||||
via Authentik.
|
||||
CC: single-user-cluster, sso-gated-admin-tools. Built-in
|
||||
K8s roles: only operator can bind them. ArgoCD: requires
|
||||
broad access but is SSO-gated via Authentik OIDC.
|
||||
"rbac_minimize_pod_creation_access":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
|
|
@ -26,12 +26,14 @@ Mutelist:
|
|||
# CloudNativePG operator
|
||||
- "^cnpg-manager$"
|
||||
Description: >-
|
||||
Built-in K8s roles required for workload controllers.
|
||||
cnpg-manager: CloudNativePG operator manages PostgreSQL pods.
|
||||
CC: single-user-cluster. Built-in K8s roles and CNPG
|
||||
operator. Only the operator can assign these roles; no
|
||||
untrusted users have cluster access.
|
||||
"rbac_minimize_service_account_token_creation":
|
||||
Regions: ["*"]
|
||||
Resources:
|
||||
- "^system:"
|
||||
Description: >-
|
||||
kube-controller-manager requires token creation for service
|
||||
account management. Built-in role.
|
||||
CC: single-user-cluster. kube-controller-manager requires
|
||||
token creation for SA management. Only operator manages
|
||||
service accounts.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue