Skip to content

Commit 53b6c5f

Browse files
authored
fix: route zitadel connections through stable base domain (#1847)
1 parent 3be1819 commit 53b6c5f

12 files changed

Lines changed: 45 additions & 23 deletions

File tree

tf/deployment/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export TF_VAR_zitadel_github_client_secret="op://tf/GITHUB_OAUTH_APP_IMMICH_ZITA
1717
export TF_VAR_futo_internal_zitadel_github_client_id="op://tf/IMMICH_GITHUB_OAUTH_APP_INTERNAL_FUTO_ZITADEL_CLIENT_ID/password"
1818
export TF_VAR_futo_internal_zitadel_github_client_secret="op://tf/IMMICH_GITHUB_OAUTH_APP_INTERNAL_FUTO_ZITADEL_CLIENT_SECRET/password"
1919
export TF_VAR_futo_zitadel_profile_json="op://tf/FUTO_ZITADEL_PROFILE_JSON/password"
20+
export TF_VAR_futo_zitadel_base_domain="op://tf/FUTO_ZITADEL_BASE_DOMAIN/password"
2021
export TF_VAR_zitadel_gitlab_client_id="op://tf/GITLAB_OAUTH_APP_FUTO_ZITADEL_CLIENT_ID/password"
2122
export TF_VAR_zitadel_gitlab_client_secret="op://tf/GITLAB_OAUTH_APP_FUTO_ZITADEL_CLIENT_SECRET/password"
2223
export TF_VAR_zitadel_gitlab_issuer="op://tf/GITLAB_OAUTH_APP_FUTO_ZITADEL_ISSUER/password"

tf/deployment/modules/scoped/zitadel/customer/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,25 @@ before Terraform can run. Do this in the ZITADEL Cloud console and 1Password:
3232
seeds stub items in `yucca_tf_${env}_manual` with `REPLACE_ME` passwords
3333
for:
3434
- `CUSTOMER_ZITADEL_DOMAIN`
35+
- `CUSTOMER_ZITADEL_BASE_DOMAIN`
3536
- `CUSTOMER_ZITADEL_PROFILE_JSON`
3637
- `CUSTOMER_ZITADEL_SMTP_HOST`
3738
- `CUSTOMER_ZITADEL_SMTP_USER`
3839
- `CUSTOMER_ZITADEL_SMTP_PASSWORD`
3940
- `CUSTOMER_ZITADEL_SMTP_SENDER_ADDRESS`
4041

4142
Replace each stub password in `yucca_tf_${env}_manual` with the real value
42-
(`CUSTOMER_ZITADEL_DOMAIN` = the instance URL `<name>.zitadel.cloud`, profile
43-
JSON from step 3, SMTP credentials for the chosen provider, and sender
44-
address e.g. `no-reply@futo.cloud`).
43+
(`CUSTOMER_ZITADEL_DOMAIN` = the public-facing auth URL — prod: the
44+
`auth.futo.cloud` vanity domain, dev/staging: the generated
45+
`<name>.zitadel.cloud` URL — consumed by app-side config, not this module;
46+
`CUSTOMER_ZITADEL_BASE_DOMAIN` = the generated `<name>.zitadel.cloud` instance
47+
host as a **bare hostname** (no `https://`, no trailing slash) in every env,
48+
which this module both connects through (provider + hosted login) and targets
49+
with the `auth.futo.cloud` CNAME, so the connection never depends on the
50+
mutable vanity domain. Stored bare to match the internal instance's
51+
`FUTO_ZITADEL_BASE_DOMAIN`. Profile JSON from step 3, SMTP
52+
credentials for the chosen provider, and sender address e.g.
53+
`no-reply@futo.cloud`).
4554
Re-apply `shared/1password/futo-account` so the copy-secrets module
4655
mirrors each value into the `yucca_tf_${env}` vault that this module
4756
reads from via `data "onepassword_item"` lookups at plan/apply time. No

tf/deployment/modules/scoped/zitadel/customer/data.tf

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ data "onepassword_vault" "tf_env" {
22
name = "yucca_tf_${var.env}"
33
}
44

5-
data "onepassword_item" "customer_zitadel_domain" {
5+
# NOTE: CUSTOMER_ZITADEL_DOMAIN (the public auth.futo.cloud vanity domain) is
6+
# intentionally not read here — this module connects to the instance via the
7+
# stable <name>.zitadel.cloud base domain below. The vanity domain remains the
8+
# public-facing auth URL consumed by app-side config.
9+
data "onepassword_item" "customer_zitadel_base_domain" {
610
vault = data.onepassword_vault.tf_env.uuid
7-
title = "CUSTOMER_ZITADEL_DOMAIN"
11+
title = "CUSTOMER_ZITADEL_BASE_DOMAIN"
812
}
913

1014
data "onepassword_item" "customer_zitadel_profile_json" {

tf/deployment/modules/scoped/zitadel/customer/dns.tf

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,20 @@ data "cloudflare_zone" "futo_cloud" {
1313
}
1414
}
1515

16-
locals {
17-
# CUSTOMER_ZITADEL_DOMAIN is stored as a URL (https://host/); Cloudflare needs
18-
# a bare hostname as CNAME content.
19-
customer_zitadel_host = trimsuffix(trimprefix(data.onepassword_item.customer_zitadel_domain.password, "https://"), "/")
20-
}
21-
22-
# Prod only: auth.futo.cloud -> the prod ZITADEL Cloud instance host from
23-
# CUSTOMER_ZITADEL_DOMAIN. dev/staging use the generated <name>.zitadel.cloud URL
24-
# directly with no custom domain. DNS-only so ZITADEL terminates TLS.
16+
# Prod only: auth.futo.cloud -> the prod ZITADEL Cloud instance's base
17+
# <name>.zitadel.cloud host from CUSTOMER_ZITADEL_BASE_DOMAIN, which is stored as
18+
# a bare hostname (no scheme) — exactly what a CNAME wants. This is NOT the
19+
# auth.futo.cloud vanity domain that CUSTOMER_ZITADEL_DOMAIN holds; pointing the
20+
# record at the vanity domain would make the CNAME reference itself. dev/staging
21+
# have no custom domain and use the generated URL directly. DNS-only so ZITADEL
22+
# terminates TLS.
2523
resource "cloudflare_dns_record" "customer_auth" {
2624
count = var.env == "prod" ? 1 : 0
2725

2826
zone_id = data.cloudflare_zone.futo_cloud.id
2927
name = "auth.futo.cloud"
3028
type = "CNAME"
31-
content = local.customer_zitadel_host
29+
content = data.onepassword_item.customer_zitadel_base_domain.password
3230
ttl = 1
3331
proxied = false
3432
}

tf/deployment/modules/scoped/zitadel/customer/hosted_login_translations.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ resource "null_resource" "hosted_login_translations" {
1212
triggers = {
1313
translations_hash = filemd5(local.hosted_login_translations_file)
1414
script_hash = filemd5(local.hosted_login_translations_script)
15-
domain = data.onepassword_item.customer_zitadel_domain.password
15+
domain = data.onepassword_item.customer_zitadel_base_domain.password
1616
locale = local.hosted_login_translations_locale
1717
}
1818

1919
provisioner "local-exec" {
2020
command = local.hosted_login_translations_script
2121

2222
environment = {
23-
ZITADEL_DOMAIN = data.onepassword_item.customer_zitadel_domain.password
23+
# Connect to the API via the stable base domain, not the vanity domain.
24+
ZITADEL_DOMAIN = data.onepassword_item.customer_zitadel_base_domain.password
2425
ZITADEL_PROFILE_JSON = data.onepassword_item.customer_zitadel_profile_json.password
2526
TRANSLATIONS_FILE = local.hosted_login_translations_file
2627
LOCALE = local.hosted_login_translations_locale

tf/deployment/modules/scoped/zitadel/customer/providers.tf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
# Connect via the stable <name>.zitadel.cloud base domain, not the auth.futo.cloud
2+
# vanity domain (CUSTOMER_ZITADEL_DOMAIN) which is an alias that could change.
13
provider "zitadel" {
2-
domain = data.onepassword_item.customer_zitadel_domain.password
4+
domain = data.onepassword_item.customer_zitadel_base_domain.password
35
insecure = false
46
jwt_profile_json = data.onepassword_item.customer_zitadel_profile_json.password
57
}

tf/deployment/modules/shared/zitadel/cloud/actions-v2.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ resource "cloudflare_worker_version" "zitadel_actions" {
8282
}]
8383

8484
bindings = [
85-
{ name = "ZITADEL_DOMAIN", type = "plain_text", text = "auth.internal.futo.org" },
85+
{ name = "ZITADEL_DOMAIN", type = "plain_text", text = var.futo_zitadel_base_domain },
8686
{ name = "GITHUB_IDP_ID", type = "plain_text", text = zitadel_idp_github.github.id },
8787
{ name = "GITLAB_IDP_ID", type = "plain_text", text = zitadel_idp_gitlab_self_hosted.gitlab.id },
8888
{ name = "ZITADEL_TOKEN", type = "secret_text", text = zitadel_personal_access_token.zitadel_actions.token },

tf/deployment/modules/shared/zitadel/cloud/branding.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ resource "zitadel_default_privacy_policy" "default" {
5353
locals {
5454
hosted_login_translations_file = "${path.module}/translations/en.json"
5555
hosted_login_translations_script = "${path.module}/scripts/set-hosted-login-translations.sh"
56-
hosted_login_translations_domain = "auth.internal.futo.org"
56+
hosted_login_translations_domain = var.futo_zitadel_base_domain
5757
hosted_login_translations_locale = "en"
5858
}
5959

tf/deployment/modules/shared/zitadel/cloud/idp-links.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ resource "terraform_data" "idp_link" {
5454
idp_id = each.value.idp_id
5555
external_user_id = each.value.external_user_id
5656
user_name = each.value.user_name
57-
domain = "auth.internal.futo.org"
57+
domain = var.futo_zitadel_base_domain
5858
token = zitadel_personal_access_token.zitadel_actions.token
5959
}
6060

tf/deployment/modules/shared/zitadel/cloud/outline-role-sync-worker.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ resource "cloudflare_worker_version" "outline_role_sync" {
4747

4848
bindings = [
4949
{ name = "OUTLINE_BASE_URL", type = "plain_text", text = "https://outline.immich.cloud" },
50-
{ name = "ZITADEL_BASE_URL", type = "plain_text", text = "https://auth.internal.futo.org" },
50+
{ name = "ZITADEL_BASE_URL", type = "plain_text", text = "https://${var.futo_zitadel_base_domain}" },
5151
{ name = "ZITADEL_OUTLINE_PROJECT_ID", type = "plain_text", text = zitadel_project.projects["Outline"].id },
5252
{ name = "ZITADEL_OUTLINE_PROJECT_ROLES", type = "plain_text", text = join(",", local.outline_role_keys) },
5353
{ name = "ZITADEL_SERVICE_ACCOUNT_TOKEN", type = "secret_text", text = zitadel_personal_access_token.outline_role_sync.token },

0 commit comments

Comments
 (0)