From f17e210f3eab7d158facc88a15c3bda1e87cad4e Mon Sep 17 00:00:00 2001 From: Thomas Rijpstra Date: Tue, 22 Apr 2025 17:43:02 +0200 Subject: [PATCH] feat(infra): add Zitadel and Zot modules - Add Zitadel identity management platform module with roles, tenants, and identity providers - Add Zot OCI registry module for container management --- infra/modules/zitadel/api-m2m-swagger/main.tf | 122 ++++++++++++++++++ .../zitadel/api-m2m-swagger/variables.tf | 44 +++++++ .../zitadel/identity-provider/google/main.tf | 82 ++++++++++++ .../identity-provider/google/variables.tf | 43 ++++++ .../google/verify-email.action.tftpl | 15 +++ infra/modules/zitadel/locals.tf | 3 + infra/modules/zitadel/main.tf | 90 +++++++++++++ .../zitadel/project/application/api/main.tf | 38 ++++++ .../project/application/api/variables.tf | 20 +++ .../project/application/user-agent/main.tf | 63 +++++++++ .../application/user-agent/variables.tf | 30 +++++ .../zitadel/project/application/web/main.tf | 61 +++++++++ .../project/application/web/variables.tf | 47 +++++++ infra/modules/zitadel/project/main.tf | 36 ++++++ infra/modules/zitadel/project/roles/main.tf | 34 +++++ .../zitadel/project/roles/variables.tf | 27 ++++ .../zitadel/project/user-grant/main.tf | 26 ++++ .../zitadel/project/user-grant/variables.tf | 26 ++++ infra/modules/zitadel/project/variables.tf | 21 +++ infra/modules/zitadel/provider.tf | 33 +++++ infra/modules/zitadel/service-account/main.tf | 38 ++++++ .../zitadel/service-account/variables.tf | 33 +++++ infra/modules/zitadel/tenant/main.tf | 23 ++++ .../modules/zitadel/tenant/role-owner/main.tf | 20 +++ .../zitadel/tenant/role-owner/variables.tf | 15 +++ infra/modules/zitadel/tenant/variables.tf | 11 ++ infra/modules/zitadel/user/main.tf | 31 +++++ infra/modules/zitadel/user/variables.tf | 26 ++++ infra/modules/zitadel/values.yaml.tftpl | 76 +++++++++++ infra/modules/zitadel/variables.tf | 61 +++++++++ infra/modules/zot/main.tf | 16 +++ infra/modules/zot/values.yaml.tftpl | 11 ++ infra/modules/zot/variables.tf | 1 + 33 files changed, 1223 insertions(+) create mode 100644 infra/modules/zitadel/api-m2m-swagger/main.tf create mode 100644 infra/modules/zitadel/api-m2m-swagger/variables.tf create mode 100644 infra/modules/zitadel/identity-provider/google/main.tf create mode 100644 infra/modules/zitadel/identity-provider/google/variables.tf create mode 100644 infra/modules/zitadel/identity-provider/google/verify-email.action.tftpl 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/variables.tf create mode 100644 infra/modules/zitadel/project/application/user-agent/main.tf create mode 100644 infra/modules/zitadel/project/application/user-agent/variables.tf create mode 100644 infra/modules/zitadel/project/application/web/main.tf create mode 100644 infra/modules/zitadel/project/application/web/variables.tf create mode 100644 infra/modules/zitadel/project/main.tf create mode 100644 infra/modules/zitadel/project/roles/main.tf create mode 100644 infra/modules/zitadel/project/roles/variables.tf create mode 100644 infra/modules/zitadel/project/user-grant/main.tf create mode 100644 infra/modules/zitadel/project/user-grant/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/variables.tf create mode 100644 infra/modules/zitadel/tenant/main.tf create mode 100644 infra/modules/zitadel/tenant/role-owner/main.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/user/main.tf create mode 100644 infra/modules/zitadel/user/variables.tf create mode 100644 infra/modules/zitadel/values.yaml.tftpl create mode 100644 infra/modules/zitadel/variables.tf create mode 100644 infra/modules/zot/main.tf create mode 100644 infra/modules/zot/values.yaml.tftpl create mode 100644 infra/modules/zot/variables.tf diff --git a/infra/modules/zitadel/api-m2m-swagger/main.tf b/infra/modules/zitadel/api-m2m-swagger/main.tf new file mode 100644 index 0000000..d16c8a3 --- /dev/null +++ b/infra/modules/zitadel/api-m2m-swagger/main.tf @@ -0,0 +1,122 @@ +terraform { + required_providers { + slugify = { + source = "public-cloud-wl/slugify" + version = "0.1.1" + } + } +} + +locals { + authority = "https://${var.zitadel_domain}" + slug_project = provider::slugify::slug(var.project) + slug_name = provider::slugify::slug(var.name) + + cluster = "${local.slug_project}.${var.cluster_domain}" + uri = "https://${local.slug_name}.${local.cluster}" +} + +module "zitadel_project_application_api" { + source = "../project/application/api" + wait_on = var.wait_on + + org_id = var.org_id + project_id = var.project_id + + name = "${var.name} API" +} + +module "zitadel_project_application_ua" { + source = "../project/application/user-agent" + wait_on = module.zitadel_project_application_api.installed + + org_id = var.org_id + project_id = var.project_id + + name = "${ var.name } (Swagger)" + + redirect_uris = ["${local.uri}/swagger/oauth2-redirect.html"] + post_logout_redirect_uris = [local.uri] +} + + +resource "kubernetes_secret" "user-agent" { + type = "Opaque" + depends_on = [module.zitadel_project_application_ua] + + metadata { + name = "${local.slug_name}-user-agent" + namespace = var.namespace + } + + data = { + "authority" = local.authority + "audience" = var.project_id + "client_id" = module.zitadel_project_application_ua.client_id + } +} + +resource "kubernetes_secret" "api" { + type = "Opaque" + depends_on = [module.zitadel_project_application_api] + + metadata { + name = "${local.slug_name}-api" + namespace = var.namespace + } + + data = { + "authority" = local.authority + "client_id" = module.zitadel_project_application_api.client_id + "client_secret" = module.zitadel_project_application_api.client_secret + } +} + +module "zitadel_service_account" { + count = var.service_account ? 1 : 0 + wait_on = module.zitadel_project_application_api.installed + source = "../service-account" + + org_id = var.org_id + + user_name = "${local.slug_name}@${ local.cluster }" + name = "${var.name} @ ${var.project}" + + with_secret = true + access_token_type = "ACCESS_TOKEN_TYPE_JWT" +} + +module "zitadel_project_user_grant" { + count = var.service_account ? 1 : 0 + source = "../project/user-grant" + + org_id = var.org_id + + project_id = var.project_id + user_id = module.zitadel_service_account[0].user_id + + roles = var.roles +} + +resource "kubernetes_secret" "service-account" { + count = var.service_account ? 1 : 0 + type = "Opaque" + depends_on = [module.zitadel_service_account] + + metadata { + name = "${local.slug_name}-service-account" + namespace = var.namespace + } + + data = { + "authority" = local.authority + "audience" = var.project_id + "client_id" = module.zitadel_service_account[count.index].client_id + "client_secret" = module.zitadel_service_account[count.index].client_secret + } +} + +output "installed" { + value = true + depends_on = [kubernetes_secret.service-account] +} diff --git a/infra/modules/zitadel/api-m2m-swagger/variables.tf b/infra/modules/zitadel/api-m2m-swagger/variables.tf new file mode 100644 index 0000000..6afa548 --- /dev/null +++ b/infra/modules/zitadel/api-m2m-swagger/variables.tf @@ -0,0 +1,44 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string +} + +variable "project_id" { + type = string +} + +variable "name" { + type = string +} + +variable "project" { + type = string +} + + +variable "roles" { + type = list(string) + description = "Roles to be granted" +} + +variable "namespace" { + type = string +} + +variable "service_account" { + type = bool + default = true +} + +variable "zitadel_domain" { + type = string +} + +variable "cluster_domain" { + type = string +} 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..2dd838a --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/main.tf @@ -0,0 +1,82 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + } + } +} + +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 = false + 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 +} + +#resource "zitadel_action" "verify-email-from-google-idp" { +# org_id = var.org_id +# name = "trustEmailVerification" +# script = templatefile("${path.module}/verify-email.action.tftpl", { +# trusted_idp = zitadel_org_idp_google.default.id, +# }) +# allowed_to_fail = false +# timeout = "10s" +#} + +#resource "zitadel_trigger_actions" "verify-email-from-google-idp" { +# org_id = var.org_id +# flow_type = "FLOW_TYPE_EXTERNAL_AUTHENTICATION" +# trigger_type = "TRIGGER_TYPE_PRE_CREATION" +# action_ids = [zitadel_action.verify-email-from-google-idp.id] +#} +# +#resource "zitadel_trigger_actions" "internal" { +# org_id = var.org_id +# flow_type = "FLOW_TYPE_INTERNAL_AUTHENTICATION" +# trigger_type = "TRIGGER_TYPE_PRE_CREATION" +# action_ids = [zitadel_action.verify-email-from-google-idp.id] +#} + +output "installed" { + value = true + depends_on = [ + zitadel_org_idp_google.default, zitadel_login_policy.default, + ] +} + +output "idp_id" { + value = zitadel_org_idp_google.default.id +} 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..dda30d5 --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/variables.tf @@ -0,0 +1,43 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +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 = true + is_creation_allowed = true + is_auto_creation = true + is_auto_update = true + auto_linking = "AUTO_LINKING_OPTION_USERNAME" + } +} + +variable "domain" { + type = string +} diff --git a/infra/modules/zitadel/identity-provider/google/verify-email.action.tftpl b/infra/modules/zitadel/identity-provider/google/verify-email.action.tftpl new file mode 100644 index 0000000..8af6be4 --- /dev/null +++ b/infra/modules/zitadel/identity-provider/google/verify-email.action.tftpl @@ -0,0 +1,15 @@ +/** + * Set first and lastname of a user on just in time provisioning for okta. + * Useful if you like to fill the first and lastname with the name stored on okta, so the user doesn't have to fill himself. + * Also set email to verified, so the user doesn't get a verification email + * + * Flow: External Authentication, Trigger: Post Authentication + * + * @param ctx + * @param api + */ +let logger = require("zitadel/log") + +function trustEmailVerification(ctx, api) { + api.setEmailVerified(true); +} 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..c874912 --- /dev/null +++ b/infra/modules/zitadel/main.tf @@ -0,0 +1,90 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.31.0" + } + } +} + +resource "kubernetes_namespace" "zitadel" { + count = var.enabled ? 1 : 0 + metadata { + name = var.namespace + } + + lifecycle { + ignore_changes = [metadata] + } +} + +resource "random_password" "zitadel_masterkey" { + length = 32 + special = true +} + +resource "kubernetes_secret" "zitadel" { + count = var.enabled ? 1 : 0 + metadata { + name = "zitadel" + namespace = kubernetes_namespace.zitadel[count.index].metadata[0].name + } + data = { + masterkey = random_password.zitadel_masterkey.result + } +} + +resource "helm_release" "zitadel" { + count = var.enabled ? 1 : 0 + depends_on = [var.wait_on, kubernetes_secret.zitadel] + name = "zitadel" + repository = "https://charts.zitadel.com" + chart = "zitadel" + namespace = kubernetes_namespace.zitadel[count.index].metadata[0].name + version = "8.12.0" + create_namespace = false + wait = true + wait_for_jobs = true + + values = [ + templatefile("${path.module}/values.yaml.tftpl", { + service_uri = local.service_uri, + database = var.database, + database_username = var.database_username, + database_password = var.database_password, + database_root_username = var.database_root_password != null ? var.database_root_username : null, + database_root_password = var.database_root_password + display_on_homepage = var.display_on_homepage + }) + ] +} + +data "kubernetes_secret" "zitadel_admin" { + depends_on = [helm_release.zitadel] + metadata { + name = "zitadel-admin-sa" + namespace = var.namespace + } +} + +resource "local_file" "zitadel_jwt_profile_file" { + content = data.kubernetes_secret.zitadel_admin.data["zitadel-admin-sa.json"] + filename = format("%s/%s", path.root, "zitadel-admin-sa.json") +} + +output "jwt_profile_file" { + value = local_file.zitadel_jwt_profile_file.filename +} + +output "installed" { + value = true + depends_on = [helm_release.zitadel, local_file.zitadel_jwt_profile_file] +} + +output "server" { + value = local.service_uri +} + +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..489bd93 --- /dev/null +++ b/infra/modules/zitadel/project/application/api/main.tf @@ -0,0 +1,38 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_application_api" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + + name = var.name + auth_method_type = "API_AUTH_METHOD_TYPE_BASIC" + // TODO: Change this to private key jwt in the future +} + +output "installed" { + value = true + depends_on = [zitadel_application_api.default] +} + +output "application_id" { + value = zitadel_application_api.default.id +} + +output "client_id" { + value = zitadel_application_api.default.client_id + sensitive = true +} + +output "client_secret" { + value = zitadel_application_api.default.client_secret + sensitive = true +} 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..f0c82e7 --- /dev/null +++ b/infra/modules/zitadel/project/application/api/variables.tf @@ -0,0 +1,20 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "name" { + type = string + description = "Application name" +} 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..e2d917c --- /dev/null +++ b/infra/modules/zitadel/project/application/user-agent/main.tf @@ -0,0 +1,63 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_application_oidc" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + + grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"] + name = var.name + project_id = var.project_id + + redirect_uris = var.redirect_uris + response_types = ["OIDC_RESPONSE_TYPE_CODE"] + + # // If selected, the requested roles of the authenticated user are added to the access token. + access_token_type = "OIDC_TOKEN_TYPE_JWT" + access_token_role_assertion = true + + # BEARER uses an Opaque token, which needs the introspection endpoint and `urn:zitadel:iam:org:project:id::aud` scope + #access_token_type = "OIDC_TOKEN_TYPE_BEARER" + + # // If you want to add additional Origins to your app which is not used as a redirect you can do that here. + #additional_origins = [] + + app_type = "OIDC_APP_TYPE_USER_AGENT" + auth_method_type = "OIDC_AUTH_METHOD_TYPE_NONE" + + # // Redirect URIs must begin with https:// unless dev_mode is true + #dev_mode = false + + # // If selected, the requested roles of the authenticated user are added to the ID token. + #id_token_role_assertion = false + # // Enables clients to retrieve profile, email, phone and address claims from ID token. + #id_token_userinfo_assertion = false + + post_logout_redirect_uris = var.post_logout_redirect_uris +} + +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/variables.tf b/infra/modules/zitadel/project/application/user-agent/variables.tf new file mode 100644 index 0000000..3220518 --- /dev/null +++ b/infra/modules/zitadel/project/application/user-agent/variables.tf @@ -0,0 +1,30 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "name" { + type = string + description = "Application name" +} + +variable "redirect_uris" { + type = list(string) +} + +variable "post_logout_redirect_uris" { + type = list(string) + default = [] +} diff --git a/infra/modules/zitadel/project/application/web/main.tf b/infra/modules/zitadel/project/application/web/main.tf new file mode 100644 index 0000000..2bc0429 --- /dev/null +++ b/infra/modules/zitadel/project/application/web/main.tf @@ -0,0 +1,61 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_application_oidc" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + + grant_types = ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"] + name = var.name + project_id = var.project_id + + redirect_uris = var.redirect_uris + response_types = ["OIDC_RESPONSE_TYPE_CODE"] + + # // If selected, the requested roles of the authenticated user are added to the access token. + #access_token_type = "OIDC_TOKEN_TYPE_JWT" + #access_token_role_assertion = true + + # BEARER uses an Opaque token, which needs the introspection endpoint and `urn:zitadel:iam:org:project:id::aud` scope + access_token_type = "OIDC_TOKEN_TYPE_BEARER" + + # // If you want to add additional Origins to your app which is not used as a redirect you can do that here. + #additional_origins = [] + + app_type = "OIDC_APP_TYPE_WEB" + auth_method_type = var.auth_method_type + + # // Redirect URIs must begin with https:// unless dev_mode is true + #dev_mode = false + + id_token_role_assertion = var.id_token_role_assertion + id_token_userinfo_assertion = var.id_token_userinfo_assertion + + post_logout_redirect_uris = var.post_logout_redirect_uris +} + +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/web/variables.tf b/infra/modules/zitadel/project/application/web/variables.tf new file mode 100644 index 0000000..8af3651 --- /dev/null +++ b/infra/modules/zitadel/project/application/web/variables.tf @@ -0,0 +1,47 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "name" { + type = string + description = "Application name" +} + +variable "redirect_uris" { + type = list(string) +} + +variable "post_logout_redirect_uris" { + type = list(string) + default = [] +} + +variable "auth_method_type" { + type = string + default = "OIDC_AUTH_METHOD_TYPE_NONE" +} + +variable "id_token_role_assertion" { + type = bool + default = false + description = "If selected, the requested roles of the authenticated user are added to the ID token." +} + +variable "id_token_userinfo_assertion" { + type = bool + default = false + description = "Enables clients to retrieve profile, email, phone and address claims from ID token." +} diff --git a/infra/modules/zitadel/project/main.tf b/infra/modules/zitadel/project/main.tf new file mode 100644 index 0000000..534a075 --- /dev/null +++ b/infra/modules/zitadel/project/main.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +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/main.tf b/infra/modules/zitadel/project/roles/main.tf new file mode 100644 index 0000000..8a94852 --- /dev/null +++ b/infra/modules/zitadel/project/roles/main.tf @@ -0,0 +1,34 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +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 + ]) +} + +output "roles" { + value = var.roles +} diff --git a/infra/modules/zitadel/project/roles/variables.tf b/infra/modules/zitadel/project/roles/variables.tf new file mode 100644 index 0000000..b18fb0f --- /dev/null +++ b/infra/modules/zitadel/project/roles/variables.tf @@ -0,0 +1,27 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +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/user-grant/main.tf b/infra/modules/zitadel/project/user-grant/main.tf new file mode 100644 index 0000000..4d1838a --- /dev/null +++ b/infra/modules/zitadel/project/user-grant/main.tf @@ -0,0 +1,26 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_user_grant" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + project_id = var.project_id + user_id = var.user_id + role_keys = var.roles +} + +output "installed" { + value = true + depends_on = [zitadel_user_grant.default] +} + +output "user_grant_id" { + value = zitadel_user_grant.default.id +} diff --git a/infra/modules/zitadel/project/user-grant/variables.tf b/infra/modules/zitadel/project/user-grant/variables.tf new file mode 100644 index 0000000..f4cd4af --- /dev/null +++ b/infra/modules/zitadel/project/user-grant/variables.tf @@ -0,0 +1,26 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "project_id" { + type = string + description = "Project Id" +} + +variable "user_id" { + type = string + description = "User Id" +} + +variable "roles" { + type = list(string) + description = "Roles to be granted" + default = [] +} diff --git a/infra/modules/zitadel/project/variables.tf b/infra/modules/zitadel/project/variables.tf new file mode 100644 index 0000000..7670a95 --- /dev/null +++ b/infra/modules/zitadel/project/variables.tf @@ -0,0 +1,21 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +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/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..c8109f5 --- /dev/null +++ b/infra/modules/zitadel/service-account/main.tf @@ -0,0 +1,38 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_machine_user" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + user_name = var.user_name + name = var.name + description = var.description + with_secret = var.with_secret + access_token_type = var.access_token_type +} + +output "installed" { + value = true + depends_on = [zitadel_machine_user.default] +} + +output "user_id" { + value = zitadel_machine_user.default.id +} + +output "client_id" { + value = zitadel_machine_user.default.client_id + sensitive = true +} + +output "client_secret" { + value = zitadel_machine_user.default.client_secret + sensitive = true +} diff --git a/infra/modules/zitadel/service-account/variables.tf b/infra/modules/zitadel/service-account/variables.tf new file mode 100644 index 0000000..70d1676 --- /dev/null +++ b/infra/modules/zitadel/service-account/variables.tf @@ -0,0 +1,33 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "user_name" { + type = string +} + +variable "name" { + type = string +} + +variable "description" { + type = string + default = null +} + +variable "with_secret" { + type = bool + default = false +} + +variable "access_token_type" { + type = string + default = "ACCESS_TOKEN_TYPE_JWT" +} diff --git a/infra/modules/zitadel/tenant/main.tf b/infra/modules/zitadel/tenant/main.tf new file mode 100644 index 0000000..6200890 --- /dev/null +++ b/infra/modules/zitadel/tenant/main.tf @@ -0,0 +1,23 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +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] +} 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..98e2c37 --- /dev/null +++ b/infra/modules/zitadel/tenant/role-owner/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_org_member" "default" { + depends_on = [var.wait_on] + org_id = var.org_id + user_id = var.user_id + roles = ["ORG_OWNER"] +} + +output "installed" { + value = true + depends_on = [zitadel_org_member.default] +} 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..0a3dc1a --- /dev/null +++ b/infra/modules/zitadel/tenant/role-owner/variables.tf @@ -0,0 +1,15 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string + description = "Zitadel Organization ID" +} + +variable "user_id" { + type = string + description = "Zitadel User ID" +} diff --git a/infra/modules/zitadel/tenant/variables.tf b/infra/modules/zitadel/tenant/variables.tf new file mode 100644 index 0000000..9faf38d --- /dev/null +++ b/infra/modules/zitadel/tenant/variables.tf @@ -0,0 +1,11 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "name" { + type = string + description = "Name of the tenant" + default = "fourlights" +} diff --git a/infra/modules/zitadel/user/main.tf b/infra/modules/zitadel/user/main.tf new file mode 100644 index 0000000..dc19396 --- /dev/null +++ b/infra/modules/zitadel/user/main.tf @@ -0,0 +1,31 @@ +terraform { + required_providers { + zitadel = { + source = "zitadel/zitadel" + version = "2.0.2" + } + } +} + +resource "zitadel_human_user" "default" { + depends_on = [var.wait_on] + + org_id = var.org_id + + email = var.email + user_name = var.user_name + first_name = var.first_name + last_name = var.last_name + + is_email_verified = true + initial_password = "Password1!" +} + +output "installed" { + value = true + depends_on = [zitadel_human_user.default] +} + +output "user_id" { + value = zitadel_human_user.default.id +} diff --git a/infra/modules/zitadel/user/variables.tf b/infra/modules/zitadel/user/variables.tf new file mode 100644 index 0000000..d449141 --- /dev/null +++ b/infra/modules/zitadel/user/variables.tf @@ -0,0 +1,26 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "org_id" { + type = string + description = "Organisation Id" +} + +variable "user_name" { + type = string +} + +variable "first_name" { + type = string +} + +variable "last_name" { + type = string +} + +variable "email" { + type = string +} diff --git a/infra/modules/zitadel/values.yaml.tftpl b/infra/modules/zitadel/values.yaml.tftpl new file mode 100644 index 0000000..47bc5ed --- /dev/null +++ b/infra/modules/zitadel/values.yaml.tftpl @@ -0,0 +1,76 @@ +zitadel: + masterkeySecretName: "zitadel" + configmapConfig: + Log: + Level: 'info' + LogStore: + Access: + Stdout: + Enabled: true + ExternalSecure: true + ExternalDomain: ${ service_uri } + ExternalPort: 443 + TLS: + Enabled: false + FirstInstance: + Org: + Machine: + Machine: + Username: zitadel-admin-sa + Name: Admin + MachineKey: + ExpirationDate: "2026-01-01T00:00:00Z" + Type: 1 + 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 } + Password: "${ database_password }" + SSL: + Mode: disable + %{ if database_root_username != null }Admin: + Username: ${ database_root_username } + Password: "${ database_root_password }" + SSL: + Mode: disable + %{ endif } + +readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 10 + +startupProbe: + periodSeconds: 5 + failureThreshold: 30 + +service: + annotations: + traefik.ingress.kubernetes.io/service.serversscheme: h2c + +ingress: + enabled: true + className: traefik + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/router.middlewares: default-preserve-host-headers@kubernetescrd + %{ if display_on_homepage }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..68e3db6 --- /dev/null +++ b/infra/modules/zitadel/variables.tf @@ -0,0 +1,61 @@ +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 +} + +variable "enabled" { + type = bool + default = true +} diff --git a/infra/modules/zot/main.tf b/infra/modules/zot/main.tf new file mode 100644 index 0000000..4ee84b3 --- /dev/null +++ b/infra/modules/zot/main.tf @@ -0,0 +1,16 @@ +resource "helm_release" "zot" { + name = "zot" + repository = "https://zotregistry.dev/helm-charts" + chart = "zot" + namespace = "registry" + create_namespace = true + + values = [ + templatefile("${path.module}/values.yaml.tftpl", { service_uri = var.service_uri }) + ] +} + +output "installed" { + value = true + depends_on = [helm_release.zot] +} diff --git a/infra/modules/zot/values.yaml.tftpl b/infra/modules/zot/values.yaml.tftpl new file mode 100644 index 0000000..9183d3c --- /dev/null +++ b/infra/modules/zot/values.yaml.tftpl @@ -0,0 +1,11 @@ +ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/router.middlewares: default-preserve-host-headers@kubernetescrd + traefik.ingress.kubernetes.io/proxy-body-size: "0" + hosts: + - host: ${ service_uri } + paths: + - path: / diff --git a/infra/modules/zot/variables.tf b/infra/modules/zot/variables.tf new file mode 100644 index 0000000..850394c --- /dev/null +++ b/infra/modules/zot/variables.tf @@ -0,0 +1 @@ +variable "service_uri" { type = string }