diff --git a/apps/authentik/authentik.yaml b/apps/authentik/authentik.yaml new file mode 100644 index 0000000..0915a53 --- /dev/null +++ b/apps/authentik/authentik.yaml @@ -0,0 +1,428 @@ +# Authentik OIDC backend +# ArgoCD-managed. BlueJay Lab. +# +# Stack: +# - PostgreSQL 16 StatefulSet (single replica, Longhorn RWO 5Gi) +# - Redis 7 Deployment (no persistence — session/cache only) +# - Authentik server + worker Deployments (image ghcr.io/goauthentik/server:2024.12.3) +# - Media PVC shared between server + worker (Longhorn RWO 2Gi) +# - Certificate via step-ca-acme ClusterIssuer +# - Traefik IngressRoute at id.iamworkin.lan +# +# Secrets come from 1Password item "authentik-credentials" (IAmWorkin vault, id y6i74ch22q5wvm7znquq4nhhcu) +# via the OnePasswordItem CRD, materialized into k8s Secret authentik/authentik-credentials. +# +# Why the discovery URL is /application/o/pimanager/ : Authentik issues per-application OIDC providers. +# The pimanager OIDC application/provider is created after the cluster pods are healthy (manual or +# via API once the bootstrap token is available — see Notes substrate). + +--- +apiVersion: v1 +kind: Namespace +metadata: + name: authentik + labels: + app.kubernetes.io/part-of: bluejay-infra + +--- +# 1Password operator pulls the authentik-credentials item into a k8s Secret of the same name. +# Field labels in 1P become Secret keys: AUTHENTIK_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD, +# BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL. +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: authentik-credentials + namespace: authentik +spec: + itemPath: "vaults/IAmWorkin/items/authentik-credentials" + +--- +# Shared media volume for server + worker pods. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: authentik-media + namespace: authentik +spec: + storageClassName: longhorn + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 2Gi + +--- +# PostgreSQL 16 StatefulSet — Authentik's primary store. +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: authentik-postgres + namespace: authentik + labels: + app: authentik-postgres + argocd.argoproj.io/instance: infra-authentik +spec: + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + podManagementPolicy: OrderedReady + serviceName: authentik-postgres + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: authentik-postgres + template: + metadata: + labels: + app: authentik-postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + value: authentik + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + value: authentik + - name: POSTGRES_INITDB_ARGS + value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + readinessProbe: + exec: + command: ["pg_isready", "-U", "authentik"] + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + exec: + command: ["pg_isready", "-U", "authentik"] + initialDelaySeconds: 30 + periodSeconds: 30 + resources: + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: 1000m, memory: 1Gi } + volumeMounts: + - name: pgdata + mountPath: /var/lib/postgresql/data + volumeClaimTemplates: + - metadata: + name: pgdata + spec: + storageClassName: longhorn + accessModes: [ReadWriteOnce] + volumeMode: Filesystem + resources: + requests: + storage: 5Gi + +--- +apiVersion: v1 +kind: Service +metadata: + name: authentik-postgres + namespace: authentik +spec: + clusterIP: None + selector: + app: authentik-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + +--- +# Redis 7 — session storage + Celery broker. No persistence needed (cache). +apiVersion: apps/v1 +kind: Deployment +metadata: + name: authentik-redis + namespace: authentik + labels: + app: authentik-redis + argocd.argoproj.io/instance: infra-authentik +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: authentik-redis + template: + metadata: + labels: + app: authentik-redis + spec: + containers: + - name: redis + image: redis:7-alpine + args: + - "--save" + - "" + - "--appendonly" + - "no" + - "--requirepass" + - "$(REDIS_PASSWORD)" + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: REDIS_PASSWORD + ports: + - containerPort: 6379 + name: redis + readinessProbe: + tcpSocket: { port: 6379 } + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + tcpSocket: { port: 6379 } + initialDelaySeconds: 30 + periodSeconds: 30 + resources: + requests: { cpu: 50m, memory: 64Mi } + limits: { cpu: 500m, memory: 256Mi } + +--- +apiVersion: v1 +kind: Service +metadata: + name: authentik-redis + namespace: authentik +spec: + selector: + app: authentik-redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + +--- +# Authentik server Deployment — HTTP frontend on :9000. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: authentik-server + namespace: authentik + labels: + app: authentik-server + argocd.argoproj.io/instance: infra-authentik +spec: + replicas: 1 + strategy: + type: Recreate # shares /media RWO PVC with worker + selector: + matchLabels: + app: authentik-server + template: + metadata: + labels: + app: authentik-server + spec: + containers: + - name: server + image: ghcr.io/goauthentik/server:2024.12.3 + args: ["server"] + ports: + - containerPort: 9000 + name: http + - containerPort: 9443 + name: https + env: + - name: AUTHENTIK_SECRET_KEY + valueFrom: + secretKeyRef: + name: authentik-credentials + key: AUTHENTIK_SECRET_KEY + - name: AUTHENTIK_REDIS__HOST + value: authentik-redis + - name: AUTHENTIK_REDIS__PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: REDIS_PASSWORD + - name: AUTHENTIK_POSTGRESQL__HOST + value: authentik-postgres + - name: AUTHENTIK_POSTGRESQL__NAME + value: authentik + - name: AUTHENTIK_POSTGRESQL__USER + value: authentik + - name: AUTHENTIK_POSTGRESQL__PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: POSTGRES_PASSWORD + - name: AUTHENTIK_BOOTSTRAP_PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: BOOTSTRAP_ADMIN_PASSWORD + - name: AUTHENTIK_BOOTSTRAP_TOKEN + valueFrom: + secretKeyRef: + name: authentik-credentials + key: BOOTSTRAP_ADMIN_TOKEN + - name: AUTHENTIK_BOOTSTRAP_EMAIL + valueFrom: + secretKeyRef: + name: authentik-credentials + key: BOOTSTRAP_ADMIN_EMAIL + - name: AUTHENTIK_DISABLE_UPDATE_CHECK + value: "true" + - name: AUTHENTIK_ERROR_REPORTING__ENABLED + value: "false" + - name: AUTHENTIK_LOG_LEVEL + value: info + readinessProbe: + httpGet: + path: /-/health/ready/ + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /-/health/live/ + port: 9000 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + resources: + requests: { cpu: 150m, memory: 512Mi } + limits: { cpu: 1500m, memory: 1Gi } + volumeMounts: + - name: media + mountPath: /media + volumes: + - name: media + persistentVolumeClaim: + claimName: authentik-media + +--- +# Authentik worker Deployment — runs Celery background tasks. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: authentik-worker + namespace: authentik + labels: + app: authentik-worker + argocd.argoproj.io/instance: infra-authentik +spec: + replicas: 1 + strategy: + type: Recreate # shares /media RWO PVC with server + selector: + matchLabels: + app: authentik-worker + template: + metadata: + labels: + app: authentik-worker + spec: + containers: + - name: worker + image: ghcr.io/goauthentik/server:2024.12.3 + args: ["worker"] + env: + - name: AUTHENTIK_SECRET_KEY + valueFrom: + secretKeyRef: + name: authentik-credentials + key: AUTHENTIK_SECRET_KEY + - name: AUTHENTIK_REDIS__HOST + value: authentik-redis + - name: AUTHENTIK_REDIS__PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: REDIS_PASSWORD + - name: AUTHENTIK_POSTGRESQL__HOST + value: authentik-postgres + - name: AUTHENTIK_POSTGRESQL__NAME + value: authentik + - name: AUTHENTIK_POSTGRESQL__USER + value: authentik + - name: AUTHENTIK_POSTGRESQL__PASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: POSTGRES_PASSWORD + - name: AUTHENTIK_DISABLE_UPDATE_CHECK + value: "true" + - name: AUTHENTIK_ERROR_REPORTING__ENABLED + value: "false" + - name: AUTHENTIK_LOG_LEVEL + value: info + resources: + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: 1000m, memory: 768Mi } + volumeMounts: + - name: media + mountPath: /media + volumes: + - name: media + persistentVolumeClaim: + claimName: authentik-media + +--- +apiVersion: v1 +kind: Service +metadata: + name: authentik-server + namespace: authentik +spec: + selector: + app: authentik-server + ports: + - name: http + port: 9000 + targetPort: 9000 + - name: https + port: 9443 + targetPort: 9443 + +--- +# step-ca leaf certificate for id.iamworkin.lan. +# step-ca container resolver uses pfSense Unbound, so the public A record for id.iamworkin.lan +# MUST exist before this Certificate is applied (cert-manager HTTP-01 will silently 2h-backoff +# otherwise). Added 2026-05-25 via scripts/pfsense-add-id-host.py. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: authentik-tls + namespace: authentik +spec: + secretName: authentik-tls + dnsNames: + - id.iamworkin.lan + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: authentik + namespace: authentik +spec: + entryPoints: [websecure] + routes: + - match: Host(`id.iamworkin.lan`) + kind: Rule + services: + - name: authentik-server + port: 9000 + tls: + secretName: authentik-tls