# --- Authentik Terraform Configuration --- variable "authentik_url" { description = "Base URL of the Authentik instance" type = string default = "https://auth.haumdaucher.de" } variable "authentik_token" { description = "Authentik API Token (create via Directory > Tokens & App passwords)" type = string sensitive = true default = null } provider "authentik" { url = var.authentik_url token = var.authentik_token } # --- Branding: Haumdaucher Custom Theme & Assets --- resource "authentik_brand" "haumdaucher" { domain = "auth.haumdaucher.de" default = true branding_title = "HAUMDAUCHER" branding_logo = "https://haumdaucher.de/images/logo_classic.png" branding_favicon = "https://haumdaucher.de/icon-192.png" branding_custom_css = file("${path.module}/haumdaucher-theme.css") } # --- Directory: User Groups --- resource "authentik_group" "infra_admins" { name = "infra-admins" is_superuser = false } resource "authentik_group" "users" { name = "users" is_superuser = false } resource "authentik_group" "verein_mitglieder" { name = "verein-mitglieder" is_superuser = false } data "authentik_user" "amy" { username = "Amy" } resource "authentik_group" "family" { name = "family" is_superuser = false users = [data.authentik_user.amy.pk] } # --- Flows: Authorization, Invalidation & Custom Invitation Flow --- data "authentik_flow" "default_authorization_flow" { slug = "default-provider-authorization-implicit-consent" } data "authentik_flow" "default_invalidation_flow" { slug = "default-invalidation-flow" } # 1. Prompt Fields for Member Onboarding resource "authentik_stage_prompt_field" "haumdaucher_enrollment_username" { name = "haumdaucher-enrollment-username" field_key = "username" label = "Benutzername" type = "username" required = true order = 10 } resource "authentik_stage_prompt_field" "haumdaucher_enrollment_email" { name = "haumdaucher-enrollment-email" field_key = "email" label = "E-Mail-Adresse" type = "email" required = true order = 15 } resource "authentik_stage_prompt_field" "haumdaucher_enrollment_name" { name = "haumdaucher-enrollment-name" field_key = "name" label = "Vollständiger Name" type = "text" required = true order = 20 } resource "authentik_stage_prompt_field" "haumdaucher_enrollment_password" { name = "haumdaucher-enrollment-password" field_key = "password" label = "Passwort" type = "password" required = true order = 30 } resource "authentik_stage_prompt_field" "haumdaucher_enrollment_password_repeat" { name = "haumdaucher-enrollment-password-repeat" field_key = "password_repeat" label = "Passwort wiederholen" type = "password" required = true order = 40 } # 2. Prompt Stage resource "authentik_stage_prompt" "haumdaucher_enrollment_prompt" { name = "haumdaucher-enrollment-prompt" fields = [ authentik_stage_prompt_field.haumdaucher_enrollment_username.id, authentik_stage_prompt_field.haumdaucher_enrollment_email.id, authentik_stage_prompt_field.haumdaucher_enrollment_name.id, authentik_stage_prompt_field.haumdaucher_enrollment_password.id, authentik_stage_prompt_field.haumdaucher_enrollment_password_repeat.id, ] } # 3. Invitation Stage (gates the flow so only valid ?itoken=... can register) resource "authentik_stage_invitation" "haumdaucher_enrollment_invitation" { name = "haumdaucher-enrollment-invitation" continue_flow_without_invitation = false } # 4. User Write Stage (creates user in database and assigns default 'users' group) resource "authentik_stage_user_write" "haumdaucher_enrollment_write" { name = "haumdaucher-enrollment-write" create_users_as_inactive = false create_users_group = authentik_group.users.id } # 5. User Login Stage (starts the user session) resource "authentik_stage_user_login" "haumdaucher_enrollment_login" { name = "haumdaucher-enrollment-login" session_duration = "days=30" } # Master Flow: Default User Login Stage (30-day persistent browser session) import { to = authentik_stage_user_login.default_authentication_login id = "49749dd0-3c0d-41e2-9f10-e6c66d719901" } resource "authentik_stage_user_login" "default_authentication_login" { name = "default-authentication-login" session_duration = "days=30" } import { to = authentik_stage_user_login.default_source_authentication_login id = "7ff5a23d-6f78-4539-b40f-d7fb24af0ae7" } resource "authentik_stage_user_login" "default_source_authentication_login" { name = "default-source-authentication-login" session_duration = "days=30" } # 6. Redirect Stage (redirects user to haumdaucher.de after successful registration) resource "authentik_stage_redirect" "haumdaucher_enrollment_redirect" { name = "haumdaucher-enrollment-redirect" mode = "static" target_static = "https://haumdaucher.de" } # 7. Flow Definition resource "authentik_flow" "haumdaucher_invitation_flow" { name = "Haumdaucher Invitation Flow" slug = "haumdaucher-invitation-flow" title = "Haumdaucher Registrierung" designation = "enrollment" } # 8. Flow Stage Bindings (Execution Order) resource "authentik_flow_stage_binding" "bind_haumdaucher_invitation" { target = authentik_flow.haumdaucher_invitation_flow.uuid stage = authentik_stage_invitation.haumdaucher_enrollment_invitation.id order = 10 } resource "authentik_flow_stage_binding" "bind_haumdaucher_prompt" { target = authentik_flow.haumdaucher_invitation_flow.uuid stage = authentik_stage_prompt.haumdaucher_enrollment_prompt.id order = 20 } resource "authentik_flow_stage_binding" "bind_haumdaucher_write" { target = authentik_flow.haumdaucher_invitation_flow.uuid stage = authentik_stage_user_write.haumdaucher_enrollment_write.id order = 30 } resource "authentik_flow_stage_binding" "bind_haumdaucher_login" { target = authentik_flow.haumdaucher_invitation_flow.uuid stage = authentik_stage_user_login.haumdaucher_enrollment_login.id order = 40 } resource "authentik_flow_stage_binding" "bind_haumdaucher_redirect" { target = authentik_flow.haumdaucher_invitation_flow.uuid stage = authentik_stage_redirect.haumdaucher_enrollment_redirect.id order = 50 } # --- ForwardAuth Proxy Providers & Applications --- # 1. Zigbee2MQTT resource "authentik_provider_proxy" "zigbee2mqtt" { name = "Zigbee2MQTT" external_host = "https://zigbee2mqtt.moritzgraf.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "zigbee2mqtt" { name = "Zigbee2MQTT" slug = "zigbee2mqtt" protocol_provider = authentik_provider_proxy.zigbee2mqtt.id } # 2. Prometheus resource "authentik_provider_proxy" "prometheus" { name = "Prometheus" external_host = "https://prometheus.haumdaucher.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "prometheus" { name = "Prometheus" slug = "prometheus" protocol_provider = authentik_provider_proxy.prometheus.id } # 3. Alertmanager resource "authentik_provider_proxy" "alertmanager" { name = "Alertmanager" external_host = "https://alertmanager.haumdaucher.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "alertmanager" { name = "Alertmanager" slug = "alertmanager" protocol_provider = authentik_provider_proxy.alertmanager.id } # 4. Nextcloud phpMyAdmin resource "authentik_provider_proxy" "nextcloud_phpmyadmin" { name = "Nextcloud phpMyAdmin" external_host = "https://nextcloud.phpmyadmin.haumdaucher.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "nextcloud_phpmyadmin" { name = "Nextcloud phpMyAdmin" slug = "nextcloud-phpmyadmin" protocol_provider = authentik_provider_proxy.nextcloud_phpmyadmin.id } # 5. Gitea phpMyAdmin resource "authentik_provider_proxy" "gitea_phpmyadmin" { name = "Gitea phpMyAdmin" external_host = "https://gitea.phpmyadmin.haumdaucher.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "gitea_phpmyadmin" { name = "Gitea phpMyAdmin" slug = "gitea-phpmyadmin" protocol_provider = authentik_provider_proxy.gitea_phpmyadmin.id } # 6. Home Assistant VS Code resource "authentik_provider_proxy" "hass_coder" { name = "Home Assistant Code" external_host = "https://hass-coder.moritzgraf.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "hass_coder" { name = "Home Assistant Code" slug = "hass-coder" protocol_provider = authentik_provider_proxy.hass_coder.id } # 7. OpenClaw resource "authentik_provider_proxy" "openclaw" { name = "OpenClaw" external_host = "https://openclaw.haumdaucher.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "openclaw" { name = "OpenClaw" slug = "openclaw" protocol_provider = authentik_provider_proxy.openclaw.id } # 8. n8n resource "authentik_provider_proxy" "n8n" { name = "n8n" external_host = "https://n8n.moritzgraf.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "n8n" { name = "n8n" slug = "n8n" protocol_provider = authentik_provider_proxy.n8n.id } # 9. SilverBullet Wiki resource "authentik_provider_proxy" "silverbullet" { name = "SilverBullet" external_host = "https://wiki.moritzgraf.de" mode = "forward_single" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id intercept_header_auth = true } resource "authentik_application" "silverbullet" { name = "SilverBullet" slug = "silverbullet" protocol_provider = authentik_provider_proxy.silverbullet.id } # --- Outpost: Embedded Proxy Outpost --- resource "authentik_outpost" "embedded" { name = "authentik Embedded Outpost" type = "proxy" protocol_providers = [ authentik_provider_proxy.zigbee2mqtt.id, authentik_provider_proxy.prometheus.id, authentik_provider_proxy.alertmanager.id, authentik_provider_proxy.nextcloud_phpmyadmin.id, authentik_provider_proxy.gitea_phpmyadmin.id, authentik_provider_proxy.hass_coder.id, authentik_provider_proxy.openclaw.id, authentik_provider_proxy.n8n.id, authentik_provider_proxy.silverbullet.id, ] } # --- Machine & AI Assistant: Service Account & API Token --- resource "authentik_user" "ai_assistant" { username = "ai-assistant" name = "AI Assistant" type = "service_account" groups = [authentik_group.infra_admins.id] } resource "authentik_token" "ai_assistant_token" { identifier = "ai-assistant-api-token" user = authentik_user.ai_assistant.id intent = "api" expiring = false retrieve_key = true } # --- Native OIDC Providers & Applications --- # Scope Mappings data "authentik_property_mapping_provider_scope" "openid" { scope_name = "openid" } data "authentik_property_mapping_provider_scope" "email" { scope_name = "email" } data "authentik_property_mapping_provider_scope" "profile" { scope_name = "profile" } data "authentik_certificate_key_pair" "generated" { name = "authentik Self-signed Certificate" } # 1. Gitea resource "random_password" "gitea_oauth_client_secret" { length = 40 special = false } resource "authentik_provider_oauth2" "gitea" { name = "Gitea" client_id = "gitea" client_secret = random_password.gitea_oauth_client_secret.result client_type = "confidential" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id signing_key = data.authentik_certificate_key_pair.generated.id grant_types = ["authorization_code", "refresh_token"] allowed_redirect_uris = [ { matching_mode = "strict" url = "https://git.moritzgraf.de/user/oauth2/authentik/callback" }, { matching_mode = "strict" url = "https://git.moritzgraf.de/user/oauth2/Authentik/callback" } ] sub_mode = "user_username" include_claims_in_id_token = true property_mappings = [ data.authentik_property_mapping_provider_scope.openid.id, data.authentik_property_mapping_provider_scope.email.id, data.authentik_property_mapping_provider_scope.profile.id, ] } resource "authentik_application" "gitea" { name = "Gitea" slug = "gitea" protocol_provider = authentik_provider_oauth2.gitea.id } output "gitea_oauth_client_secret" { description = "OAuth2 Client Secret for Gitea OIDC provider" value = random_password.gitea_oauth_client_secret.result sensitive = true } # 2. Grafana resource "random_password" "grafana_oauth_client_secret" { length = 40 special = false } resource "authentik_provider_oauth2" "grafana" { name = "Grafana" client_id = "grafana" client_secret = random_password.grafana_oauth_client_secret.result client_type = "confidential" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id signing_key = data.authentik_certificate_key_pair.generated.id grant_types = ["authorization_code", "refresh_token"] allowed_redirect_uris = [ { matching_mode = "strict" url = "https://grafana.haumdaucher.de/login/generic_oauth" } ] sub_mode = "user_username" include_claims_in_id_token = true property_mappings = [ data.authentik_property_mapping_provider_scope.openid.id, data.authentik_property_mapping_provider_scope.email.id, data.authentik_property_mapping_provider_scope.profile.id, ] } resource "authentik_application" "grafana" { name = "Grafana" slug = "grafana" protocol_provider = authentik_provider_oauth2.grafana.id } output "grafana_oauth_client_secret" { description = "OAuth2 Client Secret for Grafana OIDC provider" value = random_password.grafana_oauth_client_secret.result sensitive = true } # 3. Coder v2 resource "random_password" "coder_oauth_client_secret" { length = 40 special = false } resource "authentik_provider_oauth2" "coder" { name = "Coder" client_id = "coder" client_secret = random_password.coder_oauth_client_secret.result client_type = "confidential" authorization_flow = data.authentik_flow.default_authorization_flow.id invalidation_flow = data.authentik_flow.default_invalidation_flow.id signing_key = data.authentik_certificate_key_pair.generated.id grant_types = ["authorization_code", "refresh_token"] allowed_redirect_uris = [ { matching_mode = "strict" url = "https://coder.moritzgraf.de/api/v2/users/oidc/callback" } ] sub_mode = "user_username" include_claims_in_id_token = true property_mappings = [ data.authentik_property_mapping_provider_scope.openid.id, data.authentik_property_mapping_provider_scope.email.id, data.authentik_property_mapping_provider_scope.profile.id, ] } resource "authentik_application" "coder" { name = "Coder" slug = "coder" protocol_provider = authentik_provider_oauth2.coder.id } output "coder_oauth_client_secret" { description = "OAuth2 Client Secret for Coder OIDC provider" value = random_password.coder_oauth_client_secret.result sensitive = true } # 4. MinIO Console resource "authentik_property_mapping_provider_scope" "minio_policy" { name = "minio-policy" scope_name = "policy" expression = <