From 13bcb36ebf5555dd8c09eb5da556970edc0daed5 Mon Sep 17 00:00:00 2001 From: Mikko Numminen Date: Fri, 24 Jul 2026 08:25:26 +0300 Subject: [PATCH] fix(k8s): use the env names the app actually reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifests and Helm chart predate the NextAuth v5 migration and inject env verbatim (envFrom), so a deploy from these templates got: - broken sign-in: they set NEXTAUTH_SECRET, but @auth/core reads only AUTH_SECRET (verified in @auth/core/lib/utils/env.js — no legacy fallback); v5 in production throws MissingSecret - dead OAuth credentials: GOOGLE_CLIENT_* / GITHUB_CLIENT_* instead of the AUTH_GOOGLE_* / AUTH_GITHUB_* names v5 infers for bare provider imports - a silently disabled audit trail: MONGODB_URI, while the app reads MONGODB_URL (isMongoAvailable() false -> deferAudit no-ops) - no AUDIT_HMAC_SECRET or TOTP_ENCRYPTION_KEY, both of which fail closed in production - UntrustedHost behind the ingress: nothing set AUTH_TRUST_HOST and auth.ts does not set trustHost Rename every key to what src/ actually consumes, add the two fail-closed secrets, AUTH_TRUST_HOST, and an optional commented CRON_SECRET; drop the unread MONGO_DB_NAME; NEXTAUTH_URL becomes AUTH_URL (the legacy name still works for the URL, unlike the secret — kept consistent anyway). k8s/README examples updated to match. YAML validated by parse; key sets in the raw manifests and the Helm values are identical. Co-Authored-By: Claude Fable 5 --- k8s/README.md | 21 +++++++++++------- k8s/helm/hrmanager/values.yaml | 29 ++++++++++++++++-------- k8s/manifests/configmap.yaml | 9 +++++--- k8s/manifests/secret.yaml | 40 ++++++++++++++++++++++++---------- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/k8s/README.md b/k8s/README.md index 7431e0a..10099d6 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -77,7 +77,7 @@ Paste the output into the corresponding field in `secret.yaml`. ### 2. Update the ConfigMap -Edit `k8s/manifests/configmap.yaml` and set `NEXTAUTH_URL` to your actual domain. +Edit `k8s/manifests/configmap.yaml` and set `AUTH_URL` to your actual domain. ### 3. Set your container image @@ -142,15 +142,20 @@ Create a file **outside** your git repository (e.g., `~/hrmanager-secrets.yaml`) secrets: data: DATABASE_URL: "postgresql://user:pass@host:5432/hrmanager" - NEXTAUTH_SECRET: "your-nextauth-secret-min-32-chars" - GOOGLE_CLIENT_ID: "your-google-client-id" - GOOGLE_CLIENT_SECRET: "your-google-client-secret" - GITHUB_CLIENT_ID: "your-github-client-id" - GITHUB_CLIENT_SECRET: "your-github-client-secret" - MONGODB_URI: "mongodb://user:pass@host:27017/hrmanager_audit" + # Auth.js v5 reads AUTH_SECRET — the legacy NEXTAUTH_SECRET is not read. + AUTH_SECRET: "your-auth-secret-min-32-chars" + # Both fail closed in production; generate with: openssl rand -base64 32 + AUDIT_HMAC_SECRET: "your-audit-hmac-secret" + TOTP_ENCRYPTION_KEY: "your-totp-encryption-key" + AUTH_GOOGLE_ID: "your-google-client-id" + AUTH_GOOGLE_SECRET: "your-google-client-secret" + AUTH_GITHUB_ID: "your-github-client-id" + AUTH_GITHUB_SECRET: "your-github-client-secret" + # The app reads MONGODB_URL — with any other name the audit trail no-ops. + MONGODB_URL: "mongodb://user:pass@host:27017/hrmanager_audit" config: - NEXTAUTH_URL: "https://your-domain.com" + AUTH_URL: "https://your-domain.com" ingress: host: your-domain.com diff --git a/k8s/helm/hrmanager/values.yaml b/k8s/helm/hrmanager/values.yaml index 9568c88..5cddd83 100644 --- a/k8s/helm/hrmanager/values.yaml +++ b/k8s/helm/hrmanager/values.yaml @@ -108,8 +108,11 @@ affinity: {} # -- Application environment variables (non-sensitive) config: NODE_ENV: "production" - NEXTAUTH_URL: "https://hrmanager.example.com" - MONGO_DB_NAME: "hrmanager_audit" + # Auth.js v5 canonical name (legacy NEXTAUTH_URL also works). + AUTH_URL: "https://hrmanager.example.com" + # Required behind the ingress — Auth.js v5 rejects proxied Host headers + # with UntrustedHost otherwise. + AUTH_TRUST_HOST: "true" NEXT_TELEMETRY_DISABLED: "1" # -- Sensitive environment variables. @@ -120,14 +123,22 @@ secrets: # If set, the 'data' block below is ignored. existingSecret: "" data: - # Replace all placeholder values with actual base64-encoded secrets before deploying. + # Key names match exactly what the app reads (injected verbatim via envFrom) + # — see .env.example at the repo root for the authoritative list. DATABASE_URL: "" - NEXTAUTH_SECRET: "" - GOOGLE_CLIENT_ID: "" - GOOGLE_CLIENT_SECRET: "" - GITHUB_CLIENT_ID: "" - GITHUB_CLIENT_SECRET: "" - MONGODB_URI: "" + # Auth.js v5 session secret (v5 does not read the legacy NEXTAUTH_SECRET). + AUTH_SECRET: "" + # Fail-closed in production: audit hash chain + 2FA secret encryption. + AUDIT_HMAC_SECRET: "" + TOTP_ENCRYPTION_KEY: "" + AUTH_GOOGLE_ID: "" + AUTH_GOOGLE_SECRET: "" + AUTH_GITHUB_ID: "" + AUTH_GITHUB_SECRET: "" + # The app reads MONGODB_URL — with any other name the audit trail no-ops. + MONGODB_URL: "" + # Optional: gates /api/cron/* if scheduled via CronJob/external scheduler. + # CRON_SECRET: "" # -- Liveness probe configuration livenessProbe: diff --git a/k8s/manifests/configmap.yaml b/k8s/manifests/configmap.yaml index 6880275..eb7b4d5 100644 --- a/k8s/manifests/configmap.yaml +++ b/k8s/manifests/configmap.yaml @@ -9,8 +9,11 @@ metadata: app.kubernetes.io/managed-by: kubectl data: NODE_ENV: "production" - NEXTAUTH_URL: "https://hrmanager.example.com" - # MongoDB connection (non-sensitive host/db part — put full URI in secret) - MONGO_DB_NAME: "hrmanager_audit" + # Auth.js v5 canonical name (the legacy NEXTAUTH_URL still works, but use the + # name the rest of the config uses). + AUTH_URL: "https://hrmanager.example.com" + # Behind the ingress the Host header is proxied — without this Auth.js v5 + # rejects requests with UntrustedHost. + AUTH_TRUST_HOST: "true" # Next.js performance tuning NEXT_TELEMETRY_DISABLED: "1" diff --git a/k8s/manifests/secret.yaml b/k8s/manifests/secret.yaml index d99a448..bd9fe67 100644 --- a/k8s/manifests/secret.yaml +++ b/k8s/manifests/secret.yaml @@ -10,27 +10,45 @@ metadata: type: Opaque # All values must be base64-encoded. Example: echo -n 'myvalue' | base64 # Replace every placeholder below with your actual base64-encoded value before applying. +# +# Key names match exactly what the app reads (they are injected verbatim via +# envFrom) — see .env.example at the repo root for the authoritative list. data: # PostgreSQL connection string # Replace with: echo -n 'postgresql://user:password@host:5432/hrmanager' | base64 DATABASE_URL: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" - # NextAuth secret (min 32 chars, generate with: openssl rand -base64 32) - # Replace with: echo -n '' | base64 - NEXTAUTH_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + # Auth.js (NextAuth v5) session secret — the v5 name is AUTH_SECRET, not + # NEXTAUTH_SECRET (v5 does not read the legacy name; sign-in fails without this). + # Generate with: openssl rand -base64 32, then: echo -n '' | base64 + AUTH_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" - # Google OAuth credentials + # Audit-log hash chain key — REQUIRED in production (the app fails closed + # without it once MongoDB is configured). Generate fresh, never reuse, never + # rotate casually (old entries verify against the key that wrote them). + AUDIT_HMAC_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + + # 2FA secret-encryption key (AES-256-GCM) — REQUIRED in production (2FA + # setup/verify fails closed without it). Never rotate casually — stored + # secrets decrypt only with the key that encrypted them. + TOTP_ENCRYPTION_KEY: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + + # Google OAuth credentials (Auth.js v5 names: AUTH_GOOGLE_*) # Replace with: echo -n '' | base64 - GOOGLE_CLIENT_ID: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + AUTH_GOOGLE_ID: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" # Replace with: echo -n '' | base64 - GOOGLE_CLIENT_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + AUTH_GOOGLE_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" - # GitHub OAuth credentials + # GitHub OAuth credentials (Auth.js v5 names: AUTH_GITHUB_*) # Replace with: echo -n '' | base64 - GITHUB_CLIENT_ID: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + AUTH_GITHUB_ID: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" # Replace with: echo -n '' | base64 - GITHUB_CLIENT_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + AUTH_GITHUB_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" - # MongoDB connection string (for audit logs) + # MongoDB connection string for audit logs — the app reads MONGODB_URL + # (with any other name the audit trail silently no-ops). # Replace with: echo -n 'mongodb://user:password@host:27017/hrmanager_audit' | base64 - MONGODB_URI: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + MONGODB_URL: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl" + # Optional: gates the /api/cron/* endpoints (audit drain, job drain, cleanup) + # if you schedule them with a CronJob or an external scheduler. + # CRON_SECRET: "cmVwbGFjZV93aXRoX2Jhc2U2NF9lbmNvZGVkX3ZhbHVl"