# 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//apps # {"id":"","name":"","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 } }