Add Prowler mutelist and fix kube-state-metrics seccomp (#319)

## Summary

- Add mutelist files to suppress expected/accepted Prowler CIS findings from components we don't control
- Mutelist files stored in `mutelist/` directory, grouped by category, merged at runtime via initContainer
- Fix missing seccomp `RuntimeDefault` profile on kube-state-metrics deployment

### Mutelist categories

| File | Checks | Covers |
|------|--------|--------|
| `apiserver.yaml` | 12 | Minikube apiserver flags |
| `control-plane.yaml` | 3 | Scheduler, controller-manager, kubelet |
| `core-pod-security.yaml` | 7 | System pods, Tailscale operator, Grafana init, Prowler hostPID, forgejo-runner |
| `rbac.yaml` | 3 | Built-in K8s roles, ArgoCD, CNPG |

Muted findings appear as `status=MUTED` in reports (not hidden), preserving audit trail.

### Not muted (follow-up)

- Alloy, Immich pods missing seccomp — need separate investigation (Helm/operator-managed)

## Test plan

- [ ] `kubectl kustomize argocd/manifests/prowler/` renders cleanly
- [ ] Trigger manual scan: `kubectl --context=minikube-indri -n prowler create job prowler-mutelist-test --from=cronjob/prowler`
- [ ] Verify initContainer merges successfully (check pod logs)
- [ ] Verify muted findings show as `MUTED` in report
- [ ] Sync kube-state-metrics and verify pod starts with seccomp profile

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #319
This commit is contained in:
Erich Blume 2026-03-30 17:22:31 -07:00
commit a76e471d54
8 changed files with 240 additions and 0 deletions

View file

@ -51,3 +51,5 @@ spec:
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault

View file

@ -15,6 +15,28 @@ spec:
securityContext:
seccompProfile:
type: RuntimeDefault
initContainers:
- name: merge-mutelist
image: registry.ops.eblu.me/blumeops/prowler:kustomized
command: ["python3", "-c"]
args:
- |
import yaml, glob, pathlib
merged = {"Mutelist": {"Accounts": {"*": {"Checks": {}}}}}
for f in sorted(glob.glob("/mutelist-parts/*.yaml")):
with open(f) as fh:
data = yaml.safe_load(fh)
checks = data.get("Mutelist", {}).get("Accounts", {}).get("*", {}).get("Checks", {})
merged["Mutelist"]["Accounts"]["*"]["Checks"].update(checks)
pathlib.Path("/tmp/mutelist").mkdir(exist_ok=True)
with open("/tmp/mutelist/mutelist.yaml", "w") as fh:
yaml.dump(merged, fh, default_flow_style=False)
print(f"Merged {len(merged['Mutelist']['Accounts']['*']['Checks'])} checks from {len(glob.glob('/mutelist-parts/*.yaml'))} files")
volumeMounts:
- name: mutelist-parts
mountPath: /mutelist-parts
- name: mutelist-merged
mountPath: /tmp/mutelist
containers:
- name: prowler
image: registry.ops.eblu.me/blumeops/prowler:kustomized
@ -22,6 +44,8 @@ spec:
- kubernetes
- --compliance
- cis_1.11_kubernetes
- --mutelist-file
- /tmp/mutelist/mutelist.yaml
- -z
- --output-formats
- html
@ -32,6 +56,9 @@ spec:
volumeMounts:
- name: reports
mountPath: /reports
- name: mutelist-merged
mountPath: /tmp/mutelist
readOnly: true
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
readOnly: true
@ -47,6 +74,11 @@ spec:
- name: reports
persistentVolumeClaim:
claimName: prowler-reports
- name: mutelist-parts
configMap:
name: prowler-mutelist
- name: mutelist-merged
emptyDir: {}
- name: var-lib-kubelet
hostPath:
path: /var/lib/kubelet

View file

@ -13,6 +13,16 @@ resources:
- cronjob-image-scan.yaml
- cronjob-iac-scan.yaml
configMapGenerator:
- name: prowler-mutelist
options:
disableNameSuffixHash: true
files:
- mutelist/apiserver.yaml
- mutelist/control-plane.yaml
- mutelist/core-pod-security.yaml
- mutelist/rbac.yaml
images:
- name: registry.ops.eblu.me/blumeops/prowler
newTag: v5.22.0-6960243

View file

@ -0,0 +1,54 @@
# Minikube apiserver — flags managed by static pod manifests.
# Compensating control: cluster not internet-exposed; access via Tailscale ACLs.
Mutelist:
Accounts:
"*":
Checks:
"apiserver_always_pull_images_plugin":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default; AlwaysPullImages not enabled."
"apiserver_audit_log_maxage_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube does not configure audit logging."
"apiserver_audit_log_maxbackup_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube does not configure audit logging."
"apiserver_audit_log_maxsize_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube does not configure audit logging."
"apiserver_audit_log_path_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube does not configure audit logging."
"apiserver_deny_service_external_ips":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default; no external IPs in use."
"apiserver_disable_profiling":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default; profiling endpoint not exposed."
"apiserver_encryption_provider_config_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube does not configure etcd encryption at rest."
"apiserver_kubelet_cert_auth":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube manages kubelet certificates automatically."
"apiserver_request_timeout_set":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default; using K8s default timeout."
"apiserver_service_account_lookup_true":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default."
"apiserver_strong_ciphers_only":
Regions: ["*"]
Resources: ["^kube-apiserver-minikube$"]
Description: "Minikube default TLS cipher suite."

View file

@ -0,0 +1,18 @@
# Minikube control-plane components — managed by static pod manifests.
# Compensating control: cluster not internet-exposed; access via Tailscale ACLs.
Mutelist:
Accounts:
"*":
Checks:
"controllermanager_disable_profiling":
Regions: ["*"]
Resources: ["^kube-controller-manager-minikube$"]
Description: "Minikube default; profiling endpoint not exposed outside tailnet."
"scheduler_profiling":
Regions: ["*"]
Resources: ["^kube-scheduler-minikube$"]
Description: "Minikube default; profiling endpoint not exposed outside tailnet."
"kubelet_tls_cert_and_key":
Regions: ["*"]
Resources: ["^kubelet-config$"]
Description: "Minikube uses auto-generated kubelet certificates."

View file

@ -0,0 +1,86 @@
# Pod security checks — system pods, operator-managed pods, and accepted
# operational needs. Each check ID appears once with all matching resources.
Mutelist:
Accounts:
"*":
Checks:
"core_minimize_hostNetwork_containers":
Regions: ["*"]
Resources:
# Minikube control plane — requires hostNetwork by design
- "^etcd-minikube$"
- "^kube-apiserver-minikube$"
- "^kube-controller-manager-minikube$"
- "^kube-scheduler-minikube$"
# Minikube system pods
- "^kube-proxy-"
- "^kindnet-"
- "^storage-provisioner$"
Description: >-
Control-plane and networking pods require hostNetwork.
All managed by minikube.
"core_minimize_privileged_containers":
Regions: ["*"]
Resources:
# Minikube system
- "^kube-proxy-"
# Tailscale operator-managed proxies
- "^ts-"
- "^ingress-"
# Forgejo runner — Docker-in-Docker for CI builds
- "^forgejo-runner-"
Description: >-
kube-proxy: iptables (minikube). ts-*/ingress-*: network
namespace manipulation (Tailscale operator). forgejo-runner:
Docker-in-Docker for CI.
"core_seccomp_profile_docker_default":
Regions: ["*"]
Resources:
# Minikube system pods
- "^coredns-"
- "^kube-proxy-"
- "^kindnet-"
- "^storage-provisioner$"
# Tailscale operator-managed pods
- "^ts-"
- "^operator-"
- "^nameserver-"
- "^ingress-"
Description: >-
System pods (minikube) and Tailscale operator pods — seccomp
profiles set by upstream/operator, not user manifests.
"core_minimize_hostPID_containers":
Regions: ["*"]
Resources:
- "^prowler-"
Description: >-
Prowler CIS scanner requires hostPID to check file
permissions on kubelet and etcd data directories.
"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.
"core_minimize_containers_added_capabilities":
Regions: ["*"]
Resources:
# Minikube system pods
- "^coredns-"
- "^kindnet-"
# Grafana init-chown-data (CHOWN capability)
- "^grafana-"
Description: >-
System pods: NET_BIND_SERVICE/NET_RAW required by function
(minikube). Grafana: CHOWN for PVC init; all other
containers drop ALL.
"core_minimize_containers_capabilities_assigned":
Regions: ["*"]
Resources:
- "^coredns-"
- "^kindnet-"
- "^grafana-"
Description: >-
System pods (minikube) and Grafana init-chown-data.
See core_minimize_containers_added_capabilities.

View file

@ -0,0 +1,37 @@
# RBAC checks — built-in Kubernetes roles and operator roles that require
# broad permissions by design.
Mutelist:
Accounts:
"*":
Checks:
"rbac_minimize_wildcard_use_roles":
Regions: ["*"]
Resources:
# Built-in Kubernetes roles
- "^cluster-admin$"
- "^system:"
# ArgoCD — requires broad access for deployment management;
# ArgoCD itself is SSO-gated via Authentik
- "^argocd-"
Description: >-
Built-in K8s roles and ArgoCD. ArgoCD access is SSO-gated
via Authentik.
"rbac_minimize_pod_creation_access":
Regions: ["*"]
Resources:
# Built-in Kubernetes roles
- "^admin$"
- "^edit$"
- "^system:"
# CloudNativePG operator
- "^cnpg-manager$"
Description: >-
Built-in K8s roles required for workload controllers.
cnpg-manager: CloudNativePG operator manages PostgreSQL pods.
"rbac_minimize_service_account_token_creation":
Regions: ["*"]
Resources:
- "^system:"
Description: >-
kube-controller-manager requires token creation for service
account management. Built-in role.