feat(tenants): add tenant-specific configurations

- Add tenant configuration for 365zon
- Configure Zitadel integration for ArgoCD
- Add Fourlights tenant Zitadel configuration
This commit is contained in:
Thomas Rijpstra 2025-04-22 17:43:14 +02:00
parent f17e210f3e
commit ed1eef9db0
Signed by: thomas
SSH Key Fingerprint: SHA256:sFF5HPNPaaW14qykTkmRi1FGGO0YMUPBenlKOqepUpw
13 changed files with 581 additions and 0 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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]
}

View File

@ -0,0 +1,8 @@
terraform {
required_providers {
zitadel = {
source = "zitadel/zitadel"
version = "2.0.2"
}
}
}

View File

@ -0,0 +1,15 @@
variable "org_id" {
type = string
}
variable "user_id" {
type = string
}
variable "namespace" {
type = string
}
variable "name" {
type = string
}

View File

@ -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);
}

View File

@ -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
]
}

View File

@ -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
}

View File

@ -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 }

View File

@ -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
}

View File

@ -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
}
}

View File

@ -0,0 +1,2 @@
variable "domain" { type = string }
variable "jwt_profile_file" { type = string }