6de90acef7
Auth was structurally broken — the api's Kratos middleware was pointing at http://kratos:4433 but Kratos wasn't deployed. The only thing keeping users logged in was a 5-min Redis cache; once it expired the middleware called Whoami → no DNS → 401 → forced relogin with no path back. This commit deploys Kratos for real: Manifests: - kratos.yaml + migrate-job.yaml: pin oryd/kratos:v26.2.0@sha256:92eedc... (CalVer current stable as of 2026-06-03) - configmap.yaml: drop Google OIDC provider (not in scope); fill the Apple provider with real Services ID / Team ID / Key ID — Apple now sits at providers[0] - kratos.yaml: drop the Google-secret env binding; rebind APPLE_PRIVATE_KEY to PROVIDERS_0_APPLE_PRIVATE_KEY (shifted from index 1) - network-policies.yaml: add a kratos egress rule to allow-egress-from-api. Without this, even with kratos running, the api gets "connection refused" on http://kratos:4433 (post-DNAT NetworkPolicy enforcement — runbook §9.2). Operator prerequisites that were completed alongside this commit: - Neon kratos database created (separate from honeyDue, owner neondb_owner) - Cloudflare DNS for auth.myhoneydue.com (3 A records, proxied) - kratos: block added to config.yaml (gitignored): DSN to the Neon DIRECT endpoint, cookie + cipher secrets generated, Fastmail SMTPS URI, .p8 contents inline Out of scope intentionally: - Google sign-in (additive; can append providers[] later) - Migrating existing auth_user rows onto Kratos identities — pre-prod; existing users will need to sign in fresh, which creates a new Kratos identity and a new local user row (per migration plan in manifests/kratos/README.md). Verified end-to-end: - 338 schema migrations applied successfully - 2/2 kratos pods Ready - api → kratos:4433/sessions/whoami returns 401 for invalid token (was "connection refused" before this commit's NetworkPolicy patch) - auth.myhoneydue.com resolves through CF; cloudflare-only middleware keeps the origin protected exactly like the other hostnames Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
206 lines
6.7 KiB
YAML
206 lines
6.7 KiB
YAML
# Ory Kratos configuration for honeyDue.
|
|
#
|
|
# Secrets are NOT in this ConfigMap. The DSN, cookie/cipher secrets, SMTP URI
|
|
# and OIDC client secrets are injected as environment variables from the
|
|
# kratos-secrets Secret (see kratos.yaml). Kratos is configured natively via
|
|
# env vars, so this is the idiomatic split — only non-secret config here.
|
|
#
|
|
# OIDC scope: Apple-only as of 2026-06-03. Google is intentionally absent;
|
|
# adding it later is additive — append a `- id: google` block under
|
|
# selfservice.methods.oidc.config.providers (it becomes index 1) and bind a
|
|
# matching CLIENT_SECRET env in kratos.yaml.
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: kratos-config
|
|
namespace: honeydue
|
|
labels:
|
|
app.kubernetes.io/name: kratos
|
|
app.kubernetes.io/part-of: honeydue
|
|
data:
|
|
kratos.yml: |
|
|
# version must track the Kratos image tag — kratos.yaml + migrate-job.yaml
|
|
# both pin oryd/kratos:v26.2.0 (2026-06-03). See kratos/README.md.
|
|
version: v1.3.0 # internal config schema version; do not change unless Kratos release notes require it
|
|
|
|
serve:
|
|
public:
|
|
base_url: https://auth.myhoneydue.com/
|
|
cors:
|
|
enabled: true
|
|
allowed_origins:
|
|
- https://myhoneydue.com
|
|
- https://app.myhoneydue.com
|
|
- https://admin.myhoneydue.com
|
|
allowed_methods: [GET, POST, PUT, PATCH, DELETE, OPTIONS]
|
|
allowed_headers: [Authorization, Content-Type, X-Session-Token, Cookie]
|
|
exposed_headers: [Content-Type, Set-Cookie]
|
|
# Required: the web clients call Kratos browser flows with
|
|
# credentials (the ory_kratos_session cookie). Safe here because
|
|
# allowed_origins is an explicit list, never a wildcard.
|
|
allow_credentials: true
|
|
admin:
|
|
base_url: http://kratos.honeydue.svc.cluster.local:4434/
|
|
|
|
selfservice:
|
|
default_browser_return_url: https://app.myhoneydue.com/
|
|
allowed_return_urls:
|
|
- https://app.myhoneydue.com
|
|
- https://myhoneydue.com
|
|
- honeydue://callback
|
|
|
|
methods:
|
|
password:
|
|
enabled: true
|
|
code: # email one-time codes (verify/recover)
|
|
enabled: true
|
|
oidc:
|
|
enabled: true
|
|
config:
|
|
providers:
|
|
# index 0 — Apple Sign In. apple_private_key (.p8 contents) is
|
|
# injected via env SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_0_APPLE_PRIVATE_KEY.
|
|
# client_id is the Apple Services ID (here: the bundle ID, which
|
|
# was configured as a Services ID with Sign In with Apple
|
|
# capability — see operator notes in README.md §5).
|
|
- id: apple
|
|
provider: apple
|
|
client_id: com.myhoneydue.honeyDue
|
|
apple_team_id: X86BR9WTLD
|
|
apple_private_key_id: HQD3NCF99C
|
|
mapper_url: file:///etc/kratos/oidc.apple.jsonnet
|
|
scope: [openid, email, name]
|
|
|
|
flows:
|
|
error:
|
|
ui_url: https://app.myhoneydue.com/auth/error
|
|
login:
|
|
ui_url: https://app.myhoneydue.com/auth/login
|
|
lifespan: 10m
|
|
registration:
|
|
ui_url: https://app.myhoneydue.com/auth/registration
|
|
lifespan: 10m
|
|
after:
|
|
password:
|
|
hooks:
|
|
- hook: session # auto-login after registration
|
|
oidc:
|
|
hooks:
|
|
- hook: session
|
|
verification:
|
|
enabled: true
|
|
ui_url: https://app.myhoneydue.com/auth/verification
|
|
use: code
|
|
after:
|
|
default_browser_return_url: https://app.myhoneydue.com/
|
|
recovery:
|
|
enabled: true
|
|
ui_url: https://app.myhoneydue.com/auth/recovery
|
|
use: code
|
|
settings:
|
|
ui_url: https://app.myhoneydue.com/auth/settings
|
|
privileged_session_max_age: 15m
|
|
logout:
|
|
after:
|
|
default_browser_return_url: https://app.myhoneydue.com/
|
|
|
|
log:
|
|
level: info
|
|
format: json
|
|
leak_sensitive_values: false
|
|
|
|
ciphers:
|
|
algorithm: xchacha20-poly1305
|
|
|
|
hashers:
|
|
algorithm: bcrypt
|
|
bcrypt:
|
|
cost: 12
|
|
|
|
identity:
|
|
default_schema_id: honeydue
|
|
schemas:
|
|
- id: honeydue
|
|
url: file:///etc/kratos/identity.schema.json
|
|
|
|
courier:
|
|
smtp:
|
|
from_address: noreply@myhoneydue.com
|
|
from_name: honeyDue
|
|
# connection_uri is injected via env COURIER_SMTP_CONNECTION_URI
|
|
|
|
session:
|
|
lifespan: 720h # 30-day sessions (mobile)
|
|
cookie:
|
|
domain: myhoneydue.com
|
|
same_site: Lax
|
|
|
|
identity.schema.json: |
|
|
{
|
|
"$id": "https://honeydue.app/identity.schema.json",
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"title": "honeyDue user",
|
|
"type": "object",
|
|
"properties": {
|
|
"traits": {
|
|
"type": "object",
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"title": "Email",
|
|
"minLength": 3,
|
|
"maxLength": 320,
|
|
"ory.sh/kratos": {
|
|
"credentials": {
|
|
"password": { "identifier": true },
|
|
"code": { "identifier": true, "via": "email" },
|
|
"totp": { "account_name": true }
|
|
},
|
|
"verification": { "via": "email" },
|
|
"recovery": { "via": "email" }
|
|
}
|
|
},
|
|
"name": {
|
|
"type": "object",
|
|
"title": "Name",
|
|
"properties": {
|
|
"first": { "type": "string", "title": "First name", "maxLength": 100 },
|
|
"last": { "type": "string", "title": "Last name", "maxLength": 100 }
|
|
}
|
|
}
|
|
},
|
|
"required": ["email"],
|
|
"additionalProperties": false
|
|
}
|
|
}
|
|
}
|
|
|
|
oidc.google.jsonnet: |
|
|
// Maps Google OIDC claims onto the honeyDue identity schema.
|
|
local claims = std.extVar('claims');
|
|
{
|
|
identity: {
|
|
traits: {
|
|
email: claims.email,
|
|
[if 'given_name' in claims || 'family_name' in claims then 'name']: {
|
|
first: if 'given_name' in claims then claims.given_name else '',
|
|
last: if 'family_name' in claims then claims.family_name else '',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
oidc.apple.jsonnet: |
|
|
// Maps Apple OIDC claims onto the honeyDue identity schema. Apple only
|
|
// returns the name on the very first authorization and not in the ID
|
|
// token claims, so only email is mapped here.
|
|
local claims = std.extVar('claims');
|
|
{
|
|
identity: {
|
|
traits: {
|
|
email: claims.email,
|
|
},
|
|
},
|
|
}
|