From ed1eef9db075e9d7293fd8868351687ec3bdbd33 Mon Sep 17 00:00:00 2001 From: Thomas Rijpstra Date: Tue, 22 Apr 2025 17:43:14 +0200 Subject: [PATCH] feat(tenants): add tenant-specific configurations - Add tenant configuration for 365zon - Configure Zitadel integration for ArgoCD - Add Fourlights tenant Zitadel configuration --- infra/tenants/365zon/main.tf | 56 +++++++ infra/tenants/365zon/provider.tf | 50 ++++++ infra/tenants/365zon/variables.tf | 21 +++ infra/tenants/365zon/zitadel/main.tf | 153 ++++++++++++++++++ infra/tenants/365zon/zitadel/provider.tf | 8 + infra/tenants/365zon/zitadel/variables.tf | 15 ++ .../argocd/zitadel/groupsClaim.action.tftpl | 28 ++++ infra/tenants/argocd/zitadel/main.tf | 113 +++++++++++++ infra/tenants/argocd/zitadel/provider.tf | 14 ++ infra/tenants/argocd/zitadel/variables.tf | 17 ++ infra/tenants/fourlights/zitadel/main.tf | 54 +++++++ infra/tenants/fourlights/zitadel/provider.tf | 50 ++++++ infra/tenants/fourlights/zitadel/variables.tf | 2 + 13 files changed, 581 insertions(+) create mode 100644 infra/tenants/365zon/main.tf create mode 100644 infra/tenants/365zon/provider.tf create mode 100644 infra/tenants/365zon/variables.tf create mode 100644 infra/tenants/365zon/zitadel/main.tf create mode 100644 infra/tenants/365zon/zitadel/provider.tf create mode 100644 infra/tenants/365zon/zitadel/variables.tf create mode 100644 infra/tenants/argocd/zitadel/groupsClaim.action.tftpl create mode 100644 infra/tenants/argocd/zitadel/main.tf create mode 100644 infra/tenants/argocd/zitadel/provider.tf create mode 100644 infra/tenants/argocd/zitadel/variables.tf create mode 100644 infra/tenants/fourlights/zitadel/main.tf create mode 100644 infra/tenants/fourlights/zitadel/provider.tf create mode 100644 infra/tenants/fourlights/zitadel/variables.tf diff --git a/infra/tenants/365zon/main.tf b/infra/tenants/365zon/main.tf new file mode 100644 index 0000000..cb8eb99 --- /dev/null +++ b/infra/tenants/365zon/main.tf @@ -0,0 +1,56 @@ +locals { + name = "365Zon" +} + +resource "kubernetes_namespace" "tenant" { + metadata { + name = lower(local.name) + } + + lifecycle { + ignore_changes = [metadata] + } +} + +module "bootstrap-zitadel" { + source = "./zitadel" + + namespace = kubernetes_namespace.tenant.metadata[0].name + org_id = var.org_id + user_id = var.user_id + name = local.name +} + +// create uploads bucket in minio + +// create minio secret +resource "kubernetes_secret" "storage" { + metadata { + name = "storage" + namespace = kubernetes_namespace.tenant.metadata[0].name + } + + data = { + Storage__AccountName = var.minio_access_key + Storage__AccountKey = var.minio_secret_key + Storage__BlobUri = var.minio_service_uri + Storage__S3BucketName = "uploads" + } +} + +resource "kubernetes_secret" "connection_strings" { + metadata { + name = "connection-strings" + namespace = kubernetes_namespace.tenant.metadata[0].name + } + + data = { + ConnectionStrings__DocumentDb = var.mongodb_connection_string + ConnectionStrings__ServiceBus = var.rabbitmq_connection_string + } +} + +// okay, so now we have the identity stuff in order, and we have secrets to use for that +// next, we need to set-up: +// - the wildcard tls (*.365zon.venus.fourlights.dev) +// - argocd for all relevant apps diff --git a/infra/tenants/365zon/provider.tf b/infra/tenants/365zon/provider.tf new file mode 100644 index 0000000..2864e32 --- /dev/null +++ b/infra/tenants/365zon/provider.tf @@ -0,0 +1,50 @@ +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 +} + +locals { + k8s_config_path = format("%s/%s", path.root, "../kubeconfig") + k8s_config_yaml = file(local.k8s_config_path) + k8s_config = yamldecode(local.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/tenants/365zon/variables.tf b/infra/tenants/365zon/variables.tf new file mode 100644 index 0000000..e6b03f3 --- /dev/null +++ b/infra/tenants/365zon/variables.tf @@ -0,0 +1,21 @@ +variable "domain" { type = string } +variable "jwt_profile_file" { type = string } +variable "org_id" { type = string } +variable "user_id" { type = string } +variable "minio_access_key" { + type = string + sensitive = true +} +variable "minio_secret_key" { + type = string + sensitive = true +} +variable "minio_service_uri" { type = string } +variable "mongodb_connection_string" { + type = string + sensitive = true +} +variable "rabbitmq_connection_string" { + type = string + sensitive = true +} diff --git a/infra/tenants/365zon/zitadel/main.tf b/infra/tenants/365zon/zitadel/main.tf new file mode 100644 index 0000000..395263e --- /dev/null +++ b/infra/tenants/365zon/zitadel/main.tf @@ -0,0 +1,153 @@ +locals { + tld = "fourlights.dev" + cluster_dns = "venus.${local.tld}" + domain = "zitadel.${local.cluster_dns}" + org_domain = "fourlights.${local.domain}" +} + +module "zitadel_project" { + source = "../../../modules/zitadel/project" + + org_id = var.org_id + name = var.name + owners = [var.user_id] +} + +// TODO: add action for setting roles as scopes + +module "zitadel_project_operator_roles" { + source = "../../../modules/zitadel/project/roles" + + wait_on = [module.zitadel_project.installed] + org_id = var.org_id + project_id = module.zitadel_project.project_id + group = "Operator" + roles = [ + "manage:profiles", "manage:contacts", "manage:addresses", "manage:enquiries", "manage:flowstates", + "manage:flowevents", "manage:files" + ] +} + +module "zitadel_project_configurator_roles" { + source = "../../../modules/zitadel/project/roles" + wait_on = [module.zitadel_project_operator_roles.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + group = "Configurator" + roles = [ + "manage:brands", "manage:flows" + ] +} + +module "zitadel_project_developer_roles" { + source = "../../../modules/zitadel/project/roles" + wait_on = [module.zitadel_project_configurator_roles.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + group = "Developer" + roles = [ + "manage:jobs", "manage:infrastructure" + ] +} + +module "zitadel_project_user_grant" { + source = "../../../modules/zitadel/project/user-grant" + wait_on = [module.zitadel_project_developer_roles.installed] + org_id = var.org_id + project_id = module.zitadel_project.project_id + user_id = var.user_id + roles = concat(module.zitadel_project_developer_roles.roles, module.zitadel_project_configurator_roles.roles, module.zitadel_project_operator_roles.roles) +} + +// 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" { + source = "../../../modules/zitadel/api-m2m-swagger" + wait_on = [module.zitadel_project_user_grant.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + + name = "Core" + zitadel_domain = local.domain + cluster_domain = local.cluster_dns + + namespace = var.namespace + project = var.name + + service_account = false + roles = [] +} + +module "zitadel_project_application_salesforce" { + source = "../../../modules/zitadel/api-m2m-swagger" + wait_on = [module.zitadel_project_application_core.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + + name = "Salesforce" + zitadel_domain = local.domain + cluster_domain = local.cluster_dns + + namespace = var.namespace + project = var.name + + roles = module.zitadel_project_operator_roles.roles +} + +module "zitadel_project_application_external" { + source = "../../../modules/zitadel/api-m2m-swagger" + wait_on = [module.zitadel_project_application_salesforce.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + + name = "External" + zitadel_domain = local.domain + cluster_domain = local.cluster_dns + + namespace = var.namespace + project = var.name + + roles = module.zitadel_project_operator_roles.roles +} + +module "zitadel_project_application_module_internal" { + source = "../../../modules/zitadel/api-m2m-swagger" + wait_on = [module.zitadel_project_application_external.installed] + + org_id = var.org_id + project_id = module.zitadel_project.project_id + + name = "Internal" + zitadel_domain = local.domain + cluster_domain = local.cluster_dns + + namespace = var.namespace + project = var.name + + roles = module.zitadel_project_operator_roles.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_id" { + value = var.org_id +} + +output "project_id" { + value = module.zitadel_project.project_id +} + +output "installed" { + value = true + depends_on = [module.zitadel_project_application_external.installed] +} diff --git a/infra/tenants/365zon/zitadel/provider.tf b/infra/tenants/365zon/zitadel/provider.tf new file mode 100644 index 0000000..9d0da41 --- /dev/null +++ b/infra/tenants/365zon/zitadel/provider.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} diff --git a/infra/tenants/365zon/zitadel/variables.tf b/infra/tenants/365zon/zitadel/variables.tf new file mode 100644 index 0000000..d41d6e2 --- /dev/null +++ b/infra/tenants/365zon/zitadel/variables.tf @@ -0,0 +1,15 @@ +variable "org_id" { + type = string +} + +variable "user_id" { + type = string +} + +variable "namespace" { + type = string +} + +variable "name" { + type = string +} diff --git a/infra/tenants/argocd/zitadel/groupsClaim.action.tftpl b/infra/tenants/argocd/zitadel/groupsClaim.action.tftpl new file mode 100644 index 0000000..5aa9094 --- /dev/null +++ b/infra/tenants/argocd/zitadel/groupsClaim.action.tftpl @@ -0,0 +1,28 @@ +/** + * sets the roles an additional claim in the token with roles as value an project as key + * + * The role claims of the token look like the following: + * + * // added by the code below + * "groups": ["{roleName}", "{roleName}", ...], + * + * Flow: Complement token, Triggers: Pre Userinfo creation, Pre access token creation + * + * @param ctx + * @param api + */ +function groupsClaim(ctx, api) { + if (ctx.v1.user.grants === undefined || ctx.v1.user.grants.count == 0) { + return; + } + + let grants = []; + ctx.v1.user.grants.grants.forEach((claim) => { + claim.roles.forEach((role) => { + grants.push(role); + }); + }); + + api.v1.claims.setClaim("groups", grants); + api.v1.claims.setClaim("scope", grants); +} diff --git a/infra/tenants/argocd/zitadel/main.tf b/infra/tenants/argocd/zitadel/main.tf new file mode 100644 index 0000000..516edc5 --- /dev/null +++ b/infra/tenants/argocd/zitadel/main.tf @@ -0,0 +1,113 @@ +locals { + argocd_uri = "https://${var.argocd_service_domain}" +} + +module "zitadel_project" { + source = "../../../modules/zitadel/project" + + org_id = var.org_id + name = var.name + owners = [var.user_id] +} + +module "zitadel_project_roles_user" { + source = "../../../modules/zitadel/project/roles" + + org_id = var.org_id + project_id = module.zitadel_project.project_id + group = "Users" + roles = ["user"] +} + +module "zitadel_project_roles_admin" { + source = "../../../modules/zitadel/project/roles" + + org_id = var.org_id + project_id = module.zitadel_project.project_id + group = "Admins" + roles = ["admin"] +} + +module "zitadel_application_argocd" { + source = "../../../modules/zitadel/project/application/web" + + name = "ArgoCD" + org_id = var.org_id + project_id = module.zitadel_project.project_id + + redirect_uris = ["${ local.argocd_uri}/api/dex/callback"] + post_logout_redirect_uris = [local.argocd_uri] + + auth_method_type = "OIDC_AUTH_METHOD_TYPE_BASIC" + id_token_role_assertion = true + id_token_userinfo_assertion = true +} + +resource "zitadel_action" "groups-claim" { + org_id = var.org_id + name = "groupsClaim" + script = templatefile("${path.module}/groupsClaim.action.tftpl", {}) + allowed_to_fail = true + timeout = "10s" +} + +resource "zitadel_trigger_actions" "groups-claim-pre-user-info" { + org_id = var.org_id + flow_type = "FLOW_TYPE_CUSTOMISE_TOKEN" + trigger_type = "TRIGGER_TYPE_PRE_USERINFO_CREATION" + action_ids = [zitadel_action.groups-claim.id] +} + +resource "zitadel_trigger_actions" "groups-claim-pre-access-token" { + org_id = var.org_id + flow_type = "FLOW_TYPE_CUSTOMISE_TOKEN" + trigger_type = "TRIGGER_TYPE_PRE_ACCESS_TOKEN_CREATION" + action_ids = [zitadel_action.groups-claim.id] +} + +module "zitadel_project_user_grant" { + source = "../../../modules/zitadel/project/user-grant" + + org_id = var.org_id + + project_id = module.zitadel_project.project_id + user_id = var.user_id + + roles = module.zitadel_project_roles_admin.roles +} + +output "client_id" { + value = module.zitadel_application_argocd.client_id +} + +output "client_secret" { + value = module.zitadel_application_argocd.client_secret +} + +output "scopes" { + value = ["openid", "profile", "email", "groups"] +} + +output "logoutSuffix" { + value = "oidc/v1/end_session" +} + +output "user_roles" { + value = module.zitadel_project_roles_user.roles +} + +output "admin_roles" { + value = module.zitadel_project_roles_admin.roles +} + +output "project_id" { + value = module.zitadel_project.project_id +} + +output "installed" { + value = true + depends_on = [ + module.zitadel_project_user_grant.installed, + zitadel_trigger_actions.groups-claim-pre-access-token, zitadel_trigger_actions.groups-claim-pre-user-info + ] +} diff --git a/infra/tenants/argocd/zitadel/provider.tf b/infra/tenants/argocd/zitadel/provider.tf new file mode 100644 index 0000000..653009c --- /dev/null +++ b/infra/tenants/argocd/zitadel/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/tenants/argocd/zitadel/variables.tf b/infra/tenants/argocd/zitadel/variables.tf new file mode 100644 index 0000000..6f6cdd8 --- /dev/null +++ b/infra/tenants/argocd/zitadel/variables.tf @@ -0,0 +1,17 @@ +variable "org_id" { + type = string +} + +variable "user_id" { + type = string +} + +variable "name" { + type = string + default = "argocd" +} + +variable "domain" { type = string } +variable "jwt_profile_file" { type = string } + +variable "argocd_service_domain" { type = string } diff --git a/infra/tenants/fourlights/zitadel/main.tf b/infra/tenants/fourlights/zitadel/main.tf new file mode 100644 index 0000000..7b0100c --- /dev/null +++ b/infra/tenants/fourlights/zitadel/main.tf @@ -0,0 +1,54 @@ +module "zitadel-tenant" { + source = "../../../modules/zitadel/tenant" + + name = "fourlights" +} + +module "zitadel-idp-google" { + source = "../../../modules/zitadel/identity-provider/google" + wait_on = module.zitadel-tenant.installed + + org_id = module.zitadel-tenant.org_id + client_id = "783390190667-quvko2l2kr9ksgeo3pn6pn6t8c1mai9n.apps.googleusercontent.com" + client_secret = "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" + + domain = var.domain + + options = { + scopes = ["openid", "profile", "email"] + is_auto_creation = true + is_auto_update = true + is_creation_allowed = true + is_linking_allowed = true + + auto_linking = "AUTO_LINKING_OPTION_USERNAME" + } +} + +module "zitadel-user" { + source = "../../../modules/zitadel/user" + wait_on = module.zitadel-tenant.installed + + org_id = module.zitadel-tenant.org_id + + first_name = "Thomas" + last_name = "Rijpstra" + user_name = "thomas@fourlights.nl" + email = "thomas@fourlights.nl" +} + +module "zitadel-org-owner" { + source = "../../../modules/zitadel/tenant/role-owner" + wait_on = module.zitadel-user.installed + + org_id = module.zitadel-tenant.org_id + user_id = module.zitadel-user.user_id +} + +output "org_id" { + value = module.zitadel-tenant.org_id +} + +output "user_id" { + value = module.zitadel-user.user_id +} diff --git a/infra/tenants/fourlights/zitadel/provider.tf b/infra/tenants/fourlights/zitadel/provider.tf new file mode 100644 index 0000000..2864e32 --- /dev/null +++ b/infra/tenants/fourlights/zitadel/provider.tf @@ -0,0 +1,50 @@ +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 +} + +locals { + k8s_config_path = format("%s/%s", path.root, "../kubeconfig") + k8s_config_yaml = file(local.k8s_config_path) + k8s_config = yamldecode(local.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/tenants/fourlights/zitadel/variables.tf b/infra/tenants/fourlights/zitadel/variables.tf new file mode 100644 index 0000000..7046469 --- /dev/null +++ b/infra/tenants/fourlights/zitadel/variables.tf @@ -0,0 +1,2 @@ +variable "domain" { type = string } +variable "jwt_profile_file" { type = string }