iOS Sign In with Apple failed silently — the KMP client never reached
Kratos. Traced to the cloudflare-only Traefik middleware rejecting every
request at the auth ingress.
Root cause: on this cluster klipper-lb sits in front of Traefik and
SNATs the source IP. Traefik's ipAllowList sees the klipper-lb pod IP,
not Cloudflare's real source IP — so even legitimate iOS requests
proxied through Cloudflare get 403'd. The api ingress doesn't have
this middleware (and works correctly), so removing it from auth
matches the working pattern.
Kratos is the user-facing OIDC endpoint — every iOS/web user device
needs to reach it. Cloudflare's edge still does DDoS protection;
Kratos applies its own per-flow rate limits. The IP allowlist was
buying nothing here and breaking everything.
Verified after this change:
- GET /health/alive → 200
- GET /health/ready → 200
- GET /self-service/login/api → 200 + valid flow body listing apple
as an OIDC provider option
Related but not fixed by this commit: the same klipper-lb SNAT issue
affects admin.myhoneydue.com (which retains cloudflare-only). Admin
basic auth still gates real access there, but the IP check is dead
weight. Proper fix is configuring Traefik ipStrategy to read the
client IP from X-Forwarded-For (set by Cloudflare). Tracked as a
follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ory Kratos — honeyDue identity service (Phase 1: infrastructure)
This directory deploys Ory Kratos into the
honeydue namespace as the identity provider — replacing the hand-rolled auth
in internal/services/auth_service.go etc.
Phase 1 is infrastructure only. Once deployed, Kratos runs but nothing uses it yet — the honeyDue Go API still does its own auth. Phase 2 (backend swap) and Phase 3 (KMP/web clients) follow. Migrating onto Kratos can lose all existing user data — honeyDue is pre-production, so no user import is done.
The deploy is gated: 03-deploy.sh applies Kratos only when the
kratos-secrets Secret exists, and 02-setup-secrets.sh creates that Secret
only when config.yaml has a kratos: block. Until then the existing stack
deploys completely unaffected.
Files
| File | What |
|---|---|
configmap.yaml |
kratos.yml, identity schema, Google/Apple OIDC claim mappers (no secrets) |
migrate-job.yaml |
kratos migrate sql — schema migration, run before the Deployment |
kratos.yaml |
Deployment (×2), Service, NetworkPolicies |
ingress.yaml |
auth.myhoneydue.com → Kratos public API :4433 |
Operator prerequisites (must be done before deploying)
-
Kratos version — Ory uses CalVer (
v25.x/v26.x). Pick the current stable, then replaceREPLACE_WITH_CURRENT_STABLE_TAGinkratos.yamlandmigrate-job.yamlwithoryd/kratos:vXX.Y@sha256:<digest>, and set the matchingversion:inconfigmap.yaml. -
Kratos database — create a separate Neon database named
kratos(do not share honeyDue's). Capture its connection string as the DSN. -
DNS — add
auth.myhoneydue.comin Cloudflare (proxied), pointing at the cluster ingress like the other honeyDue hosts. Confirm thecloudflare-origin-certTLS secret coversauth.myhoneydue.com. -
Google OAuth client — Google Cloud Console → create an OAuth 2.0 client. Redirect URI:
https://auth.myhoneydue.com/self-service/methods/oidc/callback/google. Put the client ID intoconfigmap.yaml(GOOGLE_OAUTH_CLIENT_ID); the client secret goes inconfig.yaml. -
Apple Sign In — Apple Developer → a Services ID + a Sign in with Apple key. Return URL:
https://auth.myhoneydue.com/self-service/methods/oidc/callback/apple. Put the Services ID / Team ID / Key ID intoconfigmap.yaml(APPLE_SERVICES_ID/APPLE_TEAM_ID/APPLE_PRIVATE_KEY_ID); the .p8 private key goes inconfig.yaml. -
config.yaml— add akratos:block:kratos: dsn: "postgres://USER:PASS@HOST/kratos?sslmode=require" secrets_cookie: "<openssl rand -hex 16>" # generate ONCE, keep stable secrets_cipher: "<openssl rand -hex 16>" # must be exactly 32 chars smtp_connection_uri: "smtps://USER:PASS@smtp.fastmail.com:465/" google_client_secret: "<from Google Cloud Console>" apple_private_key: | -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----secrets_cookie/secrets_ciphermust stay stable forever — rotating them invalidates every session and makes encrypted data unreadable.
Deploy
cd honeyDueAPI-go
export KUBECONFIG="$(pwd)/deploy-k3s/kubeconfig"
./deploy-k3s/scripts/02-setup-secrets.sh # creates kratos-secrets from config.yaml
./deploy-k3s/scripts/03-deploy.sh # applies kratos manifests, runs migrate, rolls
03-deploy.sh applies configmap.yaml → runs migrate-job.yaml → waits →
applies kratos.yaml + ingress.yaml.
Verify
kubectl -n honeydue get pods -l app.kubernetes.io/name=kratos— 2/2 Runningkubectl -n honeydue logs job/kratos-migrate— migration succeededcurl https://auth.myhoneydue.com/health/ready—{"status":"ok"}curl https://auth.myhoneydue.com/self-service/registration/api— returns a flow
Not yet done (later phases)
- Phase 2 — honeyDue Go backend: swap
middleware/auth.gofor Kratos session validation, drop the hand-rolled auth code, rebuild theuserstable keyed on the Kratos identity ID. - Phase 3 — KMP mobile + Next.js web clients point at Kratos flows.
- Admin-panel auth stays on its own JWT (out of scope).