feat(casan): establish assurance kernel and harden control plane
This commit is contained in:
@@ -10,6 +10,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from urllib.parse import parse_qs, urlencode, urlparse
|
||||
|
||||
import jwt
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
|
||||
@@ -33,7 +34,12 @@ USERS = {
|
||||
},
|
||||
}
|
||||
KID = "casan-local-prod-idp"
|
||||
KEY = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
KEY_FILE = os.environ.get("CASAN_IDP_PRIVATE_KEY_FILE", "")
|
||||
if KEY_FILE:
|
||||
with open(KEY_FILE, "rb") as key_handle:
|
||||
KEY = serialization.load_pem_private_key(key_handle.read(), password=None)
|
||||
else:
|
||||
KEY = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
CODES = {}
|
||||
APPROVAL_SIGNER_TOKEN = os.environ.get("CASAN_APPROVAL_SIGNER_TOKEN", "")
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ not be invented or committed.
|
||||
it at `/opt/casan-control-panel/tls/tls.crt` and `tls.key` (mode `0600`).
|
||||
2. Register `https://<fqdn>/oauth2/callback` with the enterprise IdP. Map the
|
||||
`groups` claim to CASAN groups such as `casan-org-admin` and
|
||||
`casan-approver`. Copy `oauth2-proxy.env.example` outside the repo and fill
|
||||
it using the secret manager.
|
||||
`casan-approver`. Export the IdP's RS256 verification public key to
|
||||
`/opt/casan-control-panel/idp-public.pem`; rotation requires replacing this
|
||||
file and restarting the API. Copy `oauth2-proxy.env.example` outside the
|
||||
repo and fill it using the secret manager.
|
||||
3. Create a Vault Transit key `casan-audit-key` with `exportable=false`; give a
|
||||
workload identity only `transit/sign/casan-audit-key` and read-key metadata.
|
||||
Render a short-lived token into `vault.env` outside Git. Never use Vault dev
|
||||
@@ -35,6 +37,7 @@ sudo install -d -m 0700 /opt/casan-control-panel/tls /srv/casan/state /srv/casan
|
||||
sudo install -m 0600 /dev/null /opt/casan-control-panel/oauth2-proxy.env
|
||||
sudo install -m 0600 /dev/null /opt/casan-control-panel/runtime.env
|
||||
sudo install -m 0600 /dev/null /opt/casan-control-panel/vault.env
|
||||
sudo install -m 0644 /secure/export/idp-public.pem /opt/casan-control-panel/idp-public.pem
|
||||
|
||||
bash packages/casan-harness/scripts/bash/production-preflight.sh infra/production/casan-prod.env
|
||||
set -a; source infra/production/casan-prod.env; set +a
|
||||
@@ -43,9 +46,15 @@ docker compose -f docker-compose.control-panel.yml up -d
|
||||
|
||||
The preflight performs a real Object Lock anchor write. It will reject local
|
||||
URLs, image tags, expired/near-expiry TLS, dev/root Vault tokens, incomplete
|
||||
OIDC, missing paths, and buckets without Object Lock. After login, run
|
||||
OIDC, issuer/audience mismatches, missing IdP keys, local trust providers,
|
||||
missing paths, and buckets without Object Lock. After login, run
|
||||
`managed-prod-smoke.sh` with an authenticated cookie jar.
|
||||
|
||||
The API does not trust `X-CASAN-*` or forwarded-user headers from Nginx.
|
||||
oauth2-proxy returns the signed access token, Nginx removes caller-supplied
|
||||
identity headers, and the API verifies RS256 signature, issuer, audience,
|
||||
expiry and clock bounds before deriving actor, role, tenant and project.
|
||||
|
||||
## Operational anchors
|
||||
|
||||
Run `audit-ship-s3.sh` after every audit seal from the same workload identity.
|
||||
|
||||
@@ -9,10 +9,12 @@ CASAN_CP_RUNTIME_ENV=/opt/casan-control-panel/runtime.env
|
||||
CASAN_CP_VAULT_ENV=/opt/casan-control-panel/vault.env
|
||||
CASAN_CP_STATE_DIR=/srv/casan/state
|
||||
CASAN_CP_OUTPUT_DIR=/srv/casan/output
|
||||
CASAN_CP_IDP_PUBLIC_KEY=/opt/casan-control-panel/idp-public.pem
|
||||
|
||||
# CI must publish immutable image references, not tags such as :latest.
|
||||
CASAN_CP_API_IMAGE=registry.example.internal/casan/control-panel-api@sha256:replace-with-64-hex-digest
|
||||
CASAN_CP_UI_IMAGE=registry.example.internal/casan/control-panel-ui@sha256:replace-with-64-hex-digest
|
||||
CASAN_CP_OAUTH2_PROXY_IMAGE=quay.io/oauth2-proxy/oauth2-proxy@sha256:replace-with-64-hex-digest
|
||||
|
||||
# WORM anchor destination. The deploy identity needs only PutObject and
|
||||
# GetObjectLockConfiguration for this bucket/prefix; use workload identity,
|
||||
|
||||
@@ -8,6 +8,6 @@ OAUTH2_PROXY_COOKIE_SECURE=true
|
||||
OAUTH2_PROXY_REDIRECT_URL=https://control.casan.company.internal/oauth2/callback
|
||||
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=groups
|
||||
OAUTH2_PROXY_SET_XAUTHREQUEST=true
|
||||
OAUTH2_PROXY_PASS_ACCESS_TOKEN=false
|
||||
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=false
|
||||
OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
|
||||
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
|
||||
OAUTH2_PROXY_SCOPE="openid profile email groups"
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
# companion Vault file and the production preflight validation.
|
||||
CASAN_PROFILE=prod
|
||||
CASAN_CP_STRICT=1
|
||||
CASAN_CP_TRUST_AUTH_PROXY=1
|
||||
CASAN_CP_AUTH_MODE=jwt
|
||||
CASAN_CP_JWT_ISSUER=https://id.example.internal/realms/casan
|
||||
CASAN_CP_JWT_AUDIENCE=casan-control-plane
|
||||
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
|
||||
CP_BIND=0.0.0.0
|
||||
CP_PORT=3010
|
||||
CASAN_APP_ROOT=/app
|
||||
CASAN_DASHBOARD_STALE_S=300
|
||||
CASAN_PREFLIGHT=1
|
||||
CASAN_CHAT_MODEL_MODE=deterministic
|
||||
CASAN_SIGNING_PROVIDER=vault_kms
|
||||
CASAN_IMMUTABLE_ANCHOR_PROVIDER=s3_object_lock
|
||||
|
||||
Reference in New Issue
Block a user