ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
20 changed files with 145336 additions and 0 deletions
98
terraform/internal/zitadel/.terraform.lock.hcl
generated
Normal file
98
terraform/internal/zitadel/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/random" {
|
||||
version = "3.9.0"
|
||||
constraints = "~> 3.8"
|
||||
hashes = [
|
||||
"h1:8EQU5KSxezcjo/phRSe69rDOI0lk4pSaggj7FsskYp8=",
|
||||
"h1:Lw9im2VBBJQ3RyAbHPQ0rcvcmmcZWm3x+kIOpN+Tv9s=",
|
||||
"h1:U8KXqGCoNI9/guYbTvzgdtVk3fRthoG0UXwm1JoEpIs=",
|
||||
"h1:YXaVd4p6qXPPVaxIBaIDNXmBwT02ZqDn0qD+tYpw8sA=",
|
||||
"h1:cOpc03fphEt/G9Rfc4jLL/fW0D7tgvlXqiDKPF4vuww=",
|
||||
"h1:g09RR7T1xWkeGrZwWvWMT9ncJrFGr1k3CBD585UmO7w=",
|
||||
"h1:gGDdPPibmw2EWROx+sh1RGLjR5+nPwZyrf6/N9jXfeM=",
|
||||
"h1:haE7/nXCOhXKP4oXeEnER3t5CaVQWqujz4nBnpeTUv4=",
|
||||
"h1:ieSVpfZS2lKuMr05ph0QsOVpCzg7uk3cgKBaXR+Ikug=",
|
||||
"h1:ig2s1IS9IzehorRjvVAnKIsUUj8fkgyxct1L/kswcc4=",
|
||||
"h1:j3lS+ZEERFnoab8t1ppDrScGVP/cgWbzlCrEYKTCXYw=",
|
||||
"h1:lxezrKmOiQIySHAM+os8qLVq7hqufDr8h3Hpzvsk+78=",
|
||||
"h1:lzRqBJAG+NETxHbEZUJ/YP3RMEjZBinTX7VmgH3lw60=",
|
||||
"h1:tdSNWK5ApqUsgbdYieyeYLTu6nIZUV3hR1oFqUfAuGo=",
|
||||
"h1:xedet8yH/zI2CfdxsGlK0nlFWc/Bp61yrWsEa3fHB8g=",
|
||||
"zh:03f1114cc20b8913523735ab76e0f0a2b16ce13c92923a53304bf85f07fc0dbc",
|
||||
"zh:105b678ee72322a3067f105d7e05e940f6143238f377f6e87ff4ec909246ac2a",
|
||||
"zh:55f3bbf13ea18cbace61a706566a80f25f33fe2b1780b6f3d7b582af2a05b6d2",
|
||||
"zh:63adf996db48f082f7a6351eb485e219cd88795fc71e6ec60a837263ab0d2cb1",
|
||||
"zh:7e99550738a4e3cc68b8a467714b0d69371025fe95e3326d5323d026d55653e9",
|
||||
"zh:8342b54af3a18a37e075eeae61be57f4de2ba71b35d95c5075d402dd2c1f289d",
|
||||
"zh:83ee18e32ac9dd5fc91298554b7c4cfa4c3a1db50f4c797945637cc93c0844ae",
|
||||
"zh:993ecc0adbf6bd535a59fbc9b735d8c33950e6f6eb5e621d750da9b71d65d80a",
|
||||
"zh:ad722bc59d4edbf1415e827fc007c0efe6e0e9462d5568bae20b34be1058a261",
|
||||
"zh:ae9448e1f87b2f9a6c5197a0e9862162ec6b137cb3a3835e11522995d8939e7c",
|
||||
"zh:bc9cdd3aac784f759125c6627f6f6416e8726a1c184eb9cf3e55b9edbc94c627",
|
||||
"zh:c8e35b89572ba1c40a9b20022e033a3395fb8d42e7604d50c900f193ba10382e",
|
||||
"zh:e2deaa8a9975ef81d9f62baed12c41286918b0a10908e0e031f13f69a3b730a1",
|
||||
"zh:ee39707557210a0ab1098aa357d2cdfe502e5a312d0dbdffb09d08facc4d3fc5",
|
||||
"zh:f81afe4eb63e8aa9e0ea71be6c990f0dc69cb360e7191c0742a991f4a5081b64",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/vault" {
|
||||
version = "5.8.0"
|
||||
constraints = "~> 5.0"
|
||||
hashes = [
|
||||
"h1:/OZCcd0X7F9y846SOlVUMItMtLDBwasYe+Mv3HF2I1Q=",
|
||||
"h1:68t9TdU3ap9L+Ayewb478BIyv8rpEDIQxYq1xsq99fs=",
|
||||
"h1:6MmwOTMo0TpjoMs27LNKfxD+kZypRclB2WrV1OgbWD4=",
|
||||
"h1:8DpFTiNTio1Yal+kozfxkPabRvN8ucRyjVkRcKuCnCo=",
|
||||
"h1:B1DuZ215BVQbK/70wahsWR6Wy16LMicdRCuThNgeBOg=",
|
||||
"h1:EhuYG2XYdV7d3RuOB/r/KKv0k5XXtDoBMgrzPraH8yQ=",
|
||||
"h1:FIRP0mpZpyDppetTZxzmaH2kd2E0o8As9w2CANYsEhE=",
|
||||
"h1:KuT1QZ3kyjqZ/YENs5ABfhSHEBLr/01ZWzWbJrS+vDo=",
|
||||
"h1:SMqA7BA2Nv/R4O8oVi8arc1E2NXz56dcnp54vFG9sfg=",
|
||||
"h1:TO99KdU92KULAbT4D0CJSeGFLuEsOgR10d6qFnR3dDk=",
|
||||
"h1:Y3HaoTjTuH4UePPja1vDrRxrSTNhR7wq//qJsJA7/rQ=",
|
||||
"h1:bd5YeFG+ok71bEUWdvksBLPWBWVpeosSl0rbbDJAigE=",
|
||||
"h1:eSjvykpglWlaCF/77dluIgXQ/SbPlh7RSSuLjAYNZUs=",
|
||||
"h1:lUnHYFRPEO+C+DwumShhX7va0iY9ApkhoSSj/P7vCsk=",
|
||||
"h1:qFAesj3MEaAzGFL2tvV+uy/biNwGJUculuN5CFtTh1E=",
|
||||
"zh:1c9d0b740520a88a16ccc57c6bcb1a58e47b6ba19676428b2a0f5b9fe4004788",
|
||||
"zh:274ad5bad107a845927773a6a31a45cd0ed7916fa52b86d85c86c535b59fcea4",
|
||||
"zh:2b2eef2cb6840c3cf41134b1d1b6d3d50d6edbe3b2af2318bf7101843072c83b",
|
||||
"zh:42b607a2983e77a47099b5ac8550a25ecd5ba03760be19749a4ad63617ea6085",
|
||||
"zh:544877ac091e54b2954f5638edf06d76f344eb6833fb68db961c0edb5b0fdc83",
|
||||
"zh:812bd7730efb3781fcfc7442650551d30b015f931a132ae9acdffdd625526908",
|
||||
"zh:8798d164f8e25f3b211be45e79d3bd5272e9237e728942c0026300b25798e5fe",
|
||||
"zh:91201a2e5dbc88013977960f0d1139fb584cdcda1cfd27577980d8cf0a31ead3",
|
||||
"zh:a532662249b28e10a065bbab467d4ea9a12719638fe0f7e0add05d8d11142366",
|
||||
"zh:ab91b32cbcc71cd9e89cca7d88957d9410f54750f7ea60f1416d2d77b478006a",
|
||||
"zh:ae5658b04277318c8b91c0a9ed2ec05dd324a7501c1a057d53e79b0e1978a1f6",
|
||||
"zh:e02581fca392f46738a95999d5fbccca7d82500df3a88e16ae9668633eb16f9b",
|
||||
"zh:e89f59329ee939b59158c6173484ef4376da44296f6153d2fbafd3e28bd650c5",
|
||||
"zh:e9ac49b94293cb5c8b31eb6e5afddc6134ffff0481ec195b242e0175e1f6766f",
|
||||
"zh:ef60d5715c3b82ae64643dea9b4c1a82c10f72151c77af2749d3ceb060bb2adb",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/zitadel/zitadel" {
|
||||
version = "3.4.0"
|
||||
constraints = "~> 3.4"
|
||||
hashes = [
|
||||
"h1:yOyFsAgb3eBX/Ca13i7X88pmEXAp3+idVDjDilZtXYw=",
|
||||
"zh:011a42ed078750c87c3a1d58f3090518603d837e29a8dea5badad36fb804309e",
|
||||
"zh:1a6df05721c68e586110fd7c144d38379857fde7754598bb0364d1ca98b904c4",
|
||||
"zh:32745b9ae6d6259d3eb49aeb2be34c1d6c48de475ab9e70302353af518f9381d",
|
||||
"zh:3644e04087b01e63c8118c3dd4da02c6289ef9ec12ad9d4a32c9448884e6d5ff",
|
||||
"zh:46a505939c0b9c137ab002e8d7d15a7ee8598d2d32d75ce858f1da5ee7f5dc19",
|
||||
"zh:67dff0fe46d064ebf8aa352f065db964450dc5fd1159aa6e634340efcda54461",
|
||||
"zh:9266c35fcd280c4b55eb4e702df974943372ac8bd691bfa8994c9dfa59021be1",
|
||||
"zh:946419f83cc23418fcb95c93fb9345ed29222a64c24307262a17624223c6f001",
|
||||
"zh:a33d53acc640dc93b81352ba633cf392bc8c7614a72d320d59d3dcdb22d73fc4",
|
||||
"zh:a5e6410dd02e614ef699d3451d3f28dc48da0607acedcab306dfa5468d4ec9fd",
|
||||
"zh:aa811343722e7b8d65cfe208fb15b382f386b7a9db052e09a78e6289908ee67b",
|
||||
"zh:acd2f07a8df7911a9ab63a57dec9717656207f84de988ff64cd7195e73820d12",
|
||||
"zh:c9f1b60d9c4245a96dac2a3a3f56f7a1637f0585af68747c6d7762e3c1b7179d",
|
||||
"zh:e3c33dd2335712ce811bf84414a3da76cb143c0118d385389028032ff68b6c9d",
|
||||
]
|
||||
}
|
||||
44
terraform/internal/zitadel/forgejo-public.tf
Normal file
44
terraform/internal/zitadel/forgejo-public.tf
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
locals {
|
||||
forgejo_public_oidc_redirect_uri = "${trim(var.forgejo_public_base_url, "/")}/user/oauth2/${var.forgejo_public_oauth_provider_name}/callback"
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "forgejo_public" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Public Forgejo"
|
||||
redirect_uris = [local.forgejo_public_oidc_redirect_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.forgejo_public_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "forgejo_public_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.forgejo_public_oidc_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
auth_name = var.forgejo_public_oauth_provider_name
|
||||
key = zitadel_application_oidc.forgejo_public.client_id
|
||||
secret = zitadel_application_oidc.forgejo_public.client_secret
|
||||
client_id = zitadel_application_oidc.forgejo_public.client_id
|
||||
client_secret = zitadel_application_oidc.forgejo_public.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
roles_claim = local.forgejo_roles_claim
|
||||
redirect_uri = local.forgejo_public_oidc_redirect_uri
|
||||
})
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [data_json]
|
||||
}
|
||||
}
|
||||
71
terraform/internal/zitadel/forgejo.tf
Normal file
71
terraform/internal/zitadel/forgejo.tf
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
locals {
|
||||
forgejo_oidc_redirect_uri = "${trim(var.forgejo_base_url, "/")}/user/oauth2/${var.forgejo_oauth_provider_name}/callback"
|
||||
forgejo_roles_claim = "groups"
|
||||
|
||||
forgejo_zitadel_roles = {
|
||||
forgejo_user = {
|
||||
display_name = "Forgejo user"
|
||||
group = "Forgejo"
|
||||
}
|
||||
forgejo_admin = {
|
||||
display_name = "Forgejo administrator"
|
||||
group = "Forgejo"
|
||||
}
|
||||
forgejo_restricted = {
|
||||
display_name = "Forgejo restricted user"
|
||||
group = "Forgejo"
|
||||
}
|
||||
}
|
||||
|
||||
forgejo_zitadel_role_keys = keys(local.forgejo_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "forgejo" {
|
||||
for_each = local.forgejo_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "forgejo" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Forgejo"
|
||||
redirect_uris = [local.forgejo_oidc_redirect_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.forgejo_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "forgejo_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.forgejo_oidc_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
auth_name = var.forgejo_oauth_provider_name
|
||||
key = zitadel_application_oidc.forgejo.client_id
|
||||
secret = zitadel_application_oidc.forgejo.client_secret
|
||||
client_id = zitadel_application_oidc.forgejo.client_id
|
||||
client_secret = zitadel_application_oidc.forgejo.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
roles_claim = local.forgejo_roles_claim
|
||||
redirect_uri = local.forgejo_oidc_redirect_uri
|
||||
})
|
||||
|
||||
# Vault is source of truth after first write. OIDC credential rotation is explicit.
|
||||
lifecycle { ignore_changes = [data_json] }
|
||||
}
|
||||
47
terraform/internal/zitadel/frigate.tf
Normal file
47
terraform/internal/zitadel/frigate.tf
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
locals {
|
||||
frigate_oauth2_proxy_redirect_uri = "${trim(var.frigate_base_url, "/")}/oauth2/callback"
|
||||
|
||||
frigate_zitadel_roles = {
|
||||
frigate_viewer = {
|
||||
display_name = "Frigate viewer"
|
||||
group = "Frigate"
|
||||
}
|
||||
frigate_admin = {
|
||||
display_name = "Frigate administrator"
|
||||
group = "Frigate"
|
||||
}
|
||||
}
|
||||
|
||||
frigate_zitadel_role_keys = keys(local.frigate_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "frigate" {
|
||||
for_each = local.frigate_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "frigate" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Frigate"
|
||||
redirect_uris = [local.frigate_oauth2_proxy_redirect_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.frigate_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
68
terraform/internal/zitadel/grafana.tf
Normal file
68
terraform/internal/zitadel/grafana.tf
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
locals {
|
||||
grafana_oidc_redirect_uri = "${trim(var.grafana_base_url, "/")}/login/generic_oauth"
|
||||
|
||||
grafana_zitadel_roles = {
|
||||
grafana_viewer = {
|
||||
display_name = "Grafana viewer"
|
||||
group = "Grafana"
|
||||
}
|
||||
grafana_editor = {
|
||||
display_name = "Grafana editor"
|
||||
group = "Grafana"
|
||||
}
|
||||
grafana_admin = {
|
||||
display_name = "Grafana administrator"
|
||||
group = "Grafana"
|
||||
}
|
||||
}
|
||||
|
||||
grafana_zitadel_role_keys = keys(local.grafana_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "grafana" {
|
||||
for_each = local.grafana_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "grafana" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Grafana"
|
||||
redirect_uris = [local.grafana_oidc_redirect_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.grafana_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "grafana_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.grafana_oidc_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_ID = zitadel_application_oidc.grafana.client_id
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET = zitadel_application_oidc.grafana.client_secret
|
||||
client_id = zitadel_application_oidc.grafana.client_id
|
||||
client_secret = zitadel_application_oidc.grafana.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
redirect_uri = local.grafana_oidc_redirect_uri
|
||||
})
|
||||
|
||||
# Vault is source of truth after first write. OIDC credential rotation is explicit.
|
||||
lifecycle { ignore_changes = [data_json] }
|
||||
}
|
||||
42
terraform/internal/zitadel/grants.tf
Normal file
42
terraform/internal/zitadel/grants.tf
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
locals {
|
||||
service_admin_zitadel_user_grants = {
|
||||
(var.service_admin_zitadel_user_id) = var.service_admin_zitadel_role_keys
|
||||
}
|
||||
|
||||
service_grant_user_ids = setunion(
|
||||
keys(local.service_admin_zitadel_user_grants),
|
||||
keys(var.frigate_zitadel_user_grants),
|
||||
keys(var.forgejo_zitadel_user_grants),
|
||||
keys(var.grafana_zitadel_user_grants),
|
||||
keys(var.longhorn_zitadel_user_grants),
|
||||
keys(var.quay_zitadel_user_grants),
|
||||
)
|
||||
|
||||
service_zitadel_user_grants = {
|
||||
for user_id in local.service_grant_user_ids : user_id => toset(flatten([
|
||||
tolist(lookup(local.service_admin_zitadel_user_grants, user_id, toset([]))),
|
||||
tolist(lookup(var.frigate_zitadel_user_grants, user_id, toset([]))),
|
||||
tolist(lookup(var.forgejo_zitadel_user_grants, user_id, toset([]))),
|
||||
tolist(lookup(var.grafana_zitadel_user_grants, user_id, toset([]))),
|
||||
tolist(lookup(var.longhorn_zitadel_user_grants, user_id, toset([]))),
|
||||
tolist(lookup(var.quay_zitadel_user_grants, user_id, toset([]))),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "services" {
|
||||
for_each = local.service_zitadel_user_grants
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
user_id = each.key
|
||||
role_keys = each.value
|
||||
|
||||
depends_on = [
|
||||
zitadel_project_role.forgejo,
|
||||
zitadel_project_role.frigate,
|
||||
zitadel_project_role.grafana,
|
||||
zitadel_project_role.longhorn,
|
||||
zitadel_project_role.quay,
|
||||
]
|
||||
}
|
||||
69
terraform/internal/zitadel/hf-chat-ui.tf
Normal file
69
terraform/internal/zitadel/hf-chat-ui.tf
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
locals {
|
||||
hf_callback = "https://chat.quorate.internal/login/callback"
|
||||
}
|
||||
|
||||
resource "zitadel_project" "hf_chat_ui" {
|
||||
org_id = zitadel_org.primary.id
|
||||
name = "HF Chat UI"
|
||||
project_role_assertion = true
|
||||
project_role_check = true
|
||||
has_project_check = false
|
||||
private_labeling_setting = "PRIVATE_LABELING_SETTING_UNSPECIFIED"
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "hf_chat_ui" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.hf_chat_ui.id
|
||||
role_key = "hf_chat_ui_user"
|
||||
display_name = "HF Chat UI user"
|
||||
group = "HF Chat UI"
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "hf_chat_ui" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.hf_chat_ui.id
|
||||
name = "HF Chat UI"
|
||||
redirect_uris = [local.hf_callback]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = ["https://chat.quorate.internal"]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = false
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "hf_chat_ui_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = "hf-chat-ui/oidc/zitadel"
|
||||
|
||||
data_json = jsonencode({
|
||||
OPENID_CLIENT_ID = zitadel_application_oidc.hf_chat_ui.client_id
|
||||
OPENID_CLIENT_SECRET = zitadel_application_oidc.hf_chat_ui.client_secret
|
||||
client_id = zitadel_application_oidc.hf_chat_ui.client_id
|
||||
client_secret = zitadel_application_oidc.hf_chat_ui.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
redirect_uri = local.hf_callback
|
||||
})
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [data_json]
|
||||
replace_triggered_by = [
|
||||
zitadel_application_oidc.hf_chat_ui,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "hf_chat_ui" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.hf_chat_ui.id
|
||||
user_id = var.service_admin_zitadel_user_id
|
||||
role_keys = [zitadel_project_role.hf_chat_ui.role_key]
|
||||
}
|
||||
13
terraform/internal/zitadel/iam.tf
Normal file
13
terraform/internal/zitadel/iam.tf
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
resource "zitadel_machine_user" "iam_admin" {
|
||||
org_id = "369418441481847110"
|
||||
user_name = "iam-admin"
|
||||
name = "Automatically Initialized IAM Admin"
|
||||
description = "IAM admin"
|
||||
access_token_type = "ACCESS_TOKEN_TYPE_BEARER"
|
||||
with_secret = false
|
||||
}
|
||||
|
||||
import {
|
||||
to = zitadel_machine_user.iam_admin
|
||||
id = "369418441482371398:false:369418441481847110"
|
||||
}
|
||||
21
terraform/internal/zitadel/login_policy.tf
Normal file
21
terraform/internal/zitadel/login_policy.tf
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
resource "zitadel_default_login_policy" "instance" {
|
||||
user_login = true
|
||||
allow_register = false
|
||||
allow_external_idp = true
|
||||
force_mfa = false
|
||||
force_mfa_local_only = false
|
||||
passwordless_type = "PASSWORDLESS_TYPE_ALLOWED"
|
||||
hide_password_reset = false
|
||||
ignore_unknown_usernames = false
|
||||
allow_domain_discovery = false
|
||||
disable_login_with_email = false
|
||||
disable_login_with_phone = false
|
||||
default_redirect_uri = "https://${var.zitadel_domain}/ui/console"
|
||||
password_check_lifetime = "240h0m0s"
|
||||
external_login_check_lifetime = "240h0m0s"
|
||||
multi_factor_check_lifetime = "12h0m0s"
|
||||
mfa_init_skip_lifetime = "720h0m0s"
|
||||
second_factor_check_lifetime = "18h0m0s"
|
||||
second_factors = ["SECOND_FACTOR_TYPE_OTP", "SECOND_FACTOR_TYPE_U2F"]
|
||||
multi_factors = ["MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION"]
|
||||
}
|
||||
66
terraform/internal/zitadel/longhorn.tf
Normal file
66
terraform/internal/zitadel/longhorn.tf
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
locals {
|
||||
longhorn_oauth2_proxy_redirect_uri = "${trim(var.longhorn_base_url, "/")}/oauth2/callback"
|
||||
|
||||
longhorn_zitadel_roles = {
|
||||
longhorn_admin = {
|
||||
display_name = "Longhorn administrator"
|
||||
group = "Longhorn"
|
||||
}
|
||||
}
|
||||
|
||||
longhorn_zitadel_role_keys = keys(local.longhorn_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "longhorn" {
|
||||
for_each = local.longhorn_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "longhorn" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Longhorn"
|
||||
redirect_uris = [local.longhorn_oauth2_proxy_redirect_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.longhorn_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "random_password" "longhorn_oauth2_proxy_cookie_secret" {
|
||||
length = 32
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "longhorn_oauth2_proxy" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.longhorn_oauth2_proxy_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
OAUTH2_PROXY_CLIENT_ID = zitadel_application_oidc.longhorn.client_id
|
||||
OAUTH2_PROXY_CLIENT_SECRET = zitadel_application_oidc.longhorn.client_secret
|
||||
OAUTH2_PROXY_COOKIE_SECRET = random_password.longhorn_oauth2_proxy_cookie_secret.result
|
||||
client_id = zitadel_application_oidc.longhorn.client_id
|
||||
client_secret = zitadel_application_oidc.longhorn.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
redirect_uri = local.longhorn_oauth2_proxy_redirect_uri
|
||||
})
|
||||
|
||||
# Vault is source of truth after first write. OIDC credential rotation is explicit.
|
||||
lifecycle { ignore_changes = [data_json] }
|
||||
}
|
||||
14
terraform/internal/zitadel/moved.tf
Normal file
14
terraform/internal/zitadel/moved.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
moved {
|
||||
from = zitadel_action.forgejo_roles_claim
|
||||
to = zitadel_action.service_roles_claim
|
||||
}
|
||||
|
||||
moved {
|
||||
from = zitadel_trigger_actions.forgejo_roles_claim_userinfo
|
||||
to = zitadel_trigger_actions.service_roles_claim_userinfo
|
||||
}
|
||||
|
||||
moved {
|
||||
from = zitadel_trigger_actions.forgejo_roles_claim_access_token
|
||||
to = zitadel_trigger_actions.service_roles_claim_access_token
|
||||
}
|
||||
4
terraform/internal/zitadel/org.tf
Normal file
4
terraform/internal/zitadel/org.tf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
resource "zitadel_org" "primary" {
|
||||
name = var.zitadel_org_name
|
||||
org_id = var.zitadel_org_id
|
||||
}
|
||||
129
terraform/internal/zitadel/outputs.tf
Normal file
129
terraform/internal/zitadel/outputs.tf
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
output "zitadel_org_id" {
|
||||
description = "ID of the primary service ZITADEL organization."
|
||||
value = zitadel_org.primary.id
|
||||
}
|
||||
|
||||
output "zitadel_org_primary_domain" {
|
||||
description = "Primary domain assigned by ZITADEL for the primary service organization."
|
||||
value = zitadel_org.primary.primary_domain
|
||||
}
|
||||
|
||||
output "zitadel_project_id" {
|
||||
description = "ID of the ZITADEL project that groups service clients."
|
||||
value = zitadel_project.services.id
|
||||
}
|
||||
|
||||
output "forgejo_oidc_authentication_name" {
|
||||
description = "Authentication source name to use in Forgejo."
|
||||
value = var.forgejo_oauth_provider_name
|
||||
}
|
||||
|
||||
output "forgejo_oidc_discovery_url" {
|
||||
description = "OpenID Connect discovery URL for Forgejo."
|
||||
value = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
}
|
||||
|
||||
output "forgejo_oidc_redirect_uri" {
|
||||
description = "Redirect URI registered in the Forgejo ZITADEL application."
|
||||
value = local.forgejo_oidc_redirect_uri
|
||||
}
|
||||
|
||||
output "forgejo_oidc_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing Forgejo's ZITADEL OIDC client material."
|
||||
value = "${var.vault_kv_mount}/data/${var.forgejo_oidc_vault_path}"
|
||||
}
|
||||
|
||||
output "forgejo_zitadel_roles_claim" {
|
||||
description = "Flat ZITADEL custom claim reserved for Forgejo group/admin mapping."
|
||||
value = local.forgejo_roles_claim
|
||||
}
|
||||
|
||||
output "forgejo_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used to authorize Forgejo sign-in."
|
||||
value = local.forgejo_zitadel_role_keys
|
||||
}
|
||||
|
||||
output "frigate_oauth2_proxy_redirect_uri" {
|
||||
description = "Redirect URI registered in the Frigate ZITADEL application."
|
||||
value = local.frigate_oauth2_proxy_redirect_uri
|
||||
}
|
||||
|
||||
output "frigate_oauth2_client_id" {
|
||||
description = "Client ID generated for the Frigate ZITADEL application."
|
||||
value = nonsensitive(zitadel_application_oidc.frigate.client_id)
|
||||
}
|
||||
|
||||
output "frigate_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used for Frigate role mapping."
|
||||
value = local.frigate_zitadel_role_keys
|
||||
}
|
||||
|
||||
output "forgejo_public_oidc_redirect_uri" {
|
||||
description = "Redirect URI registered in the public Forgejo ZITADEL application."
|
||||
value = local.forgejo_public_oidc_redirect_uri
|
||||
}
|
||||
|
||||
output "forgejo_public_oidc_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing public Forgejo's ZITADEL OIDC client material."
|
||||
value = "${var.vault_kv_mount}/data/${var.forgejo_public_oidc_vault_path}"
|
||||
}
|
||||
|
||||
output "grafana_oidc_redirect_uri" {
|
||||
description = "Redirect URI registered in the Grafana ZITADEL application."
|
||||
value = local.grafana_oidc_redirect_uri
|
||||
}
|
||||
|
||||
output "grafana_oidc_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing Grafana's ZITADEL OIDC client material."
|
||||
value = "${var.vault_kv_mount}/data/${var.grafana_oidc_vault_path}"
|
||||
}
|
||||
|
||||
output "grafana_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used for Grafana role mapping."
|
||||
value = local.grafana_zitadel_role_keys
|
||||
}
|
||||
|
||||
output "longhorn_oauth2_proxy_redirect_uri" {
|
||||
description = "Redirect URI registered in the Longhorn ZITADEL application."
|
||||
value = local.longhorn_oauth2_proxy_redirect_uri
|
||||
}
|
||||
|
||||
output "longhorn_oauth2_proxy_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing Longhorn's oauth2-proxy OIDC client material."
|
||||
value = "${var.vault_kv_mount}/data/${var.longhorn_oauth2_proxy_vault_path}"
|
||||
}
|
||||
|
||||
output "longhorn_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used to authorize Longhorn UI access."
|
||||
value = local.longhorn_zitadel_role_keys
|
||||
}
|
||||
|
||||
output "stalwart_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used to authorize Stalwart administration."
|
||||
value = local.stalwart_zitadel_role_keys
|
||||
}
|
||||
|
||||
output "stalwart_oidc_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing Stalwart's ZITADEL resource-server metadata."
|
||||
value = "${var.vault_kv_mount}/data/${var.stalwart_oidc_vault_path}"
|
||||
}
|
||||
|
||||
output "stalwart_account_webui_client_id" {
|
||||
description = "Generated public OIDC client ID embedded in the Stalwart account WebUI build."
|
||||
value = nonsensitive(zitadel_application_oidc.stalwart_account_webui.client_id)
|
||||
}
|
||||
|
||||
output "quay_oidc_redirect_uri" {
|
||||
description = "Redirect URI registered in the Quay ZITADEL application."
|
||||
value = local.quay_oidc_redirect_uri
|
||||
}
|
||||
|
||||
output "quay_oidc_vault_secret_path" {
|
||||
description = "Vault KV v2 path containing Quay's ZITADEL OIDC client material."
|
||||
value = "${var.vault_kv_mount}/data/${var.quay_oidc_vault_path}"
|
||||
}
|
||||
|
||||
output "quay_zitadel_role_keys" {
|
||||
description = "ZITADEL project role keys used to authorize Quay sign-in."
|
||||
value = local.quay_zitadel_role_keys
|
||||
}
|
||||
61
terraform/internal/zitadel/providers.tf
Normal file
61
terraform/internal/zitadel/providers.tf
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
terraform {
|
||||
required_version = "< 2.0.0"
|
||||
backend "pg" {
|
||||
conn_str = "postgres://10.234.158.153:5432/tf_state?sslmode=require"
|
||||
schema_name = "internal_zitadel"
|
||||
}
|
||||
|
||||
required_providers {
|
||||
zitadel = {
|
||||
source = "zitadel/zitadel"
|
||||
version = "~> 3.4"
|
||||
}
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# VAULT_ADDR and VAULT_TOKEN are read from the environment.
|
||||
provider "vault" {}
|
||||
|
||||
provider "random" {}
|
||||
|
||||
ephemeral "vault_kv_secret_v2" "zitadel_iam_admin" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.zitadel_iam_admin_vault_path
|
||||
}
|
||||
|
||||
locals {
|
||||
zitadel_iam_admin_machine_key_json = ephemeral.vault_kv_secret_v2.zitadel_iam_admin.data[var.zitadel_iam_admin_machine_key_key]
|
||||
}
|
||||
|
||||
provider "zitadel" {
|
||||
domain = var.zitadel_domain
|
||||
port = "443"
|
||||
insecure = false
|
||||
jwt_profile_json = local.zitadel_iam_admin_machine_key_json
|
||||
}
|
||||
|
||||
variable "zitadel_domain" {
|
||||
description = "External DNS name for the ZITADEL instance."
|
||||
type = string
|
||||
default = "auth.quorate.dev"
|
||||
}
|
||||
|
||||
variable "zitadel_iam_admin_vault_path" {
|
||||
description = "Vault KV path containing the ZITADEL IAM admin machine-key JSON."
|
||||
type = string
|
||||
default = "zitadel/iam-admin"
|
||||
}
|
||||
|
||||
variable "zitadel_iam_admin_machine_key_key" {
|
||||
description = "Key in zitadel_iam_admin_vault_path containing the ZITADEL IAM admin machine-key JSON."
|
||||
type = string
|
||||
default = "iam-admin.json"
|
||||
}
|
||||
67
terraform/internal/zitadel/quay.tf
Normal file
67
terraform/internal/zitadel/quay.tf
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
locals {
|
||||
quay_oidc_redirect_uri = "${trim(var.quay_base_url, "/")}/oauth2/zitadel/callback"
|
||||
quay_oidc_redirect_uris = [
|
||||
local.quay_oidc_redirect_uri,
|
||||
"${local.quay_oidc_redirect_uri}/attach",
|
||||
"${local.quay_oidc_redirect_uri}/cli",
|
||||
]
|
||||
|
||||
quay_zitadel_roles = {
|
||||
quay_user = {
|
||||
display_name = "Quay user"
|
||||
group = "Quay"
|
||||
}
|
||||
quay_admin = {
|
||||
display_name = "Quay administrator"
|
||||
group = "Quay"
|
||||
}
|
||||
}
|
||||
|
||||
quay_zitadel_role_keys = keys(local.quay_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "quay" {
|
||||
for_each = local.quay_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "quay" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
name = "Quay"
|
||||
redirect_uris = local.quay_oidc_redirect_uris
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"]
|
||||
post_logout_redirect_uris = [trim(var.quay_base_url, "/")]
|
||||
app_type = "OIDC_APP_TYPE_WEB"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = false
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
additional_origins = []
|
||||
skip_native_app_success_page = false
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "quay_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.quay_oidc_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
client_id = zitadel_application_oidc.quay.client_id
|
||||
client_secret = zitadel_application_oidc.quay.client_secret
|
||||
discovery_url = "https://${var.zitadel_domain}/.well-known/openid-configuration"
|
||||
redirect_uri = local.quay_oidc_redirect_uri
|
||||
})
|
||||
|
||||
# Vault is source of truth after first write. OIDC credential rotation is explicit.
|
||||
lifecycle { ignore_changes = [data_json] }
|
||||
}
|
||||
46
terraform/internal/zitadel/quay_personas.tf
Normal file
46
terraform/internal/zitadel/quay_personas.tf
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
locals {
|
||||
quay_personas = {
|
||||
candidates-bot = {
|
||||
display_name = "Quay Candidates Bot"
|
||||
email = "[email protected]"
|
||||
last_name = "Candidates"
|
||||
}
|
||||
mirror-bot = {
|
||||
display_name = "Quay Mirror Bot"
|
||||
email = "[email protected]"
|
||||
last_name = "Mirror"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ephemeral "vault_kv_secret_v2" "quay_persona" {
|
||||
for_each = local.quay_personas
|
||||
|
||||
mount = var.vault_kv_mount
|
||||
name = "quay/personas/${each.key}"
|
||||
}
|
||||
|
||||
resource "zitadel_human_user" "quay_persona" {
|
||||
for_each = local.quay_personas
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
user_name = each.key
|
||||
first_name = "Quay"
|
||||
last_name = each.value.last_name
|
||||
display_name = each.value.display_name
|
||||
email = each.value.email
|
||||
is_email_verified = true
|
||||
initial_password = ephemeral.vault_kv_secret_v2.quay_persona[each.key].data["password"]
|
||||
initial_skip_password_change = true
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "quay_persona" {
|
||||
for_each = local.quay_personas
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.services.id
|
||||
user_id = zitadel_human_user.quay_persona[each.key].id
|
||||
role_keys = ["quay_user"]
|
||||
|
||||
depends_on = [zitadel_project_role.quay]
|
||||
}
|
||||
75
terraform/internal/zitadel/services.tf
Normal file
75
terraform/internal/zitadel/services.tf
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
resource "zitadel_project" "services" {
|
||||
org_id = zitadel_org.primary.id
|
||||
name = var.zitadel_project_name
|
||||
project_role_assertion = true
|
||||
project_role_check = true
|
||||
has_project_check = false
|
||||
private_labeling_setting = "PRIVATE_LABELING_SETTING_UNSPECIFIED"
|
||||
}
|
||||
|
||||
resource "zitadel_action" "service_roles_claim" {
|
||||
org_id = zitadel_org.primary.id
|
||||
name = "service_roles_claim"
|
||||
timeout = "10s"
|
||||
allowed_to_fail = true
|
||||
|
||||
script = <<-EOT
|
||||
function service_roles_claim(ctx, api) {
|
||||
api.v1.claims.setClaim('zitadel_action_ran', 'true');
|
||||
|
||||
var grants = ctx.v1.user && ctx.v1.user.grants;
|
||||
if (!grants || !grants.grants || grants.count == 0) {
|
||||
api.v1.claims.setClaim('groups', []);
|
||||
return;
|
||||
}
|
||||
|
||||
var roles = [];
|
||||
var serviceRoles = [
|
||||
'forgejo_user',
|
||||
'forgejo_admin',
|
||||
'forgejo_restricted',
|
||||
'grafana_viewer',
|
||||
'grafana_editor',
|
||||
'grafana_admin',
|
||||
'frigate_viewer',
|
||||
'frigate_admin',
|
||||
'longhorn_admin',
|
||||
'stalwart_admin'
|
||||
];
|
||||
grants.grants.forEach(function(grant) {
|
||||
if (grant.roles && Array.isArray(grant.roles)) {
|
||||
grant.roles.forEach(function(role) {
|
||||
if (serviceRoles.indexOf(role) !== -1 && roles.indexOf(role) === -1) {
|
||||
roles.push(role);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var hasForgejoRole = roles.some(function(role) {
|
||||
return role.indexOf('forgejo_') === 0;
|
||||
});
|
||||
if (hasForgejoRole && roles.indexOf('forgejo_login') === -1) {
|
||||
roles.push('forgejo_login');
|
||||
}
|
||||
|
||||
api.v1.claims.setClaim('groups', roles);
|
||||
}
|
||||
EOT
|
||||
|
||||
depends_on = [zitadel_project_role.forgejo, zitadel_project_role.frigate, zitadel_project_role.grafana, zitadel_project_role.longhorn, zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "zitadel_trigger_actions" "service_roles_claim_userinfo" {
|
||||
org_id = zitadel_org.primary.id
|
||||
flow_type = "FLOW_TYPE_CUSTOMISE_TOKEN"
|
||||
trigger_type = "TRIGGER_TYPE_PRE_USERINFO_CREATION"
|
||||
action_ids = [zitadel_action.service_roles_claim.id]
|
||||
}
|
||||
|
||||
resource "zitadel_trigger_actions" "service_roles_claim_access_token" {
|
||||
org_id = zitadel_org.primary.id
|
||||
flow_type = "FLOW_TYPE_CUSTOMISE_TOKEN"
|
||||
trigger_type = "TRIGGER_TYPE_PRE_ACCESS_TOKEN_CREATION"
|
||||
action_ids = [zitadel_action.service_roles_claim.id]
|
||||
}
|
||||
7
terraform/internal/zitadel/signing.tf
Normal file
7
terraform/internal/zitadel/signing.tf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
resource "zitadel_webkey" "eddsa" {
|
||||
ed25519 {}
|
||||
}
|
||||
|
||||
resource "zitadel_active_webkey" "signing" {
|
||||
key_id = zitadel_webkey.eddsa.id
|
||||
}
|
||||
187
terraform/internal/zitadel/stalwart.tf
Normal file
187
terraform/internal/zitadel/stalwart.tf
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
locals {
|
||||
account_login_uri = "${trim(var.stalwart_account_webui_base_url, "/")}/account/oauth/callback"
|
||||
account_logout_uri = "${trim(var.stalwart_account_webui_base_url, "/")}/account/oauth/logout"
|
||||
stalwart_zitadel_roles = {
|
||||
stalwart_admin = {
|
||||
display_name = "Stalwart administrator"
|
||||
group = "Stalwart"
|
||||
}
|
||||
stalwart_controller = {
|
||||
display_name = "Stalwart configuration controller"
|
||||
group = "Stalwart"
|
||||
}
|
||||
stalwart_submitter = {
|
||||
display_name = "Stalwart alert submitter"
|
||||
group = "Stalwart"
|
||||
}
|
||||
stalwart_notifier = {
|
||||
display_name = "Stalwart notification sender"
|
||||
group = "Stalwart"
|
||||
}
|
||||
stalwart_test = {
|
||||
display_name = "Stalwart authenticated client test"
|
||||
group = "Stalwart"
|
||||
}
|
||||
}
|
||||
|
||||
stalwart_zitadel_role_keys = keys(local.stalwart_zitadel_roles)
|
||||
}
|
||||
|
||||
resource "zitadel_application_oidc" "stalwart_account_webui" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
name = "Mail app passwords"
|
||||
redirect_uris = [local.account_login_uri]
|
||||
response_types = ["OIDC_RESPONSE_TYPE_CODE"]
|
||||
grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"]
|
||||
post_logout_redirect_uris = [local.account_logout_uri]
|
||||
app_type = "OIDC_APP_TYPE_USER_AGENT"
|
||||
auth_method_type = "OIDC_AUTH_METHOD_TYPE_NONE"
|
||||
version = "OIDC_VERSION_1_0"
|
||||
clock_skew = "0s"
|
||||
dev_mode = false
|
||||
access_token_type = "OIDC_TOKEN_TYPE_BEARER"
|
||||
access_token_role_assertion = true
|
||||
id_token_role_assertion = true
|
||||
id_token_userinfo_assertion = true
|
||||
}
|
||||
|
||||
resource "zitadel_project" "stalwart" {
|
||||
org_id = zitadel_org.primary.id
|
||||
name = var.stalwart_project_name
|
||||
project_role_assertion = true
|
||||
project_role_check = true
|
||||
has_project_check = true
|
||||
private_labeling_setting = "PRIVATE_LABELING_SETTING_UNSPECIFIED"
|
||||
}
|
||||
|
||||
resource "zitadel_project_role" "stalwart" {
|
||||
for_each = local.stalwart_zitadel_roles
|
||||
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
role_key = each.key
|
||||
display_name = each.value.display_name
|
||||
group = each.value.group
|
||||
}
|
||||
|
||||
resource "zitadel_machine_user" "stalwart_controller" {
|
||||
org_id = zitadel_org.primary.id
|
||||
user_name = "stalwart-controller"
|
||||
name = "Stalwart configuration controller"
|
||||
description = "Credentialless Stalwart desired-state owner"
|
||||
access_token_type = "ACCESS_TOKEN_TYPE_JWT"
|
||||
with_secret = true
|
||||
}
|
||||
|
||||
resource "zitadel_machine_user" "stalwart_sender" {
|
||||
org_id = zitadel_org.primary.id
|
||||
user_name = "stalwart-submitter"
|
||||
name = "Stalwart alert sender"
|
||||
description = "Zitadel-owned Stalwart cluster-local SMTP identity"
|
||||
access_token_type = "ACCESS_TOKEN_TYPE_JWT"
|
||||
with_secret = true
|
||||
}
|
||||
|
||||
resource "zitadel_machine_user" "stalwart_notifier" {
|
||||
org_id = zitadel_org.primary.id
|
||||
user_name = "stalwart-notifier"
|
||||
name = "Stalwart notification sender"
|
||||
description = "Zitadel-owned Stalwart notification identity"
|
||||
access_token_type = "ACCESS_TOKEN_TYPE_JWT"
|
||||
with_secret = true
|
||||
}
|
||||
|
||||
resource "zitadel_machine_user" "stalwart_test" {
|
||||
org_id = zitadel_org.primary.id
|
||||
user_name = "stalwart-test-client"
|
||||
name = "Stalwart authenticated client test"
|
||||
description = "Zitadel-owned authenticated mail client test identity"
|
||||
access_token_type = "ACCESS_TOKEN_TYPE_JWT"
|
||||
with_secret = true
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "stalwart_admin" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
user_id = var.stalwart_admin_zitadel_user_id
|
||||
role_keys = ["stalwart_admin"]
|
||||
|
||||
depends_on = [zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "stalwart_controller" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
user_id = zitadel_machine_user.stalwart_controller.id
|
||||
role_keys = ["stalwart_controller"]
|
||||
|
||||
depends_on = [zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "stalwart_sender" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
user_id = zitadel_machine_user.stalwart_sender.id
|
||||
role_keys = ["stalwart_submitter"]
|
||||
|
||||
depends_on = [zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "stalwart_notifier" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
user_id = zitadel_machine_user.stalwart_notifier.id
|
||||
role_keys = ["stalwart_notifier"]
|
||||
|
||||
depends_on = [zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "zitadel_user_grant" "stalwart_test" {
|
||||
org_id = zitadel_org.primary.id
|
||||
project_id = zitadel_project.stalwart.id
|
||||
user_id = zitadel_machine_user.stalwart_test.id
|
||||
role_keys = ["stalwart_test"]
|
||||
|
||||
depends_on = [zitadel_project_role.stalwart]
|
||||
}
|
||||
|
||||
resource "vault_kv_secret_v2" "stalwart_oidc" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.stalwart_oidc_vault_path
|
||||
|
||||
data_json = jsonencode({
|
||||
account_client_id = zitadel_application_oidc.stalwart_account_webui.client_id
|
||||
audience = zitadel_project.stalwart.id
|
||||
client_id = zitadel_machine_user.stalwart_controller.client_id
|
||||
client_secret = zitadel_machine_user.stalwart_controller.client_secret
|
||||
controller_subject = zitadel_machine_user.stalwart_controller.id
|
||||
human_admin_subject = var.stalwart_admin_zitadel_user_id
|
||||
issuer_url = "https://${var.zitadel_domain}"
|
||||
notifier_client_id = zitadel_machine_user.stalwart_notifier.client_id
|
||||
notifier_client_secret = zitadel_machine_user.stalwart_notifier.client_secret
|
||||
notifier_subject = zitadel_machine_user.stalwart_notifier.id
|
||||
sender_client_id = zitadel_machine_user.stalwart_sender.client_id
|
||||
sender_client_secret = zitadel_machine_user.stalwart_sender.client_secret
|
||||
sender_subject = zitadel_machine_user.stalwart_sender.id
|
||||
test_client_id = zitadel_machine_user.stalwart_test.client_id
|
||||
test_client_secret = zitadel_machine_user.stalwart_test.client_secret
|
||||
test_subject = zitadel_machine_user.stalwart_test.id
|
||||
})
|
||||
}
|
||||
|
||||
data "vault_kv_secret_v2" "stalwart_zitadel_notification" {
|
||||
mount = var.vault_kv_mount
|
||||
name = var.stalwart_zitadel_notification_vault_path
|
||||
}
|
||||
|
||||
resource "zitadel_email_provider_smtp" "stalwart" {
|
||||
sender_address = "[email protected]"
|
||||
sender_name = "Quorate"
|
||||
host = "stalwart-submission.stalwart.svc.cluster.local:587"
|
||||
tls = false
|
||||
user = "[email protected]"
|
||||
password = data.vault_kv_secret_v2.stalwart_zitadel_notification.data["smtp_password"]
|
||||
description = "Stalwart cluster-local notification submission"
|
||||
set_active = true
|
||||
}
|
||||
259
terraform/internal/zitadel/variables.tf
Normal file
259
terraform/internal/zitadel/variables.tf
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
variable "zitadel_org_name" {
|
||||
description = "Name of the primary service organization to create in ZITADEL."
|
||||
type = string
|
||||
default = "Quorate"
|
||||
}
|
||||
|
||||
variable "zitadel_org_id" {
|
||||
description = "Optional stable custom ID for the primary service organization. Leave null to let ZITADEL generate one."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "zitadel_project_name" {
|
||||
description = "Name of the ZITADEL project that groups service clients."
|
||||
type = string
|
||||
default = "Services"
|
||||
}
|
||||
|
||||
variable "forgejo_base_url" {
|
||||
description = "Canonical Forgejo URL. This must match the Forgejo HTTPRoute hostname and ROOT_URL."
|
||||
type = string
|
||||
default = "https://forge.quorate.internal"
|
||||
}
|
||||
|
||||
variable "forgejo_oauth_provider_name" {
|
||||
description = "Forgejo OAuth2 authentication source name; this value is part of the callback URL."
|
||||
type = string
|
||||
default = "Zitadel"
|
||||
}
|
||||
|
||||
variable "vault_kv_mount" {
|
||||
description = "Vault KV v2 mount where generated service integration secrets are mirrored."
|
||||
type = string
|
||||
default = "secret"
|
||||
}
|
||||
|
||||
variable "forgejo_oidc_vault_path" {
|
||||
description = "Vault KV path for Forgejo's ZITADEL OIDC client material."
|
||||
type = string
|
||||
default = "forgejo/oidc/zitadel"
|
||||
}
|
||||
|
||||
variable "stalwart_oidc_vault_path" {
|
||||
description = "Vault KV path containing Stalwart's ZITADEL resource-server metadata."
|
||||
type = string
|
||||
default = "stalwart/oidc/zitadel"
|
||||
}
|
||||
|
||||
variable "stalwart_zitadel_notification_vault_path" {
|
||||
description = "Vault KV path containing Stalwart's ZITADEL notification SMTP credentials."
|
||||
type = string
|
||||
default = "stalwart/zitadel-notification"
|
||||
}
|
||||
|
||||
variable "stalwart_project_name" {
|
||||
description = "Name of the dedicated ZITADEL project for Stalwart tokens."
|
||||
type = string
|
||||
default = "Stalwart"
|
||||
}
|
||||
|
||||
variable "stalwart_admin_zitadel_user_id" {
|
||||
description = "ZITADEL human user ID granted Stalwart administrative access."
|
||||
type = string
|
||||
default = "********"
|
||||
}
|
||||
|
||||
variable "forgejo_public_base_url" {
|
||||
description = "Canonical authenticated URL for the public Forgejo database."
|
||||
type = string
|
||||
default = "https://git.quorate.dev"
|
||||
}
|
||||
|
||||
variable "forgejo_public_oauth_provider_name" {
|
||||
description = "Public Forgejo OAuth2 authentication source name."
|
||||
type = string
|
||||
default = "Zitadel"
|
||||
}
|
||||
|
||||
variable "forgejo_public_oidc_vault_path" {
|
||||
description = "Vault KV path for public Forgejo's ZITADEL OIDC client material."
|
||||
type = string
|
||||
default = "forgejo-public/oidc/zitadel"
|
||||
}
|
||||
|
||||
variable "service_admin_zitadel_user_id" {
|
||||
description = "ZITADEL human user ID granted administrative access to GitOps-managed service applications."
|
||||
type = string
|
||||
default = "********"
|
||||
}
|
||||
|
||||
variable "service_admin_zitadel_role_keys" {
|
||||
description = "Service project role keys assigned to service_admin_zitadel_user_id."
|
||||
type = set(string)
|
||||
default = [
|
||||
"forgejo_admin",
|
||||
"frigate_admin",
|
||||
"grafana_admin",
|
||||
"longhorn_admin",
|
||||
"quay_admin",
|
||||
]
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for role_key in var.service_admin_zitadel_role_keys : contains([
|
||||
"forgejo_user",
|
||||
"forgejo_admin",
|
||||
"forgejo_restricted",
|
||||
"frigate_viewer",
|
||||
"frigate_admin",
|
||||
"grafana_viewer",
|
||||
"grafana_editor",
|
||||
"grafana_admin",
|
||||
"longhorn_admin",
|
||||
"quay_user",
|
||||
"quay_admin",
|
||||
], role_key)
|
||||
])
|
||||
error_message = "Service admin ZITADEL grants may only use known Forgejo, Frigate, Grafana, Longhorn, and Quay role keys."
|
||||
}
|
||||
}
|
||||
|
||||
variable "frigate_base_url" {
|
||||
description = "Canonical Frigate URL. This must match the Frigate HTTPRoute hostname and oauth2-proxy redirect URL."
|
||||
type = string
|
||||
default = "https://frigate.quorate.internal"
|
||||
}
|
||||
|
||||
variable "frigate_zitadel_user_grants" {
|
||||
description = "Map of ZITADEL user IDs to Frigate role keys to grant. Valid role keys are frigate_viewer and frigate_admin."
|
||||
type = map(set(string))
|
||||
default = {}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for _, role_keys in var.frigate_zitadel_user_grants : [
|
||||
for role_key in role_keys : contains([
|
||||
"frigate_viewer",
|
||||
"frigate_admin",
|
||||
], role_key)
|
||||
]
|
||||
]))
|
||||
error_message = "Frigate ZITADEL grants may only use frigate_viewer and frigate_admin."
|
||||
}
|
||||
}
|
||||
|
||||
variable "forgejo_zitadel_user_grants" {
|
||||
description = "Map of ZITADEL user IDs to Forgejo role keys to grant. Valid role keys are forgejo_user, forgejo_admin, and forgejo_restricted."
|
||||
type = map(set(string))
|
||||
default = {}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for _, role_keys in var.forgejo_zitadel_user_grants : [
|
||||
for role_key in role_keys : contains([
|
||||
"forgejo_user",
|
||||
"forgejo_admin",
|
||||
"forgejo_restricted",
|
||||
], role_key)
|
||||
]
|
||||
]))
|
||||
error_message = "Forgejo ZITADEL grants may only use forgejo_user, forgejo_admin, and forgejo_restricted."
|
||||
}
|
||||
}
|
||||
|
||||
variable "grafana_base_url" {
|
||||
description = "Canonical Grafana URL. This must match the Grafana HTTPRoute hostname and root URL."
|
||||
type = string
|
||||
default = "https://grafana.quorate.internal"
|
||||
}
|
||||
|
||||
variable "grafana_oidc_vault_path" {
|
||||
description = "Vault KV path for Grafana's ZITADEL OIDC client material."
|
||||
type = string
|
||||
default = "grafana/oidc/zitadel"
|
||||
}
|
||||
|
||||
variable "grafana_zitadel_user_grants" {
|
||||
description = "Map of ZITADEL user IDs to Grafana role keys to grant. Valid role keys are grafana_viewer, grafana_editor, and grafana_admin."
|
||||
type = map(set(string))
|
||||
default = {}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for _, role_keys in var.grafana_zitadel_user_grants : [
|
||||
for role_key in role_keys : contains([
|
||||
"grafana_viewer",
|
||||
"grafana_editor",
|
||||
"grafana_admin",
|
||||
], role_key)
|
||||
]
|
||||
]))
|
||||
error_message = "Grafana ZITADEL grants may only use grafana_viewer, grafana_editor, and grafana_admin."
|
||||
}
|
||||
}
|
||||
|
||||
variable "longhorn_base_url" {
|
||||
description = "Canonical Longhorn URL. This must match the Longhorn HTTPRoute hostname and oauth2-proxy redirect URL."
|
||||
type = string
|
||||
default = "https://longhorn.quorate.internal"
|
||||
}
|
||||
|
||||
variable "stalwart_account_webui_base_url" {
|
||||
description = "Public Stalwart account WebUI base URL."
|
||||
type = string
|
||||
default = "https://mail.quorate.dev"
|
||||
}
|
||||
|
||||
variable "longhorn_oauth2_proxy_vault_path" {
|
||||
description = "Vault KV path for Longhorn's oauth2-proxy OIDC client material."
|
||||
type = string
|
||||
default = "longhorn/oauth2-proxy"
|
||||
}
|
||||
|
||||
variable "longhorn_zitadel_user_grants" {
|
||||
description = "Map of ZITADEL user IDs to Longhorn role keys to grant. Valid role key is longhorn_admin."
|
||||
type = map(set(string))
|
||||
default = {}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for _, role_keys in var.longhorn_zitadel_user_grants : [
|
||||
for role_key in role_keys : contains([
|
||||
"longhorn_admin",
|
||||
], role_key)
|
||||
]
|
||||
]))
|
||||
error_message = "Longhorn ZITADEL grants may only use longhorn_admin."
|
||||
}
|
||||
}
|
||||
|
||||
variable "quay_base_url" {
|
||||
description = "Canonical Quay URL. This must match the Quay HTTPRoute hostname and SERVER_HOSTNAME."
|
||||
type = string
|
||||
default = "https://registry.quorate.internal"
|
||||
}
|
||||
|
||||
variable "quay_oidc_vault_path" {
|
||||
description = "Vault KV path for Quay's ZITADEL OIDC client material."
|
||||
type = string
|
||||
default = "quay/oidc/zitadel"
|
||||
}
|
||||
|
||||
variable "quay_zitadel_user_grants" {
|
||||
description = "Map of ZITADEL user IDs to Quay role keys to grant. Valid role keys are quay_user and quay_admin."
|
||||
type = map(set(string))
|
||||
default = {}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for _, role_keys in var.quay_zitadel_user_grants : [
|
||||
for role_key in role_keys : contains([
|
||||
"quay_user",
|
||||
"quay_admin",
|
||||
], role_key)
|
||||
]
|
||||
]))
|
||||
error_message = "Quay ZITADEL grants may only use quay_user and quay_admin."
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue