blumeops/argocd/manifests/prowler/cronjob.yaml
Erich Blume 4059b3d27b 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
2026-03-30 17:44:11 -07:00

88 lines
3.4 KiB
YAML

apiVersion: batch/v1
kind: CronJob
metadata:
name: prowler
namespace: prowler
spec:
schedule: "0 3 * * 0" # Sunday 3am
concurrencyPolicy: Forbid
jobTemplate:
spec:
ttlSecondsAfterFinished: 604800 # Auto-delete after 7 days
template:
spec:
serviceAccountName: prowler
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
command: ["/bin/sh", "-c"]
args:
- |
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
- name: mutelist-merged
mountPath: /tmp/mutelist
readOnly: true
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
readOnly: true
- name: etc-kubernetes
mountPath: /etc/kubernetes
readOnly: true
- name: var-lib-etcd
mountPath: /var/lib/etcd
readOnly: true
hostPID: true
restartPolicy: OnFailure
volumes:
- 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
- name: etc-kubernetes
hostPath:
path: /etc/kubernetes
- name: var-lib-etcd
hostPath:
path: /var/lib/etcd