infra/stacks/authentik/authentik_provider.tf

201 lines
8.8 KiB
Terraform
Raw Normal View History

[infra] Adopt Authentik catch-all Proxy Provider + Application into TF (Wave 6a) ## Context Wave 6a of the state-drift consolidation plan. The Domain wide catch all Proxy Provider (pk=5) + its wrapping Application (slug=domain-wide-catch-all) + the embedded outpost (uuid 0eecac07-97c7-443c-8925-05f2f4fe3e47) have run for a year as pure UI-created state. When the 2026-04-18 outpost SEV2 hit, it was harder to reason about the config than it should have been — the only source of truth was the Authentik admin UI. Bringing the provider + application under Terraform means future changes are reviewable in PRs and recoverable from git if the admin UI misbehaves. ## This change Adds the `goauthentik/authentik` provider to the repo's central `terragrunt.hcl` `required_providers` (side-effect: every stack can now declare authentik resources; this stack is the only current consumer). Stack-local `stacks/authentik/authentik_provider.tf` holds the provider instance configuration + API token wiring + two resources + their flow data-source lookups. ### Auth - API token stored in Vault at `secret/authentik/tf_api_token`, identifier `terraform-infra-stack`, intent=API, user=akadmin, no expiry. Rotatable by rewriting the Vault KV + any running TF apply picks it up on next plan. ### Imports (both landed zero-diff) - `authentik_application.catchall` ← id `domain-wide-catch-all` - `authentik_provider_proxy.catchall` ← id `5` ### Flow references Authorization + invalidation flows are looked up via `data "authentik_flow"` by slug (`default-provider-authorization-implicit-consent` + `default-provider-invalidation-flow`). Keeping them as data sources rather than hardcoded UUIDs means a flow recreation (slug unchanged) doesn't require an HCL edit. ### `lifecycle { ignore_changes }` scope On `authentik_provider_proxy.catchall`: - `property_mappings` (5 UUIDs), `jwt_federation_sources` (1 UUID) — the live state references complex many-to-many relations that are easier to manage from the Authentik UI than to serialise in HCL. Drift suppressed. - `skip_path_regex`, `internal_host`, all `basic_auth_*`, `intercept_header_auth`, `access_token_validity` — either defaults or UI-only tuning knobs that aren't part of Terraform's concern for this catch-all provider. On `authentik_application.catchall`: - `meta_description`, `meta_launch_url`, `meta_icon`, `group`, `backchannel_providers`, `policy_engine_mode`, `open_in_new_tab` — cosmetic/non-functional attributes; the Authentik UI is the right place to edit these and drift on them isn't interesting. ## What is NOT in this change - Outpost-binding resource — the embedded outpost's provider list is a single-row many-to-many that the Authentik UI manages cleanly; adding TF there would fight the UI without reducing drift. - Property mappings and JWT federation source — managed via UI, drift suppressed. A future wave can bring them in when someone actually wants to edit them through code review. - Other Authentik entities (Flows, Stages, Groups, RBAC policies) — same rationale: UI is the natural editing surface. Adopt incrementally as they become interesting to code-review. ## Verification ``` $ cd stacks/authentik && ../../scripts/tg plan | grep Plan: Plan: 0 to add, 1 to change, 0 to destroy. # module.authentik.kubernetes_deployment.pgbouncer — pre-existing drift, # unrelated to this commit (image_pull_policy Always -> IfNotPresent) $ ../../scripts/tg state list | grep authentik_ authentik_application.catchall authentik_provider_proxy.catchall data.authentik_flow.default_authorization_implicit_consent data.authentik_flow.default_provider_invalidation ``` ## Reproduce locally 1. `git pull && cd stacks/authentik && ../../scripts/tg init` 2. Terraform pulls goauthentik/authentik provider (first time). 3. `tg plan` — expect only pgbouncer drift; authentik resources read-only. Refs: Wave 6a of the state-drift consolidation (code-hl1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:48:26 +00:00
# goauthentik/authentik Terraform provider.
#
# Adopted 2026-04-18 (Wave 6a of the state-drift consolidation plan) to bring
# the catch-all Proxy Provider — previously managed only via the Authentik UI
# — under Terraform management. API token lives in Vault
# `secret/authentik/tf_api_token` (token identifier `terraform-infra-stack`,
# intent API, user akadmin, no expiry). Required-providers declaration sits
# in the central terragrunt.hcl so every stack has it available; only this
# stack configures a provider block.
data "vault_kv_secret_v2" "authentik_tf" {
mount = "secret"
name = "authentik"
}
provider "authentik" {
url = "https://authentik.viktorbarzin.me"
token = data.vault_kv_secret_v2.authentik_tf.data["tf_api_token"]
}
data "authentik_flow" "default_authorization_implicit_consent" {
slug = "default-provider-authorization-implicit-consent"
}
data "authentik_flow" "default_provider_invalidation" {
slug = "default-provider-invalidation-flow"
}
# -----------------------------------------------------------------------------
# Catch-all Proxy Provider + Application.
#
# Created via the Authentik UI ~a year ago; adopted into Terraform 2026-04-18
# (Wave 6a). The proxy provider is consumed by the embedded outpost
# (uuid 0eecac07-97c7-443c-8925-05f2f4fe3e47) via an outpost-level binding
# that stays in the UI — it's a single toggle with no drift risk.
# -----------------------------------------------------------------------------
resource "authentik_application" "catchall" {
name = "Domain wide catch all"
slug = "domain-wide-catch-all"
protocol_provider = authentik_provider_proxy.catchall.id
lifecycle {
ignore_changes = [meta_description, meta_launch_url, meta_icon, group, backchannel_providers, policy_engine_mode, open_in_new_tab]
}
}
resource "authentik_provider_proxy" "catchall" {
name = "Provider for Domain wide catch all"
mode = "forward_domain"
external_host = "https://authentik.viktorbarzin.me"
cookie_domain = "viktorbarzin.me"
# Flow UUIDs resolved dynamically so a flow re-creation (keeping the slug)
# doesn't require an HCL edit.
authorization_flow = data.authentik_flow.default_authorization_implicit_consent.id
invalidation_flow = data.authentik_flow.default_provider_invalidation.id
# Cookie / proxysession TTL. Drives `Max-Age` on `authentik_proxy_*`
# cookies and the `expires` column in `authentik_providers_proxy_proxysession`.
# See note on the embedded outpost below — bumping this requires an outpost
# pod restart for the gorilla session store to rebind.
access_token_validity = "weeks=4"
[infra] Adopt Authentik catch-all Proxy Provider + Application into TF (Wave 6a) ## Context Wave 6a of the state-drift consolidation plan. The Domain wide catch all Proxy Provider (pk=5) + its wrapping Application (slug=domain-wide-catch-all) + the embedded outpost (uuid 0eecac07-97c7-443c-8925-05f2f4fe3e47) have run for a year as pure UI-created state. When the 2026-04-18 outpost SEV2 hit, it was harder to reason about the config than it should have been — the only source of truth was the Authentik admin UI. Bringing the provider + application under Terraform means future changes are reviewable in PRs and recoverable from git if the admin UI misbehaves. ## This change Adds the `goauthentik/authentik` provider to the repo's central `terragrunt.hcl` `required_providers` (side-effect: every stack can now declare authentik resources; this stack is the only current consumer). Stack-local `stacks/authentik/authentik_provider.tf` holds the provider instance configuration + API token wiring + two resources + their flow data-source lookups. ### Auth - API token stored in Vault at `secret/authentik/tf_api_token`, identifier `terraform-infra-stack`, intent=API, user=akadmin, no expiry. Rotatable by rewriting the Vault KV + any running TF apply picks it up on next plan. ### Imports (both landed zero-diff) - `authentik_application.catchall` ← id `domain-wide-catch-all` - `authentik_provider_proxy.catchall` ← id `5` ### Flow references Authorization + invalidation flows are looked up via `data "authentik_flow"` by slug (`default-provider-authorization-implicit-consent` + `default-provider-invalidation-flow`). Keeping them as data sources rather than hardcoded UUIDs means a flow recreation (slug unchanged) doesn't require an HCL edit. ### `lifecycle { ignore_changes }` scope On `authentik_provider_proxy.catchall`: - `property_mappings` (5 UUIDs), `jwt_federation_sources` (1 UUID) — the live state references complex many-to-many relations that are easier to manage from the Authentik UI than to serialise in HCL. Drift suppressed. - `skip_path_regex`, `internal_host`, all `basic_auth_*`, `intercept_header_auth`, `access_token_validity` — either defaults or UI-only tuning knobs that aren't part of Terraform's concern for this catch-all provider. On `authentik_application.catchall`: - `meta_description`, `meta_launch_url`, `meta_icon`, `group`, `backchannel_providers`, `policy_engine_mode`, `open_in_new_tab` — cosmetic/non-functional attributes; the Authentik UI is the right place to edit these and drift on them isn't interesting. ## What is NOT in this change - Outpost-binding resource — the embedded outpost's provider list is a single-row many-to-many that the Authentik UI manages cleanly; adding TF there would fight the UI without reducing drift. - Property mappings and JWT federation source — managed via UI, drift suppressed. A future wave can bring them in when someone actually wants to edit them through code review. - Other Authentik entities (Flows, Stages, Groups, RBAC policies) — same rationale: UI is the natural editing surface. Adopt incrementally as they become interesting to code-review. ## Verification ``` $ cd stacks/authentik && ../../scripts/tg plan | grep Plan: Plan: 0 to add, 1 to change, 0 to destroy. # module.authentik.kubernetes_deployment.pgbouncer — pre-existing drift, # unrelated to this commit (image_pull_policy Always -> IfNotPresent) $ ../../scripts/tg state list | grep authentik_ authentik_application.catchall authentik_provider_proxy.catchall data.authentik_flow.default_authorization_implicit_consent data.authentik_flow.default_provider_invalidation ``` ## Reproduce locally 1. `git pull && cd stacks/authentik && ../../scripts/tg init` 2. Terraform pulls goauthentik/authentik provider (first time). 3. `tg plan` — expect only pgbouncer drift; authentik resources read-only. Refs: Wave 6a of the state-drift consolidation (code-hl1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:48:26 +00:00
lifecycle {
ignore_changes = [property_mappings, jwt_federation_sources, skip_path_regex, internal_host, basic_auth_enabled, basic_auth_password_attribute, basic_auth_username_attribute, intercept_header_auth]
[infra] Adopt Authentik catch-all Proxy Provider + Application into TF (Wave 6a) ## Context Wave 6a of the state-drift consolidation plan. The Domain wide catch all Proxy Provider (pk=5) + its wrapping Application (slug=domain-wide-catch-all) + the embedded outpost (uuid 0eecac07-97c7-443c-8925-05f2f4fe3e47) have run for a year as pure UI-created state. When the 2026-04-18 outpost SEV2 hit, it was harder to reason about the config than it should have been — the only source of truth was the Authentik admin UI. Bringing the provider + application under Terraform means future changes are reviewable in PRs and recoverable from git if the admin UI misbehaves. ## This change Adds the `goauthentik/authentik` provider to the repo's central `terragrunt.hcl` `required_providers` (side-effect: every stack can now declare authentik resources; this stack is the only current consumer). Stack-local `stacks/authentik/authentik_provider.tf` holds the provider instance configuration + API token wiring + two resources + their flow data-source lookups. ### Auth - API token stored in Vault at `secret/authentik/tf_api_token`, identifier `terraform-infra-stack`, intent=API, user=akadmin, no expiry. Rotatable by rewriting the Vault KV + any running TF apply picks it up on next plan. ### Imports (both landed zero-diff) - `authentik_application.catchall` ← id `domain-wide-catch-all` - `authentik_provider_proxy.catchall` ← id `5` ### Flow references Authorization + invalidation flows are looked up via `data "authentik_flow"` by slug (`default-provider-authorization-implicit-consent` + `default-provider-invalidation-flow`). Keeping them as data sources rather than hardcoded UUIDs means a flow recreation (slug unchanged) doesn't require an HCL edit. ### `lifecycle { ignore_changes }` scope On `authentik_provider_proxy.catchall`: - `property_mappings` (5 UUIDs), `jwt_federation_sources` (1 UUID) — the live state references complex many-to-many relations that are easier to manage from the Authentik UI than to serialise in HCL. Drift suppressed. - `skip_path_regex`, `internal_host`, all `basic_auth_*`, `intercept_header_auth`, `access_token_validity` — either defaults or UI-only tuning knobs that aren't part of Terraform's concern for this catch-all provider. On `authentik_application.catchall`: - `meta_description`, `meta_launch_url`, `meta_icon`, `group`, `backchannel_providers`, `policy_engine_mode`, `open_in_new_tab` — cosmetic/non-functional attributes; the Authentik UI is the right place to edit these and drift on them isn't interesting. ## What is NOT in this change - Outpost-binding resource — the embedded outpost's provider list is a single-row many-to-many that the Authentik UI manages cleanly; adding TF there would fight the UI without reducing drift. - Property mappings and JWT federation source — managed via UI, drift suppressed. A future wave can bring them in when someone actually wants to edit them through code review. - Other Authentik entities (Flows, Stages, Groups, RBAC policies) — same rationale: UI is the natural editing surface. Adopt incrementally as they become interesting to code-review. ## Verification ``` $ cd stacks/authentik && ../../scripts/tg plan | grep Plan: Plan: 0 to add, 1 to change, 0 to destroy. # module.authentik.kubernetes_deployment.pgbouncer — pre-existing drift, # unrelated to this commit (image_pull_policy Always -> IfNotPresent) $ ../../scripts/tg state list | grep authentik_ authentik_application.catchall authentik_provider_proxy.catchall data.authentik_flow.default_authorization_implicit_consent data.authentik_flow.default_provider_invalidation ``` ## Reproduce locally 1. `git pull && cd stacks/authentik && ../../scripts/tg init` 2. Terraform pulls goauthentik/authentik provider (first time). 3. `tg plan` — expect only pgbouncer drift; authentik resources read-only. Refs: Wave 6a of the state-drift consolidation (code-hl1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:48:26 +00:00
}
}
# -----------------------------------------------------------------------------
# Embedded outpost record. Adopted into Terraform 2026-05-10 as part of the
# postgres-session-backend fix:
# - `managed` is set server-side to `goauthentik.io/outposts/embedded` so
# the outpost binary's `IsEmbedded()` check returns true → it loads the
# PostgreSQL session backend (PR #16628). The Terraform provider does
# NOT expose `managed` in the schema, so the field is preserved across
# applies (TF only writes fields it knows about).
# - kubernetes_json_patches.deployment carries:
# * dshm 2Gi tmpfs (covers the 2026-04-18 ENOSPC class of issues)
# * resources requests/limits
# * `app.kubernetes.io/component=server` pod label so the K8s service
# selector lights up endpoints (works around goauthentik 2026.2.2
# service.py:52 selector mismatch on standalone embedded outposts).
# * AUTHENTIK_POSTGRESQL__{HOST,PORT,USER,PASSWORD,NAME} envFrom the
# shared `goauthentik` Secret so the postgres session backend has
# credentials to connect to the dbaas cluster.
# - kubernetes_json_patches.service replaces the controller-set selector
# (which incorrectly targets `app.kubernetes.io/name=authentik`, i.e.
# the goauthentik-server pods) with the outpost's own labels.
# -----------------------------------------------------------------------------
resource "authentik_outpost" "embedded" {
name = "authentik Embedded Outpost"
type = "proxy"
protocol_providers = [authentik_provider_proxy.catchall.id]
service_connection = "99e227a7-4562-4888-9660-4c27da678c50"
config = jsonencode({
log_level = "trace"
docker_labels = null
authentik_host = "https://authentik.viktorbarzin.me/"
docker_network = null
container_image = null
docker_map_ports = true
refresh_interval = "minutes=5"
kubernetes_replicas = 1
kubernetes_namespace = "authentik"
authentik_host_browser = ""
object_naming_template = "ak-outpost-%(name)s"
authentik_host_insecure = false
kubernetes_service_type = "ClusterIP"
kubernetes_ingress_path_type = null
kubernetes_image_pull_secrets = []
kubernetes_ingress_class_name = null
kubernetes_disabled_components = []
kubernetes_ingress_annotations = {}
kubernetes_ingress_secret_name = "authentik-outpost-tls"
kubernetes_httproute_annotations = {}
kubernetes_httproute_parent_refs = []
kubernetes_json_patches = {
deployment = [
{
op = "add"
path = "/spec/template/spec/volumes"
value = [{ name = "dshm", emptyDir = { medium = "Memory", sizeLimit = "2Gi" } }]
},
{
op = "add"
path = "/spec/template/spec/containers/0/volumeMounts"
value = [{ name = "dshm", mountPath = "/dev/shm" }]
},
{
op = "add"
path = "/spec/template/spec/containers/0/resources"
value = { limits = { memory = "2560Mi" }, requests = { cpu = "100m", memory = "128Mi" } }
},
{
op = "add"
path = "/spec/template/metadata/labels/app.kubernetes.io~1component"
value = "server"
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = { name = "AUTHENTIK_POSTGRESQL__HOST", valueFrom = { secretKeyRef = { name = "goauthentik", key = "AUTHENTIK_POSTGRESQL__HOST" } } }
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = { name = "AUTHENTIK_POSTGRESQL__PORT", valueFrom = { secretKeyRef = { name = "goauthentik", key = "AUTHENTIK_POSTGRESQL__PORT" } } }
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = { name = "AUTHENTIK_POSTGRESQL__USER", valueFrom = { secretKeyRef = { name = "goauthentik", key = "AUTHENTIK_POSTGRESQL__USER" } } }
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = { name = "AUTHENTIK_POSTGRESQL__PASSWORD", valueFrom = { secretKeyRef = { name = "goauthentik", key = "AUTHENTIK_POSTGRESQL__PASSWORD" } } }
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = { name = "AUTHENTIK_POSTGRESQL__NAME", valueFrom = { secretKeyRef = { name = "goauthentik", key = "AUTHENTIK_POSTGRESQL__NAME" } } }
},
]
service = [
{
op = "replace"
path = "/spec/selector"
value = {
"app.kubernetes.io/managed-by" = "goauthentik.io"
"app.kubernetes.io/name" = "authentik-outpost-proxy"
"goauthentik.io/outpost-name" = "authentik-embedded-outpost"
"goauthentik.io/outpost-type" = "proxy"
"goauthentik.io/outpost-uuid" = "0eecac0797c7443c892505f2f4fe3e47"
}
},
]
}
})
}
# -----------------------------------------------------------------------------
# Default User Login stage — bound to default-authentication-flow.
# Adopted into Terraform 2026-05-01 to set session_duration=weeks=4 so users
# stay logged in across browser restarts. There is no Brand.session_duration
# in authentik 2026.2.x — UserLoginStage is the correct knob.
# -----------------------------------------------------------------------------
resource "authentik_stage_user_login" "default_login" {
name = "default-authentication-login"
session_duration = "weeks=4"
lifecycle {
# Pin only session_duration; everything else stays UI-managed so the
# plan doesn't churn unrelated knobs (e.g. remember_me_offset toggles).
ignore_changes = [
remember_me_offset,
terminate_other_sessions,
geoip_binding,
network_binding,
]
}
}