feat(vuln): vuln-scanner platform manifests, Flux tier, alerts, L0/L2/L3 tests
Hand-authored platform/vuln-scanner (non-gating Flux tier, digest-pinned Trivy 0.76.1 and python 3.14.9-alpine, read-only RBAC, per-pod oauth2-proxy, internal-only cve.<domain>), five alerts with promtool tests, R-VULN-01..09 registry entries, and vuln-* L0 checks with mutation self-tests. src/ and the bot workflow are integrated separately. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4c3a284cad
commit
b85d1e3ed7
47 files changed
+2572
-6
No files matched your search
@@ -0,0 +1,80 @@
|
||||
# vuln-scanner — scheduled CVE visibility for what is running
|
||||
|
||||
Answers three questions with no manual step: what is running, what is it vulnerable to, and can the
|
||||
answer be trusted. Contract: spec `docs/17-vulnerability-visibility.md`, ADR-0027 (R-VULN-01..09).
|
||||
Hand-authored platform tooling (not a catalog service), reconciled by its own non-gating Flux tier
|
||||
`clusters/base/vuln-scanner.yaml`. Operator runbook: `runbooks/vuln-scanner.md`.
|
||||
|
||||
| Piece | Where |
|
||||
|---|---|
|
||||
| Scanner source (`python3`, stdlib only), mounted from a ConfigMap at `/app` | `src/` |
|
||||
| Accepted risk (R-VULN-08) | `acceptances.yaml` |
|
||||
| Pod, security context, sidecar | `deployment.yaml` |
|
||||
| Read-only identity (R-VULN-09) | `rbac.yaml` |
|
||||
| Network: Gateway→4180, CI→8081, Prometheus→9090; API, DNS, public 443 | `networkpolicy.yaml` |
|
||||
| Aggregate metrics and the five alerts | `servicemonitor.yaml`, `alerts.yaml`, `alerts.promtool-test.yaml` |
|
||||
|
||||
The page is `cve.<domain>`, internal only, gated by the per-pod oauth2-proxy sidecar to `platform-admins`.
|
||||
The route is disowned from public DNS and the tunnel 404s it; PiHole answers it on the LAN.
|
||||
|
||||
## Operator step: add the Keycloak client (NOT done by the change that added this tree)
|
||||
|
||||
This change adds everything around the Keycloak client but not the client. Adding it needs a new key in
|
||||
`platform/keycloak/keycloak-realm-clients.sops.yaml`, an encrypted file an agent must not decrypt; the
|
||||
L0 check `keycloak-client-secret-reconciler` then requires the client and that key to appear in the
|
||||
same change. Until this is done the page cannot complete a login (`invalid_client`, fail-closed); the
|
||||
scanner, its metrics and its alerts are unaffected.
|
||||
|
||||
In one PR (the realm import does not re-run on its own for a NEW client):
|
||||
|
||||
1. Generate one value and set it in both encrypted files, through pipes so it is never echoed:
|
||||
|
||||
```bash
|
||||
./infra/devcontainer/dev.sh run bash -c '
|
||||
cs=$(openssl rand -hex 32)
|
||||
sops set platform/keycloak/keycloak-realm-clients.sops.yaml \
|
||||
"[\"stringData\"][\"vuln-scanner-client-secret\"]" "\"$cs\""
|
||||
sops set platform/vuln-scanner/vuln-scanner-oauth2-proxy-credentials.sops.yaml \
|
||||
"[\"stringData\"][\"client-secret\"]" "\"$cs\""'
|
||||
```
|
||||
|
||||
2. Add to `platform/keycloak/keycloak-realm-import.yaml`, beside the `crafty` client:
|
||||
|
||||
```yaml
|
||||
# spec.placeholders
|
||||
VULN_SCANNER_CLIENT_SECRET:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: vuln-scanner-client-secret
|
||||
# spec.realm.clients
|
||||
- clientId: vuln-scanner
|
||||
name: CVE report (oauth2-proxy)
|
||||
protocol: openid-connect
|
||||
enabled: true
|
||||
publicClient: false
|
||||
standardFlowEnabled: true
|
||||
directAccessGrantsEnabled: false
|
||||
secret: "$(env:VULN_SCANNER_CLIENT_SECRET)"
|
||||
redirectUris:
|
||||
- https://cve.${BASE_DOMAIN}/oauth2/callback
|
||||
webOrigins:
|
||||
- https://cve.${BASE_DOMAIN}
|
||||
defaultClientScopes: [email, profile, roles]
|
||||
protocolMappers:
|
||||
- name: groups
|
||||
protocol: openid-connect
|
||||
protocolMapper: oidc-group-membership-mapper
|
||||
config:
|
||||
"full.path": "false"
|
||||
"id.token.claim": "true"
|
||||
"access.token.claim": "true"
|
||||
"userinfo.token.claim": "true"
|
||||
"claim.name": groups
|
||||
```
|
||||
|
||||
3. Regenerate the derived URL file: `python3 ci/render-keycloak-client-urls.py --write`. Commit all
|
||||
three, gate, merge.
|
||||
|
||||
4. After merge, the client must also exist in the live realm (a completed realm import does not re-run,
|
||||
per `platform/keycloak/README.md`, so create it once with `kcadm` or re-trigger the import). The
|
||||
client-secret reconciler then converges its secret and URLs.
|
||||
Reference in new issue
Block a user