feat(casan): establish assurance kernel and harden control plane

This commit is contained in:
thanhnv
2026-08-02 23:24:51 +07:00
parent 8b477f3800
commit 5745519126
51 changed files with 4076 additions and 180 deletions
+19 -10
View File
@@ -101,8 +101,10 @@ Goal workspace context:
a different actor. The executor verifies the artifact hash, applies it, runs fixed
project build/test commands, and reverses the patch if verification fails.
Local management headers: `x-casan-actor`, `x-casan-role`, `x-casan-project`,
`x-casan-tenant`. Missing role defaults to `viewer`, so writes fail closed.
In explicit loopback development mode only, local management headers are
`x-casan-actor`, `x-casan-role`, `x-casan-project`, and `x-casan-tenant`.
Missing role defaults to `viewer`, so writes fail closed. JWT mode discards
these caller assertions and derives them only from verified token claims.
Kill-switch management:
@@ -169,12 +171,19 @@ App root + telemetry paths resolve via the same marker walk-up as `casan-paths.s
compatibility aliases. Freshness is calculated independently from each file's mtime using
`CASAN_DASHBOARD_STALE_S` (default `3600`).
## Security posture (MVP)
Binds `127.0.0.1` by default. Refuses a non-loopback bind under `CASAN_PROFILE=prod` /
`CASAN_CP_STRICT=1` unless `CASAN_CP_TRUST_AUTH_PROXY=1` is set for an authenticated reverse
proxy that overwrites identity headers. Management endpoints are RBAC-gated via the harness
`rbac-check.py`; IdP group claims such as `casan-approver` are mapped to RBAC roles through
the same harness engine.
## Security posture
Binds `127.0.0.1` in explicit local development mode. Every non-loopback bind,
and every production profile, requires `CASAN_CP_AUTH_MODE=jwt`. Production
requires an RS256 public key plus configured issuer and audience; missing or
invalid configuration refuses startup. The API verifies signature, expiry,
issuer, audience, `nbf`/`iat`, and bounded clock skew in-process, then maps
verified group claims through `rbac-check.py`. Arbitrary `X-CASAN-*` and
forwarded-user headers are not an authentication mechanism.
Production variables are documented in `infra/production/runtime.env.example`.
The packaged boundary is oauth2-proxy → Nginx header stripping/bearer forwarding
→ API cryptographic verification. The current provider uses a mounted RS256
public key; automated JWKS discovery/rotation remains future work.
## Test
```bash
@@ -189,8 +198,8 @@ bash packages/casan-control-panel/scripts/local-prod-smoke.sh
The scaffold includes `Dockerfile.control-panel-api`, `Dockerfile.control-panel-ui`, and
`nginx/control-panel.conf`. Nginx protects UI/API through oauth2-proxy `auth_request`,
overwrites browser-supplied `X-CASAN-*` headers, and passes IdP group claims to the API for
RBAC mapping. The local smoke starts a self-signed HTTPS stack with a mock OIDC IdP and
removes browser-supplied identity headers, and forwards the signed bearer token for API
verification. The local smoke starts a self-signed HTTPS stack with a mock OIDC IdP and
expects `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin`; it also
asserts the Command Center returns all nine widgets with provenance envelopes and invokes
`managed-prod-smoke.sh` with the authenticated mock-IdP cookie jar. A passing local run