feat: add production deployment handoff controls
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# CASAN Linux Production Handoff
|
||||
|
||||
This directory turns the local production-like lab into a deployment that
|
||||
fails closed. It is deliberately parameterized: a real DNS name, enterprise
|
||||
OIDC tenant, Vault policy/token and S3 bucket are tenant-owned secrets and must
|
||||
not be invented or committed.
|
||||
|
||||
## One-time infrastructure
|
||||
|
||||
1. Create the DNS record for `CASAN_PUBLIC_FQDN` to the Linux host and allow
|
||||
TCP 443 only. Obtain a certificate whose SAN includes that exact name; place
|
||||
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.
|
||||
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
|
||||
mode or the `root` token.
|
||||
4. Create a versioned S3 bucket with Object Lock enabled at creation time.
|
||||
Require default COMPLIANCE retention, SSE-KMS with `CASAN_S3_KMS_KEY_ID` and deny delete/retention
|
||||
bypass. The runtime identity requires `s3:PutObject` plus
|
||||
`s3:GetObjectLockConfiguration`, scoped to the audit prefix.
|
||||
5. Let CI publish the API/UI as immutable `@sha256` images, attest those
|
||||
artifacts and put the resulting digests in `casan-prod.env`.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
cp infra/production/casan-prod.env.example infra/production/casan-prod.env
|
||||
# Fill non-secret paths, FQDN, image digests, S3 bucket and region.
|
||||
|
||||
sudo install -d -m 0700 /opt/casan-control-panel/tls /srv/casan/state /srv/casan/output
|
||||
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
|
||||
|
||||
bash packages/casan-harness/scripts/bash/production-preflight.sh infra/production/casan-prod.env
|
||||
set -a; source infra/production/casan-prod.env; set +a
|
||||
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
|
||||
`managed-prod-smoke.sh` with an authenticated cookie jar.
|
||||
|
||||
## Operational anchors
|
||||
|
||||
Run `audit-ship-s3.sh` after every audit seal from the same workload identity.
|
||||
Use a systemd timer or an orchestrated job; it produces a unique immutable key
|
||||
per audit head and asks S3 for COMPLIANCE retention on every write.
|
||||
|
||||
Install the included timer after the first successful preflight (the runtime
|
||||
identity must also have `s3:GetObject` so retries can be idempotent):
|
||||
|
||||
```bash
|
||||
sudo install -D -m 0644 infra/production/systemd/casan-audit-anchor.service /etc/systemd/system/casan-audit-anchor.service
|
||||
sudo install -D -m 0644 infra/production/systemd/casan-audit-anchor.timer /etc/systemd/system/casan-audit-anchor.timer
|
||||
sudo install -D -m 0600 infra/production/casan-prod.env /etc/casan/production.env
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now casan-audit-anchor.timer
|
||||
systemctl list-timers casan-audit-anchor.timer
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# Public control-plane endpoint. Do not use localhost, an IP address, or example.com.
|
||||
CASAN_PUBLIC_FQDN=control.casan.company.internal
|
||||
CASAN_CP_HTTPS_PORT=443
|
||||
|
||||
# Host paths. These are provisioned outside the repository with restrictive permissions.
|
||||
CASAN_CP_TLS_DIR=/opt/casan-control-panel/tls
|
||||
CASAN_CP_OAUTH_ENV=/opt/casan-control-panel/oauth2-proxy.env
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# WORM anchor destination. The deploy identity needs only PutObject and
|
||||
# GetObjectLockConfiguration for this bucket/prefix; use workload identity,
|
||||
# never static credentials in this file.
|
||||
CASAN_S3_BUCKET=casan-production-audit
|
||||
CASAN_S3_PREFIX=audit-anchors
|
||||
CASAN_S3_REGION=ap-northeast-1
|
||||
CASAN_S3_RETENTION_DAYS=365
|
||||
CASAN_S3_KMS_KEY_ID=alias/casan-audit
|
||||
@@ -0,0 +1,13 @@
|
||||
# Enterprise OIDC values. Keep the real file at /opt/casan-control-panel with chmod 0600.
|
||||
OAUTH2_PROXY_PROVIDER=oidc
|
||||
OAUTH2_PROXY_OIDC_ISSUER_URL=https://id.example.internal/realms/casan
|
||||
OAUTH2_PROXY_CLIENT_ID=casan-control-panel
|
||||
OAUTH2_PROXY_CLIENT_SECRET=replace-from-secret-manager
|
||||
OAUTH2_PROXY_COOKIE_SECRET=replace-with-32-byte-base64-secret
|
||||
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_SCOPE="openid profile email groups"
|
||||
@@ -0,0 +1,11 @@
|
||||
# Non-secret runtime configuration. The service refuses to run without the
|
||||
# companion Vault file and the production preflight validation.
|
||||
CASAN_PROFILE=prod
|
||||
CASAN_CP_STRICT=1
|
||||
CASAN_CP_TRUST_AUTH_PROXY=1
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=CASAN immutable audit-head anchor
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=casan
|
||||
Group=casan
|
||||
EnvironmentFile=/etc/casan/production.env
|
||||
Environment=CASAN_PROFILE=prod
|
||||
ExecStart=/srv/casan/repo/packages/casan-harness/scripts/bash/audit-ship-s3.sh /srv/casan/state/logs/audit/audit-head.txt
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/srv/casan/state
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Periodically anchor CASAN audit head to S3 Object Lock
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=5min
|
||||
Persistent=true
|
||||
Unit=casan-audit-anchor.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,4 @@
|
||||
# Store the real token through a workload identity/agent rendered file. Never commit it.
|
||||
VAULT_ADDR=https://vault.service.consul:8200
|
||||
VAULT_TOKEN=replace-with-short-lived-transit-policy-token
|
||||
VAULT_CACERT=/run/secrets/vault-ca.pem
|
||||
Reference in New Issue
Block a user