diff --git a/tf/deployment/modules/scoped/zitadel/customer/.terraform.lock.hcl b/tf/deployment/modules/scoped/zitadel/customer/.terraform.lock.hcl index 139f584a..7e2dd839 100644 --- a/tf/deployment/modules/scoped/zitadel/customer/.terraform.lock.hcl +++ b/tf/deployment/modules/scoped/zitadel/customer/.terraform.lock.hcl @@ -25,6 +25,26 @@ provider "registry.opentofu.org/1password/onepassword" { ] } +provider "registry.opentofu.org/cloudflare/cloudflare" { + version = "5.21.1" + constraints = "5.21.1" + hashes = [ + "h1:Lh5LHSNKoKwCx4F/YlRjoLZ+jZLxnoxEfOMzUX9n4zg=", + "h1:gNF1Sro3G9nXhtdkitXwDVKxI1jpBAf8KPv+Y4kAJwk=", + "h1:hU72otEs26Wx6tcJD9igX6I/BQtVgeRuaIe3s/hn6bQ=", + "h1:iWJb0lHfVWmCJQSyroXOT8zQlFOT8k1caHcfaooG5wk=", + "zh:049719425b8be43d9d4f0c208217aca0baa22374f061d7ff92f02563490f649c", + "zh:0a8a3c1b26680b437fe9e7910ca81e532d36f8efacfb14f45690b6a779856993", + "zh:32b61f80892243f7ab8e453fa038c1f3e2aac733ccb98307c2cfe798b2793b32", + "zh:42c27f3cd62979e70716c51f682a3d131d51ad76d86dff83d8cdbfffcebac841", + "zh:4c8cd464f9b6ecde5cd4430bbba4be3b810826105e51ef6328b6a2b69f821443", + "zh:586ea42ef74d6c5bc4c9b89da6b1f8618a19f4e80272fe8d615e7d5b11c491af", + "zh:b09b86c7cac7085e01c9b7a828f09d13c44589d3e3cd42f0b694ca3e4cd3ed0a", + "zh:eac80665e60c701b37a6318f4e405d67f1720f8da5f93135c6256049282d3367", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + ] +} + provider "registry.opentofu.org/hashicorp/null" { version = "3.3.0" constraints = "~> 3.2" diff --git a/tf/deployment/modules/scoped/zitadel/customer/README.md b/tf/deployment/modules/scoped/zitadel/customer/README.md index 94bcd9df..61ce71aa 100644 --- a/tf/deployment/modules/scoped/zitadel/customer/README.md +++ b/tf/deployment/modules/scoped/zitadel/customer/README.md @@ -21,12 +21,11 @@ before Terraform can run. Do this in the ZITADEL Cloud console and 1Password: 1. Create a new instance for customer auth in the FUTO ZITADEL Cloud account. One per env — prod first, dev/staging later (or vice versa). -2. Add the custom domain: - - prod → `auth.futo.tech` - - staging → `auth.staging.futo.tech` (or chosen equivalent) - - dev → `auth.dev.futo.tech` (or chosen equivalent) - - DNS is managed via the existing Cloudflare modules. +2. Custom domain — **prod only**: `auth.futo.cloud`. Add it on the instance in + the ZITADEL Cloud console. The `auth.futo.cloud` CNAME → the prod instance + URL is managed by this module (`dns.tf`, in the futo `futo.cloud` zone), so + no manual DNS is needed. dev/staging have no custom domain — they use the + generated `.zitadel.cloud` instance URL directly. 3. In each instance, create a machine user with role `IAM_OWNER`, generate a JWT key, and download the profile JSON. 4. Apply `modules/shared/1password/futo-account` — the manual-secrets module @@ -40,8 +39,9 @@ before Terraform can run. Do this in the ZITADEL Cloud console and 1Password: - `CUSTOMER_ZITADEL_SMTP_SENDER_ADDRESS` Replace each stub password in `yucca_tf_${env}_manual` with the real value - (custom domain from step 2, profile JSON from step 3, SMTP credentials - for the chosen provider, and sender address e.g. `no-reply@futo.tech`). + (`CUSTOMER_ZITADEL_DOMAIN` = the instance URL `.zitadel.cloud`, profile + JSON from step 3, SMTP credentials for the chosen provider, and sender + address e.g. `no-reply@futo.cloud`). Re-apply `shared/1password/futo-account` so the copy-secrets module mirrors each value into the `yucca_tf_${env}` vault that this module reads from via `data "onepassword_item"` lookups at plan/apply time. No diff --git a/tf/deployment/modules/scoped/zitadel/customer/config.tf b/tf/deployment/modules/scoped/zitadel/customer/config.tf index 036236a8..0f299911 100644 --- a/tf/deployment/modules/scoped/zitadel/customer/config.tf +++ b/tf/deployment/modules/scoped/zitadel/customer/config.tf @@ -7,6 +7,10 @@ terraform { source = "zitadel/zitadel" version = "2.12.8" } + cloudflare = { + source = "cloudflare/cloudflare" + version = "5.21.1" + } onepassword = { source = "1Password/onepassword" version = "~> 2.1" diff --git a/tf/deployment/modules/scoped/zitadel/customer/dns.tf b/tf/deployment/modules/scoped/zitadel/customer/dns.tf new file mode 100644 index 00000000..0e6205c0 --- /dev/null +++ b/tf/deployment/modules/scoped/zitadel/customer/dns.tf @@ -0,0 +1,29 @@ +data "terraform_remote_state" "futo_cloudflare_api_keys" { + backend = "pg" + + config = { + conn_str = var.tf_state_postgres_conn_str + schema_name = "prod_cloudflare_futo_api_keys" + } +} + +data "cloudflare_zone" "futo_cloud" { + filter = { + name = "futo.cloud" + } +} + +# Prod only: auth.futo.cloud -> the prod instance URL, which is exactly what +# CUSTOMER_ZITADEL_DOMAIN holds (the provider connects to it). dev/staging use +# the generated .zitadel.cloud URL directly with no custom domain. +# DNS-only so ZITADEL terminates TLS for the custom domain. +resource "cloudflare_dns_record" "customer_auth" { + count = var.env == "prod" ? 1 : 0 + + zone_id = data.cloudflare_zone.futo_cloud.id + name = "auth.futo.cloud" + type = "CNAME" + content = data.onepassword_item.customer_zitadel_domain.password + ttl = 1 + proxied = false +} diff --git a/tf/deployment/modules/scoped/zitadel/customer/providers.tf b/tf/deployment/modules/scoped/zitadel/customer/providers.tf index cf823817..d57926bf 100644 --- a/tf/deployment/modules/scoped/zitadel/customer/providers.tf +++ b/tf/deployment/modules/scoped/zitadel/customer/providers.tf @@ -7,3 +7,7 @@ provider "zitadel" { provider "onepassword" { service_account_token = var.futo_op_service_account_token } + +provider "cloudflare" { + api_token = data.terraform_remote_state.futo_cloudflare_api_keys.outputs.terraform_key_futo_cloudflare_account +} diff --git a/tf/deployment/modules/scoped/zitadel/customer/terragrunt.hcl b/tf/deployment/modules/scoped/zitadel/customer/terragrunt.hcl index 6d0fbaa3..9d44b6db 100644 --- a/tf/deployment/modules/scoped/zitadel/customer/terragrunt.hcl +++ b/tf/deployment/modules/scoped/zitadel/customer/terragrunt.hcl @@ -27,5 +27,5 @@ remote_state { } dependencies { - paths = [] + paths = ["../../../shared/cloudflare/futo-api-keys"] }