## 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
73 lines
2.7 KiB
YAML
73 lines
2.7 KiB
YAML
---
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: prowler-image-scan
|
|
namespace: prowler
|
|
spec:
|
|
schedule: "0 3 * * 6" # Saturday 3am
|
|
concurrencyPolicy: Forbid
|
|
jobTemplate:
|
|
spec:
|
|
ttlSecondsAfterFinished: 604800 # Auto-delete after 7 days
|
|
template:
|
|
spec:
|
|
securityContext:
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
initContainers:
|
|
# Workaround: Prowler's --registry flag is broken (registry args
|
|
# not passed to provider constructor). Generate image list from
|
|
# zot catalog API instead.
|
|
# See: https://github.com/prowler-cloud/prowler/issues/10457
|
|
- name: enumerate-images
|
|
image: registry.ops.eblu.me/blumeops/prowler:kustomized
|
|
command: ["python3", "-c"]
|
|
args:
|
|
- |
|
|
import json, urllib.request
|
|
|
|
REGISTRY = "https://registry.ops.eblu.me"
|
|
catalog = json.loads(urllib.request.urlopen(f"{REGISTRY}/v2/_catalog").read())
|
|
images = []
|
|
for repo in catalog["repositories"]:
|
|
if not repo.startswith("blumeops/"):
|
|
continue
|
|
tags = json.loads(urllib.request.urlopen(f"{REGISTRY}/v2/{repo}/tags/list").read())
|
|
for tag in tags.get("tags") or []:
|
|
images.append(f"registry.ops.eblu.me/{repo}:{tag}")
|
|
|
|
with open("/shared/images.txt", "w") as f:
|
|
f.write("\n".join(images) + "\n")
|
|
print(f"Discovered {len(images)} images")
|
|
for img in images:
|
|
print(img)
|
|
volumeMounts:
|
|
- name: shared
|
|
mountPath: /shared
|
|
containers:
|
|
- name: prowler
|
|
image: registry.ops.eblu.me/blumeops/prowler:kustomized
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
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
|
|
- name: shared
|
|
mountPath: /shared
|
|
readOnly: true
|
|
restartPolicy: OnFailure
|
|
volumes:
|
|
- name: reports
|
|
persistentVolumeClaim:
|
|
claimName: prowler-reports
|
|
- name: shared
|
|
emptyDir: {}
|