3d3ba84df0
Account deletion removed all local data but left the Ory Kratos
identity intact — an orphaned identity that can still authenticate.
Close the gap:
- kratos.Client gains the admin API: NewClient(publicURL, adminURL)
and DeleteIdentity (DELETE /admin/identities/{id}; a 404 is treated
as success so a retry after a partial failure is idempotent).
- AuthService.DeleteAccount deletes the Kratos identity FIRST; if that
call fails it aborts before touching local data, so the operation is
retryable rather than partially applied.
- KRATOS_ADMIN_URL config (default http://kratos:4434) + router wiring.
- kratos NetworkPolicy split: the api pods may now reach the admin API
:4434 (Traefik still reaches only the public API :4433).
- kratos CORS: allow_credentials + OPTIONS so the web browser flows
(ory_kratos_session cookie) work; origins stay an explicit allowlist.
- Regression tests: identity teardown happens, and a Kratos failure
aborts the deletion instead of orphaning local data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
208 lines
6.1 KiB
YAML
208 lines
6.1 KiB
YAML
# Ory Kratos — identity service for honeyDue.
|
|
#
|
|
# Deployed only once the operator has completed the prerequisites in
|
|
# kratos/README.md (Neon `kratos` database, auth.myhoneydue.com DNS, Apple +
|
|
# Google OAuth apps, and the kratos-secrets Secret). Until then 03-deploy.sh
|
|
# skips the Kratos apply, so the existing stack is unaffected.
|
|
#
|
|
# IMAGE: oryd/kratos uses CalVer (v25.x / v26.x). The tag below is a
|
|
# fail-loud placeholder — set the current stable tag and pin a @sha256:
|
|
# digest (like redis/vmagent) before deploying. See kratos/README.md.
|
|
# The schema-migration Job is in migrate-job.yaml (run before this).
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: kratos
|
|
namespace: honeydue
|
|
labels:
|
|
app.kubernetes.io/name: kratos
|
|
app.kubernetes.io/part-of: honeydue
|
|
spec:
|
|
replicas: 2
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxUnavailable: 0
|
|
maxSurge: 1
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: kratos
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: kratos
|
|
app.kubernetes.io/part-of: honeydue
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: kratos
|
|
image: oryd/kratos:REPLACE_WITH_CURRENT_STABLE_TAG
|
|
imagePullPolicy: IfNotPresent
|
|
args:
|
|
- serve
|
|
- --config
|
|
- /etc/kratos/kratos.yml
|
|
- --watch-courier # send verification/recovery email in-process
|
|
ports:
|
|
- name: public
|
|
containerPort: 4433
|
|
- name: admin
|
|
containerPort: 4434
|
|
env:
|
|
# Kratos is configured natively via env vars; secrets come from
|
|
# the kratos-secrets Secret rather than the ConfigMap.
|
|
- name: DSN
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: dsn } }
|
|
- name: SECRETS_COOKIE
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: secrets_cookie } }
|
|
- name: SECRETS_CIPHER
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: secrets_cipher } }
|
|
- name: COURIER_SMTP_CONNECTION_URI
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: smtp_connection_uri } }
|
|
# OIDC provider secrets — index must match the providers list
|
|
# order in configmap.yaml (0 = google, 1 = apple).
|
|
- name: SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_0_CLIENT_SECRET
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: google_client_secret } }
|
|
- name: SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_APPLE_PRIVATE_KEY
|
|
valueFrom: { secretKeyRef: { name: kratos-secrets, key: apple_private_key } }
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /etc/kratos
|
|
readOnly: true
|
|
- name: tmp
|
|
mountPath: /tmp
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health/ready
|
|
port: 4434
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health/alive
|
|
port: 4434
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 30
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 512Mi
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
volumes:
|
|
- name: config
|
|
configMap:
|
|
name: kratos-config
|
|
- name: tmp
|
|
emptyDir:
|
|
sizeLimit: 64Mi
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: kratos
|
|
namespace: honeydue
|
|
labels:
|
|
app.kubernetes.io/name: kratos
|
|
app.kubernetes.io/part-of: honeydue
|
|
spec:
|
|
selector:
|
|
app.kubernetes.io/name: kratos
|
|
ports:
|
|
- name: public
|
|
port: 4433
|
|
targetPort: 4433
|
|
- name: admin
|
|
port: 4434
|
|
targetPort: 4434
|
|
---
|
|
# Ingress to Kratos. Traefik (the auth.myhoneydue.com IngressRoute) reaches
|
|
# only the public API :4433. The honeyDue api pods reach the public API :4433
|
|
# (session whoami) AND the admin API :4434 (identity deletion on account
|
|
# close). The admin API :4434 takes no other cluster ingress.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: allow-ingress-to-kratos
|
|
namespace: honeydue
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: kratos
|
|
policyTypes:
|
|
- Ingress
|
|
ingress:
|
|
# Traefik ingress controller -> public API only.
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
ports:
|
|
- port: 4433
|
|
protocol: TCP
|
|
# honeyDue api pods -> public API (whoami) + admin API (identity deletion).
|
|
- from:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: api
|
|
ports:
|
|
- port: 4433
|
|
protocol: TCP
|
|
- port: 4434
|
|
protocol: TCP
|
|
---
|
|
# Kratos egress: DNS, the Neon Postgres database, SMTP, and HTTPS to the
|
|
# OIDC providers (Apple/Google token + JWKS endpoints).
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: allow-egress-from-kratos
|
|
namespace: honeydue
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: kratos
|
|
policyTypes:
|
|
- Egress
|
|
egress:
|
|
- to:
|
|
- namespaceSelector: {}
|
|
ports:
|
|
- port: 53
|
|
protocol: UDP
|
|
- port: 53
|
|
protocol: TCP
|
|
# Neon Postgres (external)
|
|
- to:
|
|
- ipBlock:
|
|
cidr: 0.0.0.0/0
|
|
except:
|
|
- 10.42.0.0/16
|
|
- 10.43.0.0/16
|
|
ports:
|
|
- port: 5432
|
|
protocol: TCP
|
|
# SMTP (Fastmail) + HTTPS to Apple/Google OIDC endpoints (external)
|
|
- to:
|
|
- ipBlock:
|
|
cidr: 0.0.0.0/0
|
|
except:
|
|
- 10.42.0.0/16
|
|
- 10.43.0.0/16
|
|
ports:
|
|
- port: 465
|
|
protocol: TCP
|
|
- port: 443
|
|
protocol: TCP
|