Adds terraform/zitadel: the provisioning half the ArgoCD wiring left open. It declares 1 project, 19 project roles, 12 OIDC applications, 5 machine users plus keys, one API application for the gateway, project grants, per-agent claim metadata, and a disabled groups-claim action. Facts established while writing it: - Client IDs cannot be preserved. ZITADEL v4.19.2 CreateOIDCApplicationRequest has no client_id field, and provider 3.8.7 rejects a settable application id (terraform validate reports "Invalid or unknown key"). Plan §3.2 assumed a client_id-preserving re-point; every consumer now also changes client ID. - Role keys equal the existing group names, so the groups claim can be byte-identical and no RBAC file changes. v4.19.2 allows role keys of 1-200 characters with no character-set restriction. - Machine users get JWT profile keys, not passwords. Per-agent claims move to user metadata and reach tokens only through the groups-claim action. - The action stays gated: enable_groups_claim_action defaults to false because the action runtime API is unverified. Two validations stop a null script from reaching file(). Checks: terraform fmt -check -recursive clean, terraform validate 0 errors, plan reaches only the expected missing-credentials error, structural gate 0 error / 0 warning, and one reviewer round: BLOCK on a plan-time fileexists(null) and on the missing api-gw machine path, both fixed, re-reviewed to SIGN-OFF. State is local and git-ignored; it carries generated client secrets and machine keys. Move it to MinIO before a second operator applies. Not applied: no Zitadel instance is running yet.
137 lines
5.3 KiB
Terraform
137 lines
5.3 KiB
Terraform
# 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
|
|
}
|
|
}
|