Files
infrapuzzle/terraform/authentik.tf
T

790 lines
25 KiB
Terraform

# --- 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
skip_path_regex = "^/service_worker\\.js$|^/\\.client/.*|^/manifest\\.json$"
access_token_validity = "days=7"
}
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 = <<EOF
if "infra-admins" in [group.name for group in request.user.groups.all()]:
return {"policy": "consoleAdmin"}
return {"policy": "readonly"}
EOF
}
resource "random_password" "minio_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "minio" {
name = "MinIO"
client_id = "minio"
client_secret = random_password.minio_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://minio-console.haumdaucher.de/oauth_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,
authentik_property_mapping_provider_scope.minio_policy.id,
]
}
resource "authentik_application" "minio" {
name = "MinIO"
slug = "minio"
protocol_provider = authentik_provider_oauth2.minio.id
}
output "minio_oauth_client_secret" {
description = "OAuth2 Client Secret for MinIO OIDC provider"
value = random_password.minio_oauth_client_secret.result
sensitive = true
}
# 5. Nextcloud
resource "random_password" "nextcloud_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "nextcloud" {
name = "Nextcloud"
client_id = "nextcloud"
client_secret = random_password.nextcloud_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://cloud.haumdaucher.de/apps/user_oidc/code"
}
]
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" "nextcloud" {
name = "Nextcloud"
slug = "nextcloud"
protocol_provider = authentik_provider_oauth2.nextcloud.id
}
output "nextcloud_oauth_client_secret" {
description = "OAuth2 Client Secret for Nextcloud OIDC provider"
value = random_password.nextcloud_oauth_client_secret.result
sensitive = true
}
# 6. Home Assistant
resource "random_password" "homeassistant_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "homeassistant" {
name = "Home Assistant"
client_id = "homeassistant"
client_secret = random_password.homeassistant_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://hass.moritzgraf.de/auth/oidc/callback"
},
{
matching_mode = "strict"
url = "https://hass.moritzgraf.de/auth/external/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" "homeassistant" {
name = "Home Assistant"
slug = "homeassistant"
protocol_provider = authentik_provider_oauth2.homeassistant.id
}
output "homeassistant_oauth_client_secret" {
description = "OAuth2 Client Secret for Home Assistant OIDC provider"
value = random_password.homeassistant_oauth_client_secret.result
sensitive = true
}
# --- Application Policy Bindings (Access Control & RBAC) ---
# 1. Admin-Only Applications (Strictly restricted to infra-admins)
resource "authentik_policy_binding" "openclaw_admin_only" {
target = authentik_application.openclaw.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "zigbee2mqtt_admin_only" {
target = authentik_application.zigbee2mqtt.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "prometheus_admin_only" {
target = authentik_application.prometheus.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "alertmanager_admin_only" {
target = authentik_application.alertmanager.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "nextcloud_phpmyadmin_admin_only" {
target = authentik_application.nextcloud_phpmyadmin.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "gitea_phpmyadmin_admin_only" {
target = authentik_application.gitea_phpmyadmin.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "hass_coder_admin_only" {
target = authentik_application.hass_coder.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "n8n_admin_only" {
target = authentik_application.n8n.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "coder_admin_only" {
target = authentik_application.coder.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "minio_admin_only" {
target = authentik_application.minio.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "silverbullet_admin_only" {
target = authentik_application.silverbullet.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "silverbullet_family" {
target = authentik_application.silverbullet.uuid
group = authentik_group.family.id
order = 1
}
# 2. Shared & Member Applications (Accessible to all authenticated users)
resource "authentik_policy_binding" "grafana_users" {
target = authentik_application.grafana.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "nextcloud_users" {
target = authentik_application.nextcloud.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "gitea_users" {
target = authentik_application.gitea.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "homeassistant_users" {
target = authentik_application.homeassistant.uuid
group = authentik_group.users.id
order = 0
}