Files
homelab/terraform/zitadel/clients.tf
T

137 lines
5.3 KiB
Terraform
Raw Normal View History

# OIDC applications — one per consumer that logs a browser in.
#
# Client IDs: the plan §3.2 wants every client_id byte-identical after the move,
# so that a consumer change is only the issuer URL plus the secret.
#
# THAT GOAL IS NOT ACHIEVABLE HERE, verified twice on 2026-10-24:
# - ZITADEL v4.19.2 CreateOIDCApplicationRequest has no client_id field, and
# CreateOIDCApplicationResponse documents the value as "generated"
# (proto/zitadel/app/v2beta/app_service.proto:540-650).
# - zitadel provider 3.8.7 exposes no settable application id: `id` on
# `zitadel_application_v2` is not a valid argument (terraform validate
# rejects it), and `client_id` on `zitadel_application_oidc` is computed.
# Consequence: every consumer takes a NEW client_id as well as a new secret.
# Budget the extra config change per consumer — this is not the one-line
# re-point the plan assumed.
#
# The only way to keep the old value is to create each app through the v2 REST
# API with an explicit body id, then import it:
# POST /v2/projects/<project_id>/apps
# {"id":"<old-client-id>","name":"<old-client-id>","oidcRequest":{...}}
# Not implemented here: it would put app creation outside Terraform state.
#
# Secrets are generated by ZITADEL, never written here. `terraform output -json
# client_ids` and `client_secrets` feed the SOPS re-point (§3.2, §6.9). Do not
# commit the tfstate: it carries those values.
locals {
oidc_clients = {
# Gateway. Browser sessions for the console, plus a machine path
# (machines.tf) for callers that need client_credentials.
"api-gw" = {
redirect_uris = ["http://localhost:3000/callback", "https://api.riotpiao.com/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"argocd" = {
redirect_uris = ["http://localhost:8080/auth/callback", "https://argocd.riotpiao.com/auth/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"forgejo" = {
redirect_uris = ["http://localhost:3000/user/oauth2/authentik/callback", "https://forgejo.riotpiao.com/user/oauth2/authentik/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"grafana" = {
redirect_uris = ["http://localhost:3000/login/generic_oauth", "https://grafana.riotpiao.com/login/generic_oauth"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"homarr" = {
redirect_uris = ["http://localhost:7575/auth/callback", "https://homarr.riotpiao.com/auth/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"immich" = {
redirect_uris = ["app.immich:///oauth-callback", "https://img.riotpiao.com/auth/login", "https://img.riotpiao.com/user/oauth2/callback"]
app_type = "NATIVE"
auth_method = "BASIC"
dev_mode = false
}
"kubernetes" = {
redirect_uris = ["http://localhost:8000", "http://localhost:18000"]
app_type = "WEB"
auth_method = "NONE"
dev_mode = true
}
"minio" = {
redirect_uris = ["http://localhost:9000/auth/sso/oauth2/code", "https://minio.riotpiao.com/auth/sso/oauth2/code"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"paperless" = {
redirect_uris = ["http://localhost:8000/accounts/oidc/authentik/login/callback/", "https://paperless.riotpiao.com/accounts/oidc/authentik/login/callback/"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"poimen" = {
redirect_uris = ["http://localhost:3000/callback", "https://poimen.riotpiao.com/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"queue-sqs" = {
redirect_uris = ["http://localhost:8080/callback", "https://queue.riotpiao.com/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
"vault" = {
redirect_uris = ["http://localhost:8200/ui/vault/auth/oidc/oidc/callback", "https://vault.riotpiao.com/ui/vault/auth/oidc/oidc/callback"]
app_type = "WEB"
auth_method = "BASIC"
dev_mode = false
}
}
}
resource "zitadel_application_v2" "oidc" {
for_each = local.oidc_clients
org_id = data.zitadel_org.default.id
project_id = zitadel_project_v2.homelab.id
name = each.key
oidc {
redirect_uris = each.value.redirect_uris
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
grant_types = [
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
"OIDC_GRANT_TYPE_REFRESH_TOKEN",
]
app_type = "OIDC_APP_TYPE_${each.value.app_type}"
auth_method_type = "OIDC_AUTH_METHOD_TYPE_${each.value.auth_method}"
version = "OIDC_VERSION_1_0"
# JWT access tokens: Kubernetes RBAC, MinIO policy mapping and the Go
# gateway all read claims, and an opaque bearer token carries none.
access_token_type = "OIDC_TOKEN_TYPE_JWT"
# Assert roles without the client having to request the scope. Every
# consumer here maps roles or groups to local permissions.
access_token_role_assertion = true
id_token_role_assertion = true
id_token_userinfo_assertion = true
dev_mode = each.value.dev_mode
}
}