- src/lib/rate-limit.ts (Redis sliding window, fail-open) - before-hook on /sign-in/email: 5 attempts/min per IP+email - worker job audit-archive (daily 03:30, JSONL → MinIO, 90d retention) - worker job panel-backup (daily 04:00, pg_dump -Fc -Z9 → MinIO) - Dockerfile adds postgresql16-client - scripts/restore-drill.sh restores latest dump into panel_drill - docs/disaster-recovery.md + docs/phase4-deferred.md (mTLS + Infisical rationale)
3.0 KiB
Phase 4 deferred: mTLS + Infisical
The PRD lists mTLS (Layer 2 admin API hardening) and Infisical (secret manager) as Phase 4 work. Both are deliberately deferred. Reasons + trigger criteria below.
mTLS for /internal/admin/*
Why deferred: no spoke currently exposes /internal/admin/*. Layer 2 is wire-ready (AdminClient with X-Internal-Token) but unused. Standing up a CA, distributing client certs, and adding mTLS verification on every spoke before any spoke actually has admin endpoints is premature engineering — we'd ship infrastructure for traffic that doesn't exist.
Threat model today: spokes have no admin endpoints. There is no Layer 2 traffic to intercept. Tailscale already gates network access to panel-web; the panel cannot be reached from the public internet at all.
Trigger to revisit:
- Second spoke ships
/internal/admin/*(i.e. we have 2+ spokes calling admin endpoints). - OR token rotation becomes a chore (manual env updates start drifting).
- OR a regulatory requirement appears (KVKK audit, customer SOC2 ask, etc.).
When triggered, do:
- Generate a self-signed CA (long-lived) and per-spoke client certs (1-year, rotated by cron).
- Add
caCert/clientCert/clientKeyoptions toAdminClient(already structured for it). - Spoke-side: configure their gateway (Traefik/Caddy/Nginx) to require client cert on
/internal/admin/*. - Keep
X-Internal-Tokenas defense-in-depth (mTLS authenticates client machine, token authenticates application).
Infisical self-hosted
Why deferred: no Infisical instance running in this Coolify. Adding it requires (a) deploying Infisical, (b) creating projects + service tokens, (c) installing the agent or SDK in panel-web + panel-worker, (d) migrating ~12 env vars. That's a weekend of work for marginal benefit at this scale — Coolify already encrypts env at rest and audits writes.
Trigger to revisit:
- Need to share secrets across multiple machines (right now everything's on
maestro). - Need a secret-rotation cadence (e.g. quarterly rotation of
BETTER_AUTH_SECRET). - Need versioned, time-windowed secrets (preview environments with their own credentials).
- Need an audit trail of who looked at a secret (Coolify only audits writes).
When triggered, do:
- Deploy Infisical via Coolify (1 web + 1 db).
- Create project
super-panelwith envsproductionandstaging. - Install
@infisical/sdkin bothapps/webandapps/worker, swapprocess.env.Xreads through the SDK with cache. - Remove the corresponding Coolify env vars (one at a time; verify on each).
- Add CI gate: fail PRs that introduce raw
process.env.<SECRET>access for known-sensitive keys.
What we do have today (Phase 4 actually delivered)
- Rate limit on
/sign-in/email(5/min per IP+email, Redis-backed, fail-open). - Daily audit archive to MinIO (90-day retention).
- Daily
pg_dumpof panel-postgres to MinIO (panel-backups/). - Restore drill script (
scripts/restore-drill.sh) — monthly cadence target. - DR runbook (
docs/disaster-recovery.md).