From 8d6cd81788089ee04dbbbc8024c4a0ff96860430 Mon Sep 17 00:00:00 2001 From: Thomas Rijpstra Date: Sat, 22 Feb 2025 11:55:14 +0100 Subject: [PATCH] WIP --- .../zitadel/identity-provider/google/main.tf | 19 ++ .../identity-provider/google/provider.tf | 56 ++++ .../identity-provider/google/variables.tf | 32 ++ infra/modules/zitadel/locals.tf | 3 + infra/modules/zitadel/main.tf | 141 +++++++++ .../zitadel/project/application/api/main.tf | 17 + .../project/application/api/provider.tf | 14 + .../project/application/api/variables.tf | 26 ++ .../project/application/user-agent/main.tf | 27 ++ .../application/user-agent/provider.tf | 14 + .../application/user-agent/variables.tf | 36 +++ infra/modules/zitadel/project/main.tf | 44 +++ infra/modules/zitadel/project/member/main.tf | 21 ++ .../zitadel/project/member/provider.tf | 14 + .../zitadel/project/member/variables.tf | 38 +++ infra/modules/zitadel/project/provider.tf | 14 + infra/modules/zitadel/project/roles/main.tf | 27 ++ .../modules/zitadel/project/roles/provider.tf | 14 + .../zitadel/project/roles/variables.tf | 32 ++ infra/modules/zitadel/project/variables.tf | 50 +++ infra/modules/zitadel/provider.tf | 33 ++ infra/modules/zitadel/service-account/main.tf | 21 ++ .../zitadel/service-account/provider.tf | 14 + .../zitadel/service-account/variables.tf | 38 +++ infra/modules/zitadel/tenant/main.tf | 0 infra/modules/zitadel/tenant/provider.tf | 0 .../modules/zitadel/tenant/role-owner/main.tf | 30 ++ .../zitadel/tenant/role-owner/provider.tf | 14 + .../zitadel/tenant/role-owner/variables.tf | 22 ++ infra/modules/zitadel/tenant/variables.tf | 56 ++++ infra/modules/zitadel/values.yaml.tftpl | 51 +++ infra/modules/zitadel/variables.tf | 51 +++ shuttles/terraform-configure/main.tf | 174 +++++++++++ shuttles/terraform-zitadel-365zon/main.tf | 295 ++++++++++++++++++ shuttles/terraform/zitadel-admin-sa.json | 0 35 files changed, 1438 insertions(+) create mode 100644 infra/modules/zitadel/identity-provider/google/main.tf create mode 100644 infra/modules/zitadel/identity-provider/google/provider.tf create mode 100644 infra/modules/zitadel/identity-provider/google/variables.tf create mode 100644 infra/modules/zitadel/locals.tf create mode 100644 infra/modules/zitadel/main.tf create mode 100644 infra/modules/zitadel/project/application/api/main.tf create mode 100644 infra/modules/zitadel/project/application/api/provider.tf create mode 100644 infra/modules/zitadel/project/application/api/variables.tf create mode 100644 infra/modules/zitadel/project/application/user-agent/main.tf create mode 100644 infra/modules/zitadel/project/application/user-agent/provider.tf create mode 100644 infra/modules/zitadel/project/application/user-agent/variables.tf create mode 100644 infra/modules/zitadel/project/main.tf create mode 100644 infra/modules/zitadel/project/member/main.tf create mode 100644 infra/modules/zitadel/project/member/provider.tf create mode 100644 infra/modules/zitadel/project/member/variables.tf create mode 100644 infra/modules/zitadel/project/provider.tf create mode 100644 infra/modules/zitadel/project/roles/main.tf create mode 100644 infra/modules/zitadel/project/roles/provider.tf create mode 100644 infra/modules/zitadel/project/roles/variables.tf create mode 100644 infra/modules/zitadel/project/variables.tf create mode 100644 infra/modules/zitadel/provider.tf create mode 100644 infra/modules/zitadel/service-account/main.tf create mode 100644 infra/modules/zitadel/service-account/provider.tf create mode 100644 infra/modules/zitadel/service-account/variables.tf create mode 100644 infra/modules/zitadel/tenant/main.tf create mode 100644 infra/modules/zitadel/tenant/provider.tf create mode 100644 infra/modules/zitadel/tenant/role-owner/main.tf create mode 100644 infra/modules/zitadel/tenant/role-owner/provider.tf create mode 100644 infra/modules/zitadel/tenant/role-owner/variables.tf create mode 100644 infra/modules/zitadel/tenant/variables.tf create mode 100644 infra/modules/zitadel/values.yaml.tftpl create mode 100644 infra/modules/zitadel/variables.tf create mode 100644 shuttles/terraform-configure/main.tf create mode 100644 shuttles/terraform-zitadel-365zon/main.tf create mode 100644 shuttles/terraform/zitadel-admin-sa.json diff --git a/infra/modules/zitadel/identity-provider/google/main.tf b/infra/modules/zitadel/identity-provider/google/main.tf new file mode 100644 index 0000000..fdb6abc --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/main.tf @@ -0,0 +1,19 @@ +resource "zitadel_org" "default" { + depends_on = [var.wait_on] + name = var.name +} + +#resource "zitadel_idp_google" "default" { +# name = "Google" +# client_id = "182902..." +# client_secret = "GOCSPX-*****" +# scopes = ["openid", "profile", "email"] +# is_linking_allowed = false +# is_creation_allowed = true +# is_auto_creation = false +# is_auto_update = true +# auto_linking = "AUTO_LINKING_OPTION_USERNAME" +#} + +#google_client_id = "783390190667-0nkts50perpmhott4i7ro1ob5n7koi5i.apps.googleusercontent.com" +#google_client_secret = "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" diff --git a/infra/modules/zitadel/identity-provider/google/provider.tf b/infra/modules/zitadel/identity-provider/google/provider.tf new file mode 100644 index 0000000..2550782 --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/provider.tf @@ -0,0 +1,56 @@ +locals { + k8s_config = yamldecode(var.k8s_config_yaml) + k8s_host = local.k8s_config.clusters[0].cluster.server + k8s_auth = try( + { + token = local.k8s_config.users[0].user.token + using_token = true + }, + { + client_certificate = base64decode(local.k8s_config.users[0].user["client-certificate-data"]) + client_key = base64decode(local.k8s_config.users[0].user["client-key-data"]) + using_token = false + } + ) +} + +provider "kubernetes" { + host = local.k8s_host + insecure = true + token = local.k8s_auth.using_token ? local.k8s_auth.token : null + client_certificate = local.k8s_auth.using_token ? null : local.k8s_auth.client_certificate + client_key = local.k8s_auth.using_token ? null : local.k8s_auth.client_key +} + +provider "helm" { + kubernetes { + host = local.k8s_host + insecure = true + token = local.k8s_auth.using_token ? local.k8s_auth.token : null + client_certificate = local.k8s_auth.using_token ? null : local.k8s_auth.client_certificate + client_key = local.k8s_auth.using_token ? null : local.k8s_auth.client_key + } +} + +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "1.2.0" + } + } +} + +data "kubernetes_secret" "zitadel_admin" { + depends_on = [var.wait_on] + metadata { + name = var.secret + namespace = var.namespace + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_json = data.kubernetes_secret.zitadel_admin.data["${var.secret}.json"] +} diff --git a/infra/modules/zitadel/identity-provider/google/variables.tf b/infra/modules/zitadel/identity-provider/google/variables.tf new file mode 100644 index 0000000..d3009d3 --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/variables.tf @@ -0,0 +1,32 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "secret" { + type = string + description = "Secret holding the JWT Profile JSON" +} + +variable "namespace" { + type = string + description = "Namespace holding the secret" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "k8s_config_yaml" { + description = "Content of k8s config yaml file" + type = string +} + +variable "name" { + type = string + description = "Name of the tenant" + default = "fourlights" +} diff --git a/infra/modules/zitadel/locals.tf b/infra/modules/zitadel/locals.tf new file mode 100644 index 0000000..6bf05a2 --- /dev/null +++ b/infra/modules/zitadel/locals.tf @@ -0,0 +1,3 @@ +locals { + service_uri = join(".", [var.service_name, var.server_dns]) +} diff --git a/infra/modules/zitadel/main.tf b/infra/modules/zitadel/main.tf new file mode 100644 index 0000000..a9f8d18 --- /dev/null +++ b/infra/modules/zitadel/main.tf @@ -0,0 +1,141 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.31.0" + } + } +} + +resource "kubernetes_namespace" "fusionauth" { + metadata { + name = var.namespace + } + + lifecycle { + ignore_changes = [metadata] + } +} + +data "kubernetes_secret" "bridge-tls" { + metadata { + name = "bridge-tls" + namespace = "cert-manager" + } +} + +resource "kubernetes_secret" "fusionauth-tls" { + metadata { + name = "fusionauth-tls" + namespace = kubernetes_namespace.fusionauth.metadata[0].name + } + + data = data.kubernetes_secret.bridge-tls.data + type = data.kubernetes_secret.bridge-tls.type +} + +resource "kubernetes_secret" "postgresql-auth" { + metadata { + name = "postgresql-auth" + namespace = kubernetes_namespace.fusionauth.metadata[0].name + } + + data = { + password = var.database_password + } +} + +resource "random_password" "api_key" { + length = 32 + special = false +} + +resource "random_password" "admin" { + length = 32 + special = false +} + +resource "random_uuid" "default_tenant_id" {} + +resource "helm_release" "fusionauth" { + depends_on = [var.wait_on, kubernetes_secret.postgresql-auth, kubernetes_secret.fusionauth-tls] + name = "fusionauth" + repository = "https://fusionauth.github.io/charts" + chart = "fusionauth" + namespace = kubernetes_namespace.fusionauth.metadata[0].name + version = "1.0.10" + create_namespace = false + wait = true + wait_for_jobs = true + + values = [ + templatefile("${path.module}/values.yaml", { + service_uri = local.service_uri, + database = var.database, + database_username = var.database_username, + database_root_username = var.database_root_password != null ? var.database_root_username : null, + + # TODO: Add theme customization, and use as default + + kickstart_json = jsonencode({ + variables = { + defaultTenantId = random_uuid.default_tenant_id.result + adminEmail = "engineering@fourlights.nl" + adminPassword = random_password.admin.result + } + apiKeys = [{ key = random_password.api_key.result, description = "Terraform API Key" }], + requests = [ + { + "method" : "POST", + "url" : "/api/user/registration/00000000-0000-0000-0000-000000000001", + "body" : { + "user" : { + "email" : "#{adminEmail}", + "firstName" : "Thomas", + "lastName" : "Rijpstra", + "password" : "#{adminPassword}", + "data" : { + "Company" : "Four Lights", + "user_type" : "iconclast" + } + }, + "registration" : { + "applicationId" : "#{FUSIONAUTH_APPLICATION_ID}", + "roles" : [ + "admin" + ] + } + } + } + ], + }) + }) + ] +} + +output "installed" { + value = true + depends_on = [helm_release.fusionauth] +} + +output "api_key" { + value = random_password.api_key.result + sensitive = true +} + +output "admin_password" { + value = random_password.admin.result + sensitive = true +} + +output "server" { + value = local.service_uri +} + +output "default_tenant_id" { + value = random_uuid.default_tenant_id.result +} + +output "uri" { + value = "https://${local.service_uri}" +} diff --git a/infra/modules/zitadel/project/application/api/main.tf b/infra/modules/zitadel/project/application/api/main.tf new file mode 100644 index 0000000..9895069 --- /dev/null +++ b/infra/modules/zitadel/project/application/api/main.tf @@ -0,0 +1,17 @@ +resource "zitadel_project_member" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + user_id = var.user_id + roles = var.roles +} + +output "installed" { + value = true + depends_on = [zitadel_project_member.default] +} + +output "project_member_id" { + value = zitadel_project_member.default.id +} diff --git a/infra/modules/zitadel/project/application/api/provider.tf b/infra/modules/zitadel/project/application/api/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/project/application/api/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/project/application/api/variables.tf b/infra/modules/zitadel/project/application/api/variables.tf new file mode 100644 index 0000000..ae9b5e2 --- /dev/null +++ b/infra/modules/zitadel/project/application/api/variables.tf @@ -0,0 +1,26 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} diff --git a/infra/modules/zitadel/project/application/user-agent/main.tf b/infra/modules/zitadel/project/application/user-agent/main.tf new file mode 100644 index 0000000..220b565 --- /dev/null +++ b/infra/modules/zitadel/project/application/user-agent/main.tf @@ -0,0 +1,27 @@ +resource "zitadel_application_oidc" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + name = var.name + +} + +output "installed" { + value = true + depends_on = [zitadel_application_oidc.default] +} + +output "application_id" { + value = zitadel_application_oidc.default.id +} + +output "client_id" { + value = zitadel_application_oidc.default.client_id + sensitive = true +} + +output "client_secret" { + value = zitadel_application_oidc.default.client_secret + sensitive = true +} diff --git a/infra/modules/zitadel/project/application/user-agent/provider.tf b/infra/modules/zitadel/project/application/user-agent/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/project/application/user-agent/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/project/application/user-agent/variables.tf b/infra/modules/zitadel/project/application/user-agent/variables.tf new file mode 100644 index 0000000..6ed21a6 --- /dev/null +++ b/infra/modules/zitadel/project/application/user-agent/variables.tf @@ -0,0 +1,36 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "name" { + type = string + description = "Application name" +} + +variable "api_url" { + type = string + description = "Uri on which the API is available" +} diff --git a/infra/modules/zitadel/project/main.tf b/infra/modules/zitadel/project/main.tf new file mode 100644 index 0000000..e116eeb --- /dev/null +++ b/infra/modules/zitadel/project/main.tf @@ -0,0 +1,44 @@ +resource "zitadel_org_idp_google" "default" { + depends_on = [var.wait_on] + org_id = var.org_id + name = "Google" + client_id = var.client_id + client_secret = var.client_secret + scopes = var.options.scopes + is_linking_allowed = var.options.is_linking_allowed + is_creation_allowed = var.options.is_creation_allowed + is_auto_creation = var.options.is_auto_creation + is_auto_update = var.options.is_auto_update + auto_linking = var.options.auto_linking +} + +resource "zitadel_login_policy" "default" { + depends_on = [zitadel_org_idp_google.default] + + org_id = var.org_id + user_login = true + allow_register = true + allow_external_idp = true + force_mfa = false + force_mfa_local_only = false + passwordless_type = "PASSWORDLESS_TYPE_ALLOWED" + hide_password_reset = "false" + password_check_lifetime = "240h0m0s" + external_login_check_lifetime = "240h0m0s" + multi_factor_check_lifetime = "24h0m0s" + mfa_init_skip_lifetime = "720h0m0s" + second_factor_check_lifetime = "24h0m0s" + ignore_unknown_usernames = true + default_redirect_uri = "https://${var.domain}" + second_factors = ["SECOND_FACTOR_TYPE_OTP", "SECOND_FACTOR_TYPE_U2F"] + multi_factors = ["MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION"] + idps = [zitadel_org_idp_google.default.id] + allow_domain_discovery = true + disable_login_with_email = true + disable_login_with_phone = true +} + +output "installed" { + value = true + depends_on = [zitadel_org_idp_google.default, zitadel_login_policy.default] +} diff --git a/infra/modules/zitadel/project/member/main.tf b/infra/modules/zitadel/project/member/main.tf new file mode 100644 index 0000000..a7322b4 --- /dev/null +++ b/infra/modules/zitadel/project/member/main.tf @@ -0,0 +1,21 @@ +resource "zitadel_project_role" "default" { + count = length(var.roles) + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + role_key = var.roles[count.index] + display_name = var.roles[count.index] + group = var.group +} + +output "installed" { + value = true + depends_on = [zitadel_project_role.default] +} + +output "role_ids" { + value = toset([ + for role in zitadel_project_role.default : role.id + ]) +} diff --git a/infra/modules/zitadel/project/member/provider.tf b/infra/modules/zitadel/project/member/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/project/member/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/project/member/variables.tf b/infra/modules/zitadel/project/member/variables.tf new file mode 100644 index 0000000..0b5b575 --- /dev/null +++ b/infra/modules/zitadel/project/member/variables.tf @@ -0,0 +1,38 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "group" { + type = string + description = "Optional group name" + default = null +} + +variable "roles" { + type = list(string) + description = "Roles to be added" + default = [] +} diff --git a/infra/modules/zitadel/project/provider.tf b/infra/modules/zitadel/project/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/project/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/project/roles/main.tf b/infra/modules/zitadel/project/roles/main.tf new file mode 100644 index 0000000..611f668 --- /dev/null +++ b/infra/modules/zitadel/project/roles/main.tf @@ -0,0 +1,27 @@ +resource "zitadel_project" "default" { + depends_on = [var.wait_on] + org_id = var.org_id + name = var.name + project_role_assertion = true + project_role_check = true + has_project_check = true + private_labeling_setting = "PRIVATE_LABELING_SETTING_ENFORCE_PROJECT_RESOURCE_OWNER_POLICY" +} + +resource "zitadel_project_member" "default" { + count = length(var.owners) + + org_id = var.org_id + project_id = zitadel_project.default.id + user_id = var.owners[count.index] + roles = ["PROJECT_OWNER"] +} + +output "installed" { + value = true + depends_on = [zitadel_project.default, zitadel_project_member.default] +} + +output "project_id" { + value = zitadel_project.default.id +} diff --git a/infra/modules/zitadel/project/roles/provider.tf b/infra/modules/zitadel/project/roles/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/project/roles/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/project/roles/variables.tf b/infra/modules/zitadel/project/roles/variables.tf new file mode 100644 index 0000000..28d991a --- /dev/null +++ b/infra/modules/zitadel/project/roles/variables.tf @@ -0,0 +1,32 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "name" { + type = string + description = "Name of the project" +} + +variable "owners" { + type = list(string) + description = "User IDs to be granted `PROJECT_OWNER` role" + default = [] +} diff --git a/infra/modules/zitadel/project/variables.tf b/infra/modules/zitadel/project/variables.tf new file mode 100644 index 0000000..c648d93 --- /dev/null +++ b/infra/modules/zitadel/project/variables.tf @@ -0,0 +1,50 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "client_id" { + type = string + description = "Google Client ID" +} + +variable "client_secret" { + type = string + description = "Google Client Secret" +} + +variable "options" { + type = object({ + scopes = list(string) + is_linking_allowed = bool + is_creation_allowed = bool + is_auto_creation = bool + is_auto_update = bool + auto_linking = string + }) + default = { + scopes = ["openid", "profile", "email"], + is_linking_allowed = false + is_creation_allowed = true + is_auto_creation = false + is_auto_update = true + auto_linking = "AUTO_LINKING_OPTION_USERNAME" + } +} diff --git a/infra/modules/zitadel/provider.tf b/infra/modules/zitadel/provider.tf new file mode 100644 index 0000000..7b43e74 --- /dev/null +++ b/infra/modules/zitadel/provider.tf @@ -0,0 +1,33 @@ +locals { + k8s_config = yamldecode(var.k8s_config_yaml) + k8s_host = local.k8s_config.clusters[0].cluster.server + k8s_auth = try( + { + token = local.k8s_config.users[0].user.token + using_token = true + }, + { + client_certificate = base64decode(local.k8s_config.users[0].user["client-certificate-data"]) + client_key = base64decode(local.k8s_config.users[0].user["client-key-data"]) + using_token = false + } + ) +} + +provider "kubernetes" { + host = local.k8s_host + insecure = true + token = local.k8s_auth.using_token ? local.k8s_auth.token : null + client_certificate = local.k8s_auth.using_token ? null : local.k8s_auth.client_certificate + client_key = local.k8s_auth.using_token ? null : local.k8s_auth.client_key +} + +provider "helm" { + kubernetes { + host = local.k8s_host + insecure = true + token = local.k8s_auth.using_token ? local.k8s_auth.token : null + client_certificate = local.k8s_auth.using_token ? null : local.k8s_auth.client_certificate + client_key = local.k8s_auth.using_token ? null : local.k8s_auth.client_key + } +} diff --git a/infra/modules/zitadel/service-account/main.tf b/infra/modules/zitadel/service-account/main.tf new file mode 100644 index 0000000..a7322b4 --- /dev/null +++ b/infra/modules/zitadel/service-account/main.tf @@ -0,0 +1,21 @@ +resource "zitadel_project_role" "default" { + count = length(var.roles) + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + role_key = var.roles[count.index] + display_name = var.roles[count.index] + group = var.group +} + +output "installed" { + value = true + depends_on = [zitadel_project_role.default] +} + +output "role_ids" { + value = toset([ + for role in zitadel_project_role.default : role.id + ]) +} diff --git a/infra/modules/zitadel/service-account/provider.tf b/infra/modules/zitadel/service-account/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/service-account/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/service-account/variables.tf b/infra/modules/zitadel/service-account/variables.tf new file mode 100644 index 0000000..0b5b575 --- /dev/null +++ b/infra/modules/zitadel/service-account/variables.tf @@ -0,0 +1,38 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "group" { + type = string + description = "Optional group name" + default = null +} + +variable "roles" { + type = list(string) + description = "Roles to be added" + default = [] +} diff --git a/infra/modules/zitadel/tenant/main.tf b/infra/modules/zitadel/tenant/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/infra/modules/zitadel/tenant/provider.tf b/infra/modules/zitadel/tenant/provider.tf new file mode 100644 index 0000000..e69de29 diff --git a/infra/modules/zitadel/tenant/role-owner/main.tf b/infra/modules/zitadel/tenant/role-owner/main.tf new file mode 100644 index 0000000..e8f5117 --- /dev/null +++ b/infra/modules/zitadel/tenant/role-owner/main.tf @@ -0,0 +1,30 @@ +resource "zitadel_org" "default" { + depends_on = [var.wait_on] + name = var.name + is_default = true +} + +output "org_id" { + value = zitadel_org.default.id +} + +output "installed" { + value = true + depends_on = [zitadel_org.default] +} + + +#resource "zitadel_idp_google" "default" { +# name = "Google" +# client_id = "182902..." +# client_secret = "GOCSPX-*****" +# scopes = ["openid", "profile", "email"] +# is_linking_allowed = false +# is_creation_allowed = true +# is_auto_creation = false +# is_auto_update = true +# auto_linking = "AUTO_LINKING_OPTION_USERNAME" +#} + +#google_client_id = "783390190667-0nkts50perpmhott4i7ro1ob5n7koi5i.apps.googleusercontent.com" +#google_client_secret = "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" diff --git a/infra/modules/zitadel/tenant/role-owner/provider.tf b/infra/modules/zitadel/tenant/role-owner/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/modules/zitadel/tenant/role-owner/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = var.domain + insecure = "false" + jwt_profile_file = var.jwt_profile_file +} diff --git a/infra/modules/zitadel/tenant/role-owner/variables.tf b/infra/modules/zitadel/tenant/role-owner/variables.tf new file mode 100644 index 0000000..f7674df --- /dev/null +++ b/infra/modules/zitadel/tenant/role-owner/variables.tf @@ -0,0 +1,22 @@ +variable "domain" { + type = string + description = "Domain for the zitadel instance" + default = "localhost" +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "name" { + type = string + description = "Name of the tenant" + default = "fourlights" +} + +variable "jwt_profile_file" { + type = string + description = "Path to the jwt profile file" +} diff --git a/infra/modules/zitadel/tenant/variables.tf b/infra/modules/zitadel/tenant/variables.tf new file mode 100644 index 0000000..5916ab1 --- /dev/null +++ b/infra/modules/zitadel/tenant/variables.tf @@ -0,0 +1,56 @@ +variable "service_name" { + type = string + description = "Name of the service" + default = "auth" +} + +variable "server_dns" { + type = string + description = "Domain for the server" +} + +variable "k8s_config_yaml" { + description = "Content of k8s config yaml file" + type = string +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "namespace" { + type = string +} + +variable "database" { + type = string + default = "zitadel" +} + +variable "database_username" { + type = string + default = "zitadel" +} + +variable "database_password" { + type = string + sensitive = true +} + +variable "database_root_username" { + type = string + default = "postgres" +} + +variable "database_root_password" { + type = string + sensitive = true + default = null +} + +variable "display_on_homepage" { + type = bool + default = false +} diff --git a/infra/modules/zitadel/values.yaml.tftpl b/infra/modules/zitadel/values.yaml.tftpl new file mode 100644 index 0000000..1a11e3f --- /dev/null +++ b/infra/modules/zitadel/values.yaml.tftpl @@ -0,0 +1,51 @@ +zitadel: + masterkeySecretName: "zitadel" + configmapConfig: + ExternalSecure: false + ExternalDomain: ${ external_domain } + TLS: + Enabled: false + #ExternalPort: 443 + #ExternalSecurePort: 443 + #ExternalPathPrefix: ${ external_path_prefix } + #ExternalTLS: false + #ExternalTLSAllowInsecure: false + #ExternalTLSAllowInsecureSkipVerify: false + Database: + Postgres: + Host: postgresql-hl.postgresql.svc.cluster.local + Port: 5432 + Database: ${ database } + MaxOpenConns: 20 + MaxIdleConns: 10 + MaxConnLifetime: 30m + MaxConnIdleTime: 5m + User: + Username: ${ database_username } + PasswordSecret: postgresql-auth + PasswordSecretKey: password + SSL: + Mode: disable + Admin: + Username: ${ database_root_username } + PasswordSecret: postgresql-auth + PasswordSecretKey: root-password + +ingress: + enabled: true + className: traefik + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: web + %{ if displayOnHomepage } + gethomepage.dev/enabled: "true" + gethomepage.dev/name: "Zitadel" + gethomepage.dev/description: "Identity and Access Management" + gethomepage.dev/group: "Tools" + gethomepage.dev/icon: "zitadel.png" + %{ endif } + hosts: + - host: ${service_uri} + paths: + - path: / + pathType: Prefix diff --git a/infra/modules/zitadel/variables.tf b/infra/modules/zitadel/variables.tf new file mode 100644 index 0000000..8f92245 --- /dev/null +++ b/infra/modules/zitadel/variables.tf @@ -0,0 +1,51 @@ +variable "service_name" { + type = string + description = "Name of the service" + default = "auth" +} + +variable "server_dns" { + type = string + description = "Domain for the server" +} + +variable "k8s_config_yaml" { + description = "Content of k8s config yaml file" + type = string +} + +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "namespace" { + type = string +} + +variable "database" { + type = string + default = "fusionauth" +} + +variable "database_username" { + type = string + default = "fusionauth" +} + +variable "database_password" { + type = string + sensitive = true +} + +variable "database_root_username" { + type = string + default = "postgres" +} + +variable "database_root_password" { + type = string + sensitive = true + default = null +} diff --git a/shuttles/terraform-configure/main.tf b/shuttles/terraform-configure/main.tf new file mode 100644 index 0000000..58151c5 --- /dev/null +++ b/shuttles/terraform-configure/main.tf @@ -0,0 +1,174 @@ +locals { + tld = "fourlights.dev" + cluster_dns = "venus.${local.tld}" + bridge_dns = "bridge.${local.cluster_dns}" + is_installed = true + node_count = 3 +} + +resource "kubernetes_manifest" "preserve-host-middleware" { + depends_on = [local.is_installed] + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "preserve-host-headers" + namespace = "default" # NOTE: Hardcoded by design + } + spec = { + headers = { + customRequestHeaders = { + "X-Forwarded-Proto" = "https" + "X-Forwarded-Port" = "443" + } + } + } + } +} + +resource "kubernetes_manifest" "https-redirect-middleware" { + depends_on = [local.is_installed] + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "redirect-to-https" + namespace = "default" # NOTE: Hardcoded by design + } + spec = { + redirectScheme = { + permanent = true + scheme = "https" + } + } + } +} + +module "homepage" { + source = "../../infra/modules/homepage" + wait_on = local.is_installed + k8s_config_yaml = local.k8s_config_yaml + + server_dns = local.cluster_dns + service_name = "homepage" + service_uri = local.cluster_dns + namespace = "homepage" +} + +module "minio" { + source = "../../infra/modules/minio" + wait_on = local.is_installed + k8s_config_yaml = local.k8s_config_yaml + + server_dns = local.cluster_dns + service_name = "storage" + namespace = "minio" + + admin_server_dns = local.cluster_dns # Restricted admin access, access via bridge + + tls = false + admin = true + ingressClass = "traefik" + storageSize = "10Gi" + + displayOnHomepage = true +} + +module "mongodb" { + source = "../../infra/modules/mongodb" + wait_on = local.is_installed + k8s_config_yaml = local.k8s_config_yaml + + namespace = "mongodb" + replicas = local.node_count +} + +module "rabbitmq" { + source = "../../infra/modules/rabbitmq" + wait_on = local.is_installed + k8s_config_yaml = local.k8s_config_yaml + + server_dns = "local" # Restricted admin access, access via bridge + + service_name = "rabbitmq" + namespace = "rabbitmq" + + tls = false + admin = true + ingressClass = "traefik" +} + +module "postgresql" { + source = "../../infra/modules/postgresql" + + namespace = "postgresql" + k8s_config_yaml = local.k8s_config_yaml + username = "bridge" +} + +module "zitadel-db" { + source = "../../infra/modules/postgresql/tenant" + wait_on = module.postgresql.installed + + name = "zitadel" + root_password = module.postgresql.root_password + k8s_config_yaml = local.k8s_config_yaml +} + +module "zitadel" { + source = "../../infra/modules/zitadel" + wait_on = module.zitadel-db.installed + k8s_config_yaml = local.k8s_config_yaml + + server_dns = local.cluster_dns + + service_name = "zitadel" + namespace = "zitadel" + + database_password = module.zitadel-db.password + database_root_password = module.postgresql.root_password + + display_on_homepage = true +} + +module "zitadel-tenant" { + source = "../../infra/modules/zitadel/tenant" + wait_on = module.zitadel.installed + + domain = module.zitadel.server + name = "fourlights" + jwt_profile_file = module.zitadel.jwt_profile_file +} + +module "zitadel-idp-google" { + source = "../../infra/modules/zitadel/identity-provider/google" + wait_on = module.zitadel-tenant.installed + + domain = module.zitadel.server + jwt_profile_file = module.zitadel.jwt_profile_file + org_id = module.zitadel-tenant.org_id + client_id = "783390190667-0nkts50perpmhott4i7ro1ob5n7koi5i.apps.googleusercontent.com" + client_secret = "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" + + options = { + scopes = ["openid", "profile", "email"] + is_auto_creation = true + is_auto_update = true + is_creation_allowed = true + is_linking_allowed = false + + auto_linking = "AUTO_LINKING_OPTION_USERNAME" + } +} + +// module "zitadel-machine-user" { +// source = "../../infra/modules/zitadel/tenant" +// wait_on = module.zitadel.installed +// k8s_config_yaml = local.k8s_config_yaml +// +// domain = module.zitadel.server +// secret = "zitadel-admin-sa" +// namespace = "zitadel" +// name = "fourlights" +// } + diff --git a/shuttles/terraform-zitadel-365zon/main.tf b/shuttles/terraform-zitadel-365zon/main.tf new file mode 100644 index 0000000..b638b47 --- /dev/null +++ b/shuttles/terraform-zitadel-365zon/main.tf @@ -0,0 +1,295 @@ +locals { + tld = "fourlights.dev" + cluster_dns = "venus.${local.tld}" + domain = "zitadel.${local.cluster_dns}" + org_domain = "fourlights.${local.domain}" + jwt_profile_file = "../terraform/zitadel-admin-sa.json" + name = "365Zon" + + user_id = "308083708882059797" +} + +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +provider "zitadel" { + domain = local.domain + insecure = "false" + jwt_profile_file = local.jwt_profile_file +} + +data "zitadel_orgs" "default" { + domain = local.domain +} + +data "zitadel_org" "default" { + for_each = toset(data.zitadel_orgs.default.ids) + id = each.value +} + +module "zitadel_project" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + name = local.name + owners = [local.user_id] +} + +// TODO: add action for setting roles as scopes + +module "zitadel_project_operator_roles" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/roles" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + group = "Operator" + roles = [ + "manage:profiles", "manage:contacts", "manage:addresses", "manage:enquiries", "manage:flowstates", + "manage:flowevents", "manage:files" + ] +} + +module "zitadel_project_configurator_roles" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/roles" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + group = "Configurator" + roles = [ + "manage:brands", "manage:flows" + ] +} + +module "zitadel_project_developer_roles" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/roles" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + group = "Developer" + roles = [ + "manage:jobs", "manage:infrastructure" + ] +} + +// TODO: Move External (and 365zon Push service account) to own project +// TODO: Add grant for external project +// TODO: Add read roles + +module "zitadel_project_application_core_api" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/api" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Core API" +} + +module "zitadel_project_application_core_ua" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/applicaitn/user-agent" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Core (Swagger)" + + +} + +module "zitadel_project_application_module_365zon_api" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/api" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: Salesforce Pull API" +} + +module "zitadel_project_application_module_365zon_ua" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/user-agent" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: Salesforce Pull (Swagger)" +} + +module "zitadel_project_application_module_external_api" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/api" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: External API" +} + +module "zitadel_project_application_module_external_ua" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/user-agent" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: External (Swagger)" +} + +module "zitadel_project_application_module_internal_api" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/api" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: Internal API" +} + +module "zitadel_project_application_module_internal_ua" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/application/user-agent" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + project_id = module.zitadel_project[count.index].project_id + jwt_profile_file = local.jwt_profile_file + + name = "Module: Internal swagger" +} + +module "zitadel_service_account_module_internal" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/service-account" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + user_name = "${local.name}-module-internal@${ local.org_domain }" + name = "Module Internal @ ${local.name}" + + with_secret = true + access_token_type = "ACCESS_TOKEN_TYPE_JWT" +} + +module "zitadel_project_member_module_internal" { + wait_on = module.zitadel_project_operator_roles[count.index].installed + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/member" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + project_id = module.zitadel_project[count.index].project_id + user_id = module.zitadel_service_account_module_internal[count.index].user_id + + roles = module.zitadel_project_operator_roles[count.index].roles +} + +module "zitadel_service_account_module_external" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/service-account" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + user_name = "${local.name}-module-external@${ local.org_domain }" + name = "Module External @ ${local.name}" + + with_secret = true + access_token_type = "ACCESS_TOKEN_TYPE_JWT" +} + +module "zitadel_project_member_module_external" { + wait_on = module.zitadel_project_operator_roles[count.index].installed + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/member" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + project_id = module.zitadel_project[count.index].project_id + user_id = module.zitadel_service_account_module_external[count.index].user_id + + roles = module.zitadel_project_operator_roles[count.index].roles +} + +module "zitadel_service_account_module_365zon" { + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/service-account" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + user_name = "${local.name}-module-365zon@${ local.org_domain }" + name = "Module 365Zon @ ${local.name}" + + with_secret = true + access_token_type = "ACCESS_TOKEN_TYPE_JWT" +} + +module "zitadel_project_member_module_365zon" { + wait_on = module.zitadel_project_operator_roles[count.index].installed + count = data.zitadel_org.default.count + source = "../../infra/modules/zitadel/project/member" + + domain = local.domain + org_id = data.zitadel_org.default[count.index].id + jwt_profile_file = local.jwt_profile_file + + project_id = module.zitadel_project[count.index].project_id + user_id = module.zitadel_service_account_module_365zon[count.index].user_id + + roles = module.zitadel_project_operator_roles[count.index].roles +} + +// TODO: Application for Front-End End (implicit, authorization_code, refresh_token) +// TODO: Update API applications with callback apiDomain/swagger/oauth2-redirect.html to allow logging in for swagger (and probably hangire?) +// TODO: Put all the relevant secrets into secret manager +// TODO: Set up opentelemetry and update appinsights shit to use that. + +output "org_ids" { + value = data.zitadel_orgs.default.ids +} + +output "project_ids" { + value = [for project in module.zitadel_project : project.project_id] +} diff --git a/shuttles/terraform/zitadel-admin-sa.json b/shuttles/terraform/zitadel-admin-sa.json new file mode 100644 index 0000000..e69de29