feat(casan): establish assurance kernel and harden control plane
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# CASAN Control Plane
|
||||
|
||||
Status: production-capable packaged authentication and trust configuration;
|
||||
external infrastructure remains operator-provisioned.
|
||||
|
||||
## Deployment modes
|
||||
|
||||
### Local development
|
||||
|
||||
- Default bind: `127.0.0.1`.
|
||||
- `CASAN_CP_AUTH_MODE=local` is allowed only on loopback and outside production.
|
||||
- Local actor headers are explicitly development identity and are not a
|
||||
production authentication claim.
|
||||
|
||||
### Networked or production
|
||||
|
||||
- `CASAN_CP_AUTH_MODE=jwt` is mandatory.
|
||||
- Production requires RS256 verification, a mounted public key, exact issuer
|
||||
and audience, and a clock skew from 0 through 300 seconds.
|
||||
- Missing or invalid configuration refuses startup before NestJS listens.
|
||||
- HS256 is retained only for deterministic non-production tests.
|
||||
|
||||
## Packaged identity boundary
|
||||
|
||||
```text
|
||||
Browser
|
||||
-> enterprise OIDC
|
||||
-> oauth2-proxy session
|
||||
-> Nginx auth_request
|
||||
removes X-CASAN-* and forwarded identity headers
|
||||
forwards signed Bearer access token
|
||||
-> NestJS AuthProvider
|
||||
verifies RS256 signature, iss, aud, sub, exp, nbf and iat
|
||||
-> VerifiedClaims
|
||||
-> CASAN RBAC role mapping and scoped request identity
|
||||
```
|
||||
|
||||
Nginx is a TLS/session boundary, not the identity authority for the API. The
|
||||
API ignores caller-supplied identity assertions and derives actor, role,
|
||||
tenant and project only after token verification.
|
||||
|
||||
## Authentication implementation
|
||||
|
||||
`backend/src/common/auth-provider.ts` defines:
|
||||
|
||||
- `AuthProvider` and `JwtAuthProvider`.
|
||||
- Typed `VerifiedClaims` and `AuthenticationDecision`.
|
||||
- RS256/HS256 signature verification using Node's standard crypto APIs.
|
||||
- Issuer, audience, expiry, not-before, issued-at and bounded-skew checks.
|
||||
- Safe tenant/project syntax validation.
|
||||
- Structured authentication audit evidence without raw tokens.
|
||||
- Middleware that overwrites request identity only from verified claims.
|
||||
|
||||
`auth-context.ts` refuses direct header use in JWT mode unless the middleware
|
||||
has marked the identity verified. Existing RBAC services then map verified
|
||||
groups and continue to produce governance/authorization evidence.
|
||||
|
||||
## Production configuration
|
||||
|
||||
Required runtime variables:
|
||||
|
||||
```text
|
||||
CASAN_PROFILE=prod
|
||||
CASAN_CP_AUTH_MODE=jwt
|
||||
CASAN_CP_JWT_ISSUER=https://...
|
||||
CASAN_CP_JWT_AUDIENCE=...
|
||||
CASAN_CP_JWT_PUBLIC_KEY_FILE=/run/casan-idp/idp-public.pem
|
||||
CASAN_CP_JWT_ROLE_CLAIM=groups
|
||||
CASAN_CP_JWT_TENANT_CLAIM=casan_tenant
|
||||
CASAN_CP_JWT_PROJECT_CLAIM=casan_project
|
||||
CASAN_CP_JWT_CLOCK_SKEW_SECONDS=60
|
||||
```
|
||||
|
||||
The host path is configured as `CASAN_CP_IDP_PUBLIC_KEY` and mounted read-only.
|
||||
oauth2-proxy must set xauthrequest output and pass the access token. Images for
|
||||
API, UI and oauth2-proxy must be digest-pinned.
|
||||
|
||||
`production-preflight.sh` validates:
|
||||
|
||||
- TLS hostname, expiry and matching private key.
|
||||
- IdP public key readability and RSA public-key format.
|
||||
- HTTPS OIDC issuer, secure cookies and token forwarding.
|
||||
- Matching oauth2-proxy client ID and API audience.
|
||||
- Matching issuer and bounded JWT clock skew.
|
||||
- External Vault/KMS signing and S3 Object Lock provider selection.
|
||||
- Live short-lived non-root Vault token lookup.
|
||||
- A real Object Lock COMPLIANCE anchor write.
|
||||
- Digest-pinned images and valid Compose/Nginx configuration.
|
||||
|
||||
## Authentication evidence
|
||||
|
||||
Each production authentication decision appends a JSONL record under
|
||||
`CASAN_STATE_ROOT/logs/auth/decisions.jsonl` with category, policy, decision,
|
||||
reason, actor, issuer, scope and request method/path. Tokens and secrets are
|
||||
never written. A failure to authenticate returns HTTP 401 with a stable reason
|
||||
code; it is not converted to viewer access.
|
||||
|
||||
## Local production-like smoke
|
||||
|
||||
`docker-compose.control-panel.local.yml` uses the same bearer-token path. The
|
||||
smoke script generates an ephemeral RSA keypair, mounts the private key into
|
||||
the mock IdP and the public key into the API, and enables token forwarding.
|
||||
This validates the boundary without treating the mock IdP as production.
|
||||
|
||||
## Residual limitations
|
||||
|
||||
- Public-key rotation currently requires replacing the mounted key and
|
||||
restarting the API; automated JWKS discovery and rollover are not present.
|
||||
- Enterprise IdP, managed TLS, Vault and S3 are not provisioned by the repo.
|
||||
- Local tests validate crypto and provider contracts, but do not prove a real
|
||||
tenant's claim mapping, token lifecycle, network policy or key rotation.
|
||||
- The Control Plane is production-capable for this packaged deployment model;
|
||||
it is not yet a universal multi-runtime scheduler or supervisor.
|
||||
Reference in New Issue
Block a user