See full diagram gallery for interactive versions
Authentik is the homelab SSO and identity provider. It replaced OAuth2 Proxy on 2026-04-04, managing all authentication via Google OIDC federation, per-app group policies, and Traefik forwardAuth middleware.
| Public URL | https://auth.k3s.strommen.systems |
| Internal URL | https://auth.k3s.internal.strommen.systems |
| Namespace | authentik |
| Chart | authentik/authentik v2026.2.1 |
| Status | Phase 1 + Phase 2 complete. Phase 3 (per-app group policies) planned. |
| Component | Image | Purpose |
|---|---|---|
| authentik-server | ghcr.io/goauthentik/server:2026.2.1 |
Web UI, API, embedded proxy outpost |
| authentik-worker | ghcr.io/goauthentik/server:2026.2.1 |
Background tasks (email, GeoIP, blueprints) |
| PostgreSQL | postgres:16-alpine |
User store, config, task queues (5Gi Longhorn PVC) |
Note: Redis was removed in Authentik 2025.10. PostgreSQL now handles all task queues. No Redis deployment.
All external requests pass through Authentik's embedded outpost. Users see Google login if no session exists.
| Service | URL | Authentik App | Auth Policy |
|---|---|---|---|
| Open WebUI | https://chat.k3s.strommen.systems | openclaw |
All authenticated users |
| Cluster Dashboard | https://dash.k3s.strommen.systems | dashboard |
All authenticated users |
| Auto Brand | https://auto-brand.k3s.strommen.systems | auto-brand |
All authenticated users |
| HAM Habit Tracker | https://ham.k3s.strommen.systems | ham |
All authenticated users |
| Jellyseerr | https://jellyseerr.k3s.strommen.systems | jellyseerr |
All authenticated users |
| Radarr | https://radarr.k3s.strommen.systems | radarr |
All authenticated users |
| Sonarr | https://sonarr.k3s.strommen.systems | sonarr |
All authenticated users |
| Prowlarr | https://prowlarr.k3s.strommen.systems | prowlarr |
All authenticated users |
| Bazarr | https://bazarr.k3s.strommen.systems | bazarr |
All authenticated users |
| Tdarr | https://tdarr.k3s.strommen.systems | tdarr |
All authenticated users |
| T-Shirt Store | https://store.k3s.strommen.systems | store |
All authenticated users |
| Media Controller | https://media-controller.k3s.strommen.systems | media-controller |
All authenticated users |
| Cardboard | https://cardboard.k3s.internal.strommen.systems | cardboard |
Owner-only expression policy (Terraform: cardboard-access.tf) — restricts to mstrommen@gmail.com |
qBittorrent and Trade Bot are internal-only (no public external URL) and are not protected by Authentik middleware.
These apps have built-in OIDC support and handle login natively via Authentik as the IdP.
| Service | URL | Notes |
|---|---|---|
| Grafana | https://grafana.k3s.internal.strommen.systems | Group->role: admins=Admin, writers=Editor |
| Wiki.js | https://wiki.k3s.internal.strommen.systems | sub_mode=user_email; selfRegistration=true |
| Gitea | https://gitea.k3s.internal.strommen.systems | auth source added via gitea CLI |
| Harbor | https://harbor.k3s.internal.strommen.systems | oidc_auto_onboard=true, oidc_admin_group=authentik-admins |
| Jellyfin (web) | https://jellyfin.k3s.strommen.systems | jellyfin-plugin-sso with app-media-streaming group policy (Phase 2.6, complete 2026-04-04) |
These services are NOT protected by Authentik forwardAuth or OIDC.
| Service | Reason |
|---|---|
| Home Assistant | Companion app incompatible with forwardAuth; Google Assistant webhook requires open access. HA handles its own auth. |
| Jellyfin (TV/mobile) | Roku, Apple TV, iOS apps can't do OIDC redirects — use native Jellyfin auth |
| Plex | External clients (TV, mobile, desktop) need direct Plex account auth |
| Media Profiler | Open to any @gmail.com via oauth2-proxy-open-gmail (separate OAuth2 Proxy instance) |
| Group | Access |
|---|---|
authentik-admins |
Full control, all apps |
authentik-writers |
Read + interact |
k8s-readers |
kubectl get/list/watch (excl. secrets) |
| Name | Group | Notes |
|---|---|---|
| Mathew | authentik-admins | Break-glass local admin (akadmin) |
| Aja | authentik-writers | All app groups |
| Steve Le | authentik-writers | All app groups |
| Hamilton | authentik-writers + k8s-readers | Harbor, ham namespace, WireGuard VPN |
| Bryce | authentik-writers | All app groups |
| Jacob | authentik-writers | All app groups |
| Group | Controls |
|---|---|
app-media-streaming |
Jellyfin web SSO |
app-media-requests |
Jellyseerr |
app-media-management |
Radarr, Sonarr, Prowlarr, Bazarr, qBittorrent, Tdarr |
app-grafana |
Grafana dashboards |
app-openclaw |
OpenClaw AI assistant |
app-harbor |
Harbor container registry |
The authentik-forward-auth middleware is defined in public-ingress namespace:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authentik-forward-auth
namespace: public-ingress
spec:
forwardAuth:
address: http://authentik-server.authentik.svc.cluster.local/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
To protect a service, add to its IngressRoute:
middlewares:
- name: authentik-forward-auth
namespace: public-ingress
The terraform/environments/google/ environment is a dedicated Terraform workspace that manages:
cloudresourcemanager, oauth2, cloudidentity)This is a separate environment from aws/ and homelab-prod/. It uses three providers together to bridge GCP credentials → Kubernetes Secrets → Authentik configuration in a single terraform apply.
| State backend | S3 k3s-homelab-tfstate-855878721457/google/terraform.tfstate |
| GCP project | homelab-oauth (personal account, no org) |
# Provider versions (terraform/environments/google/main.tf)
hashicorp/google ~> 6.0
goauthentik/authentik >= 2024.0
hashicorp/kubernetes ~> 2.0
modules/google-oauth-appStores manually-created GCP OAuth client credentials as a Kubernetes Secret in the target namespace.
Why manual? GCP does not support creating standard OAuth 2.0 Web App clients via Terraform API. The client must be created once in the GCP Console, then credentials are passed in as variables.
One-time GCP Console setup:
https://auth.k3s.strommen.systems/oauth2/callbackclient_id and client_secretTF_VAR_authentik_google_client_id / TF_VAR_authentik_google_client_secretmodules/authentik-google-sourceWires the GCP OAuth client into Authentik as a social source (authentik_source_oauth.google). Configures auth + enrollment flows so users can log in via "Sign in with Google".
The Google social source may already be configured manually in Authentik. This module formalizes that configuration as IaC — applying it is safe and idempotent.
| Resource | Module | Result |
|---|---|---|
google-oauth-client K8s Secret in authentik ns |
google-oauth-app |
Stores client_id + client_secret from GCP Console |
| Authentik Google social source | authentik-google-source |
Configures authentik_source_oauth.google with auth + enrollment flows |
| GCP API enablement | main.tf |
Enables cloudresourcemanager, oauth2, cloudidentity APIs |
terraform/environments/google/cardboard-access.tf demonstrates the pattern for application-level access control managed as Terraform:
| Resource | Purpose |
|---|---|
authentik_provider_proxy.cardboard |
Proxy provider for cardboard.k3s.internal.strommen.systems (forward_single mode) |
authentik_policy_expression.cardboard-owner-only |
Expression policy — allows only mstrommen@gmail.com |
| Policy binding | Binds the expression policy to the Cardboard application |
To add a new app with owner-only access, follow the cardboard-access.tf pattern:
resource "authentik_provider_proxy" "<app>_proxy" {
name = "<app>"
external_host = "https://<app>.k3s.internal.strommen.systems"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
}
resource "authentik_policy_expression" "<app>_owner_only" {
name = "<app>-owner-only"
expression = <<-EOT
return request.user.email in ["mstrommen@gmail.com"]
EOT
}
resource "authentik_application" "<app>" {
name = "<app>"
slug = "<app>"
protocol_provider = authentik_provider_proxy.<app>_proxy.id
}
resource "authentik_policy_binding" "<app>_owner_binding" {
target = authentik_application.<app>.uuid
policy = authentik_policy_expression.<app>_owner_only.id
order = 0
}
authentik-writers (standard) or authentik-adminsIf Authentik is down:
kubectl port-forward -n <namespace> svc/<service> 8080:<port>
Grafana has a local admin account always available regardless of Authentik state.
PostgreSQL backup CronJob runs nightly:
s3://k3s-homelab-backups-855878721457/postgres-backups/authentik/
Credentials: authentik-secrets secret (namespace: authentik) — never committed to git.
| Secret | Namespace | Keys |
|---|---|---|
authentik-secrets |
authentik |
AUTHENTIK_SECRET_KEY, AUTHENTIK_POSTGRESQL__PASSWORD, AUTHENTIK_BOOTSTRAP_PASSWORD, AUTHENTIK_BOOTSTRAP_EMAIL, postgres-password (same value as AUTHENTIK_POSTGRESQL__PASSWORD — read directly by PostgreSQL StatefulSet in authentik.yaml) |
google-oauth-client |
authentik |
client_id, client_secret — managed by terraform/environments/google/ |
Bootstrap command (if recreating manually):
# Generate the postgres password once and use in both keys
POSTGRES_PASS="$(openssl rand -base64 32 | tr -d '\n')"
kubectl create secret generic authentik-secrets \
--from-literal=AUTHENTIK_SECRET_KEY="$(openssl rand -base64 60 | tr -d '\n')" \
--from-literal=AUTHENTIK_POSTGRESQL__PASSWORD="${POSTGRES_PASS}" \
--from-literal=postgres-password="${POSTGRES_PASS}" \
--from-literal=AUTHENTIK_BOOTSTRAP_PASSWORD="<strong-password>" \
--from-literal=AUTHENTIK_BOOTSTRAP_EMAIL="<admin-email>" \
-n authentik
Note:
postgres-passwordandAUTHENTIK_POSTGRESQL__PASSWORDmust be identical. The StatefulSet (authentik.yaml) readspostgres-passworddirectly; the Helm deployment readsAUTHENTIK_POSTGRESQL__PASSWORDviaenvFrom.secretRef.
Prometheus metrics at :9300/metrics. ServiceMonitor label: release: prometheus.
Grafana dashboard: kubernetes/apps/authentik/grafana-dashboard-authentik.yaml
Authentik uses a default-deny base with three explicit policy objects in network-policy.yaml.
Note on intra-namespace selector: kube-router's ipset sync lag causes connection failures from newly-spawned Job/CronJob pods if
podSelector: {}is used. All intra-namespace rules useipBlock: 10.42.0.0/16(pod CIDR) instead.
| Policy | Direction | Source/Dest | Port(s) | Purpose |
|---|---|---|---|---|
authentik-default-deny |
Ingress + Egress | — | — | Default deny all |
authentik-allow-ingress |
Ingress | Traefik (kube-system) | 9000, 9443 | Browser traffic + forwardAuth |
authentik-allow-ingress |
Ingress | Traefik (any namespace) | 9000 | Cross-namespace forwardAuth subrequests |
authentik-allow-ingress |
Ingress | Prometheus (monitoring) | 9300 | Metrics scraping |
authentik-allow-ingress |
Ingress | 10.42.0.0/16 (pod CIDR) |
all | Intra-namespace (server ↔ worker ↔ postgres) |
authentik-allow-egress |
Egress | CoreDNS (kube-system) | 53 | DNS resolution |
authentik-allow-egress |
Egress | 10.42.0.0/16 (pod CIDR) |
all | Intra-namespace (postgres:5432) |
authentik-allow-egress |
Egress | Internet (not RFC1918) | 443 | Google OAuth, Authentik blueprints |
authentik-backup-allow-egress |
Egress | CoreDNS | 53 | Backup pod DNS |
authentik-backup-allow-egress |
Egress | Same-namespace pods | 5432 | Backup → PostgreSQL |
authentik-backup-allow-egress |
Egress | Internet (not RFC1918) | 443 | Backup → S3 upload |
kubernetes/apps/authentik/
authentik.yaml -- PostgreSQL StatefulSet + backup CronJob + IngressRoutes
authentik-helm-values.yaml -- Helm values (no Redis, external PG, rate limits)
network-policy.yaml -- NetworkPolicy (Traefik + Prometheus ingress)
grafana-dashboard-authentik.yaml -- Pre-provisioned Grafana dashboard
terraform/environments/google/
main.tf -- Providers (google, authentik, kubernetes); S3 backend
variables.tf -- Input variables (client IDs, secrets)
terraform.tfvars.example -- Variable template (no secrets committed)
authentik.tf -- authentik-google-source module call
oauth_apps.tf -- google-oauth-app module calls (one per OAuth client)
cardboard-access.tf -- Cardboard proxy provider + owner-only expression policy
terraform/modules/google-oauth-app/
-- Creates K8s Secret in target namespace from GCP OAuth client credentials
terraform/modules/authentik-google-source/
-- Wires Google OAuth client into Authentik as a social source
| Date | Phase | Story | Status |
|---|---|---|---|
| 2026-04-03 | 1 | 1.1 Core deployment | Complete |
| 2026-04-03 | 1 | 1.2 Google federation + users | Complete |
| 2026-04-04 | 1 | 1.3 Public service cutover (11 services) | Complete |
| 2026-04-05 | 1 | media-controller forwardAuth fix | Complete |
| 2026-04-11 | 1 | 1.4 OAuth2 Proxy cleanup (after 7-day soak) | Scheduled |
| 2026-04-03 | 2 | 2.1 Grafana OIDC | Complete |
| 2026-04-03 | 2 | 2.2 Wiki.js OIDC | Complete |
| 2026-04-03 | 2 | 2.3 Gitea OIDC | Complete |
| 2026-04-03 | 2 | 2.4 Harbor OIDC | Complete |
| 2026-04-03 | 2 | 2.5 Jellyseerr (uses Jellyfin auth) | Complete |
| 2026-04-04 | 2 | 2.6 Jellyfin SSO plugin (web only) | Complete |
| 2026-04-04 | 2 | 2.7 Jellyfin group-based provisioning | Complete |
| 2026-04-05 | 2 | 2.8 Google OAuth Terraform automation | Complete |
| — | 3 | Per-app group policies for internal services | Planned |
| — | 4 | Collective mesh integration | Planned |
Phase 1.4 action item: On or after 2026-04-11, delete the OAuth2 Proxy deployment from
public-ingressnamespace. Confirm no traffic since cutover date before deleting.
app-openclaw group policy