# 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:///oauth2/callback` with the enterprise IdP. Map the `groups` claim to CASAN groups such as `casan-org-admin` and `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 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 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 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, 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. 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 ``` ## Backup and restore drill Back up CASAN runtime state using `state-backup.sh`. In production the command requires `CASAN_BACKUP_PASSPHRASE_FILE`; store that file via the customer secret manager, not in the repository. Restore refuses a non-empty target directory, so a drill is non-destructive by default. ```bash export CASAN_PROFILE=prod export CASAN_BACKUP_PASSPHRASE_FILE=/run/secrets/casan-backup-passphrase export CASAN_BACKUP_ROOT=/srv/casan/backups bash packages/casan-harness/scripts/bash/state-backup.sh create "$(date -u +%Y%m%dT%H%M%SZ)" bash packages/casan-harness/scripts/bash/state-backup.sh verify "" bash packages/casan-harness/scripts/bash/state-backup.sh restore "" --to /srv/casan/restore-drill ``` Perform and retain a restore drill before enabling write actions. A production runbook must define retention, off-site replication, an owner, RPO/RTO targets, and approval for any restore into a live state directory. ## Hardened runner requirement Production sandbox execution requires a **rootless Docker daemon** plus a non-root container process (`65532:65532` by default). CASAN rejects a rootful daemon when `CASAN_PROFILE=prod`; the workload has no network, read-only root filesystem, no Linux capabilities, no-new-privileges, bounded PID/memory/CPU, and only its explicit workspace bind mount. Provision the workspace so the non-root runner identity can write it.