Compare commits

...

3 Commits

Author SHA1 Message Date
Thomas Rijpstra d348029f3b
WIP 2025-03-01 16:24:28 +01:00
Thomas Rijpstra df5c5b9a8e
WIP 2025-02-22 11:55:35 +01:00
Thomas Rijpstra 8d6cd81788
WIP 2025-02-22 11:55:14 +01:00
63 changed files with 2150 additions and 37 deletions

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="009bc178-658e-4c81-9bb8-8d7bf6b8cbc6" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../infra/clusters/app-365zon/.terraform/terraform.tfstate" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/clusters/app-365zon/.terraform/terraform.tfstate" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/clusters/app-365zon/main.tf" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/clusters/app-365zon/main.tf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/modules/mijn-365zon-nl/main.tf" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/modules/mijn-365zon-nl/main.tf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/modules/minio/main.tf" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/modules/minio/main.tf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/modules/minio/values.yaml.tftpl" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/modules/minio/values.yaml.tftpl" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/modules/minio/variables.tf" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/modules/minio/variables.tf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../infra/modules/mongodb/values.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../infra/modules/mongodb/values.yaml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 1
}</component>
<component name="ProjectId" id="2oqTXEtODybqnAKfjaqPi9uslRP" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "main",
"last_opened_file_path": "/home/lamelos/Projects/fourlights/devops",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-deb605915726-JavaScript-WS-243.22562.222" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="009bc178-658e-4c81-9bb8-8d7bf6b8cbc6" name="Changes" comment="" />
<created>1731596143702</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1731596143702</updated>
<workItem from="1731596144788" duration="1417000" />
<workItem from="1736261138378" duration="1228000" />
<workItem from="1736775177111" duration="7000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

View File

@ -59,6 +59,7 @@ resource "helm_release" "argocd" {
oauth_client_id = var.oauth_client_id,
oauth_client_secret = var.oauth_client_secret,
oauth_redirect_uri = var.oauth_redirect_uri
tls = var.tls
})
]
}

View File

@ -42,14 +42,27 @@ server:
hostname: ${ service_uri }
annotations:
kubernetes.io/ingress.class: traefik
%{ if tls }
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.middlewares: default-redirect-to-https@kubernetescrd,default-preserve-host-headers@kubernetescrd
%{ else }
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-preserve-host-headers@kubernetescrd
%{ endif }
%{ if tls }
extraTls:
- hosts:
- ${ service_uri }
secretName: argocd-tls
%{ endif }
config:
rbac: |
scopes: '[groups]'
"policy.csv": |
g, admin, role:admin
g, user, role:readonly
"policy.default": ''
%{ if oauth_uri != null }
dex.config: |
connectors:
@ -57,9 +70,9 @@ server:
id: oidc
name: OIDC
config:
issuer: ${ oauth_issuer }
clientID: ${ oauth_client_id }
clientSecret: ${ oauth_client_secret }
issuer: "${ oauth_issuer }"
clientID: "${ oauth_client_id }"
clientSecret: "${ oauth_client_secret }"
insecureSkipEmailVerified: true
insecureEnableGroups: true
scopes:
@ -67,6 +80,7 @@ server:
- email
- openid
- groups
logoutURL: "${ oauth_redirect_uri }"
claimMapping:
name: fullName # ArgoCD expects 'name', FusionAuth provides 'fullName'
preferred_username: email

View File

@ -64,3 +64,8 @@ variable "oauth_redirect_uri" {
description = "OAuth redirect URI"
default = null
}
variable "tls" {
type = bool
default = false
}

View File

@ -4,21 +4,6 @@ config:
- Github:
- abbr: GH
href: https://github.com/
services:
- My First Group:
- My First Service:
href: http://localhost/
description: Homepage is awesome
- My Second Group:
- My Second Service:
href: http://localhost/
description: Homepage is the best
- My Third Group:
- My Third Service:
href: http://localhost/
description: Homepage is 😎
widgets:
# show the kubernetes widget, with the cluster summary and individual nodes
- kubernetes:

View File

@ -58,6 +58,7 @@ resource "helm_release" "minio" {
admin = var.admin,
tls = var.mode == "distributed" ? false : var.tls
ingressClass = var.ingressClass
displayOnHomepage = var.displayOnHomepage
})
]
}
@ -66,3 +67,13 @@ output "installed" {
value = true
depends_on = [helm_release.minio]
}
output "access_key" {
value = random_password.minio_access_key.result
sensitive = true
}
output "secret_key" {
value = random_password.minio_secret_key.result
sensitive = true
}

View File

@ -22,6 +22,13 @@ ingress:
ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
%{ endif }
%{ if displayOnHomepage }
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Minio"
gethomepage.dev/description: "S3-Compatible cloud storage"
gethomepage.dev/group: "Tools"
gethomepage.dev/icon: "minio.png"
%{ endif }
apiIngress:
enabled: true

View File

@ -65,3 +65,7 @@ variable "storageSize" {
default = "6Gi"
}
variable "displayOnHomepage" {
type = bool
default = false
}

View File

@ -56,3 +56,8 @@ output "installed" {
value = true
depends_on = [helm_release.mongodb]
}
output "connection_string" {
value = format("mongodb://%s:%s@%s:%s/%s?", "root", random_password.mongodb_root_password.result, "mongodb-headless.mongodb.svc.cluster.local", "27017", "admin")
sensitive = true
}

View File

@ -1,4 +1,6 @@
resource "kubernetes_namespace" "postgresql" {
count = var.enabled ? 1 : 0
metadata {
name = var.namespace
}
@ -19,11 +21,12 @@ resource "random_password" "postgresql_root_password" {
}
resource "kubernetes_secret" "postgresql_auth" {
count = var.enabled ? 1 : 0
type = "generic"
depends_on = [var.wait_on]
metadata {
name = "postgresql-auth"
namespace = kubernetes_namespace.postgresql.metadata.0.name
namespace = kubernetes_namespace.postgresql[count.index].metadata.0.name
}
data = {
@ -33,11 +36,12 @@ resource "kubernetes_secret" "postgresql_auth" {
}
resource "helm_release" "postgresql" {
count = var.enabled ? 1 : 0
depends_on = [var.wait_on, kubernetes_secret.postgresql_auth]
name = "postgresql"
repository = "https://charts.bitnami.com/bitnami"
chart = "postgresql"
namespace = kubernetes_namespace.postgresql.metadata.0.name
namespace = kubernetes_namespace.postgresql[count.index].metadata.0.name
version = "16.0.5"
wait = true

View File

@ -17,6 +17,7 @@ resource "random_password" "tenant" {
}
resource "kubernetes_job" "create-tenant" {
count = var.enabled ? 1 : 0
depends_on = [var.wait_on]
metadata {
@ -108,5 +109,5 @@ output "username" {
}
output "job_name" {
value = kubernetes_job.create-tenant.metadata[0].name
value = var.enabled ? kubernetes_job.create-tenant[0].metadata[0].name : null
}

View File

@ -38,3 +38,8 @@ variable "k8s_config_yaml" {
description = "Content of k8s config yaml file"
type = string
}
variable "enabled" {
type = bool
default = true
}

View File

@ -16,3 +16,8 @@ variable "namespace" {
variable "username" {
type = string
}
variable "enabled" {
type = bool
default = true
}

View File

@ -41,3 +41,8 @@ output "installed" {
value = true
depends_on = [helm_release.rabbitmq]
}
output "connection_string" {
value = "rabbitmq://user:${random_password.password.result}@rabbitmq-headless.${var.namespace}.svc.cluster.local:5672/"
sensitive = true
}

View File

@ -0,0 +1,117 @@
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" = "urn:zitadel:iam:org:project:id:${ var.project_id }:aud"
"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" = "urn:zitadel:iam:org:project:id:${ var.project_id }:aud"
"client_id" = module.zitadel_service_account[count.index].client_id
"client_secret" = module.zitadel_service_account[count.index].client_secret
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
locals {
service_uri = join(".", [var.service_name, var.server_dns])
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

16
infra/modules/zot/main.tf Normal file
View File

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

View File

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

View File

@ -0,0 +1 @@
variable "service_uri" { type = string }

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,132 @@
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"
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"
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"
org_id = var.org_id
project_id = module.zitadel_project.project_id
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" {
source = "../../../modules/zitadel/api-m2m-swagger"
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"
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"
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"
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
}

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,27 @@
/**
* 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);
}

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 }

View File

@ -39,21 +39,41 @@ provider "registry.terraform.io/hashicorp/helm" {
}
provider "registry.terraform.io/hashicorp/kubernetes" {
version = "2.35.1"
version = "2.31.0"
constraints = "2.31.0"
hashes = [
"h1:Av0Wk8g2XjY2oap7nyWNHEgfCRfphdJvrkqJjEM2ZKM=",
"zh:12212ca5ae47823ce14bfafb909eeb6861faf1e2435fb2fc4a8b334b3544b5f5",
"zh:3f49b3d77182df06b225ab266667de69681c2e75d296867eb2cf06a8f8db768c",
"zh:40832494d19f8a2b3cd0c18b80294d0b23ef6b82f6f6897b5fe00248a9997460",
"zh:739a5ddea61a77925ee7006a29c8717377a2e9d0a79a0bbd98738d92eec12c0d",
"zh:a02b472021753627c5c39447a56d125a32214c29ff9108fc499f2dcdf4f1cc4f",
"zh:b78865b3867065aa266d6758c9601a2756741478f5735a838c20d633d65e085b",
"zh:d362e87464683f5632790e66920ea803adb54c2bc0cb24b6fd9a314d2b1efffd",
"zh:d98206fe88c2c9a52b8d2d0cb2c877c812a4a51d19f9d8428e63cbd5fd8a304d",
"zh:dfa320946b1ce3f3615c42b3447a28dc9f604c06d8b9a6fe289855ab2ade4d11",
"h1:wGHbATbv/pBVTST1MtEn0zyVhZbzZJD2NYq2EddASHY=",
"zh:0d16b861edb2c021b3e9d759b8911ce4cf6d531320e5dc9457e2ea64d8c54ecd",
"zh:1bad69ed535a5f32dec70561eb481c432273b81045d788eb8b37f2e4a322cc40",
"zh:43c58e3912fcd5bb346b5cb89f31061508a9be3ca7dd4cd8169c066203bcdfb3",
"zh:4778123da9206918a92dfa73cc711475d2b9a8275ff25c13a30513c523ac9660",
"zh:8bfa67d2db03b3bfae62beebe6fb961aee8d91b7a766efdfe4d337b33dfd23dd",
"zh:9020bb5729db59a520ade5e24984b737e65f8b81751fbbd343926f6d44d22176",
"zh:90431dbfc5b92498bfbce38f0b989978c84421a6c33245b97788a46b563fbd6e",
"zh:b71a061dda1244f6a52500e703a9524b851e7b11bbf238c17bbd282f27d51cb2",
"zh:d6232a7651b834b89591b94bf4446050119dcde740247e6083a4d55a2cefd28a",
"zh:d89fba43e699e28e2b5e92fff2f75fc03dbc8de0df9dacefe1a8836f8f430753",
"zh:ef85c0b744f5ba1b10dadc3c11e331ba4225c45bb733e024d7218c24b02b0512",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:fc1debd2e695b5222d2ccc8b24dab65baba4ee2418ecce944e64d42e79474cb5",
"zh:fdaf960443720a238c09e519aeb30faf74f027ac5d1e0a309c3b326888e031d7",
]
}
provider "registry.terraform.io/hashicorp/local" {
version = "2.5.2"
hashes = [
"h1:JlMZD6nYqJ8sSrFfEAH0Vk/SL8WLZRmFaMUF9PJK5wM=",
"zh:136299545178ce281c56f36965bf91c35407c11897f7082b3b983d86cb79b511",
"zh:3b4486858aa9cb8163378722b642c57c529b6c64bfbfc9461d940a84cd66ebea",
"zh:4855ee628ead847741aa4f4fc9bed50cfdbf197f2912775dd9fe7bc43fa077c0",
"zh:4b8cd2583d1edcac4011caafe8afb7a95e8110a607a1d5fb87d921178074a69b",
"zh:52084ddaff8c8cd3f9e7bcb7ce4dc1eab00602912c96da43c29b4762dc376038",
"zh:71562d330d3f92d79b2952ffdda0dad167e952e46200c767dd30c6af8d7c0ed3",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:805f81ade06ff68fa8b908d31892eaed5c180ae031c77ad35f82cb7a74b97cf4",
"zh:8b6b3ebeaaa8e38dd04e56996abe80db9be6f4c1df75ac3cccc77642899bd464",
"zh:ad07750576b99248037b897de71113cc19b1a8d0bc235eb99173cc83d0de3b1b",
"zh:b9f1c3bfadb74068f5c205292badb0661e17ac05eb23bfe8bd809691e4583d0e",
"zh:cc4cbcd67414fefb111c1bf7ab0bc4beb8c0b553d01719ad17de9a047adff4d1",
]
}
@ -75,3 +95,49 @@ provider "registry.terraform.io/hashicorp/random" {
"zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0",
]
}
provider "registry.terraform.io/public-cloud-wl/slugify" {
version = "0.1.1"
constraints = "0.1.1"
hashes = [
"h1:iOJEMYX1bLfUnKjSxluQkKijr5NgWSqb2lU9Ag2Q12w=",
"zh:13f77dedcc74256053ac51512372510d722116bf58e119fac203fe599d667720",
"zh:2223be634f684f76e265efdaafdf95a948ba9e44f09f8a89540bdb564eff17f1",
"zh:73e8b763c796d57186756cf0bab75323e2d92c873f1df8eccd8a7e336a2e3e81",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:9f83adcf17de03afb5c27111cb26c580dc5296dffd40fca4571e81ad0bad3bad",
"zh:a5414ade8cbae9aea10dee79e43da247ceecb7e4a54e76d39906ee60b7365a7d",
"zh:bd118ead731e129c92c0dfe3c9a2ebbd8fa25ba6508deaaaccb9ac3a7f70af2d",
"zh:c8ce48ad921956edcee0643cb6184442f3deb438e5605a53794dfd6e8f89a559",
"zh:d96da8a32ef2b807ed3bd943294c6e1d0bd5fc3a793deb762f74d0c54aeff335",
"zh:e30a218b474afe082e005faf51c323ed8747d46845bfacab4cd3adc0c51704ec",
"zh:e3cd265c38da6e65974ac1b9b6be608ba0534178f16f059ad13672de6846e32e",
"zh:f2ded7f8c771a603ad3e2df84986b5f175c38049b7a9ab4a3cd384abafb33dff",
"zh:f2ece1996cf686583afd19384041204a32e08389dc6f4f105501584e653e797d",
"zh:fa2418b74cea55d29dad24f5095aaf30d6253d63ebac3c0c47949b3de8087c88",
"zh:fdc8d3fbca6a19db203802e7a7337075e39b9ffb7a3887a7583e379be61bde17",
]
}
provider "registry.terraform.io/zitadel/zitadel" {
version = "2.0.2"
constraints = "2.0.2"
hashes = [
"h1:iymeaNBrZ4smcr7eHrxO4gbXQ6bx/enKyj3RQ6xZRYA=",
"zh:01e16af0dda9372696b5e1d43ec709aed79829b49ee69a4f9606a248752f672d",
"zh:048c4e726fb846cfe9ab0a0a1f86d3f8922442154b086e2bd8e389b32f69f2f0",
"zh:3a3f6bea621c9d480f1f288cffebace8620979b9260cfeae8f9af5d9a25ed490",
"zh:4d349e584786589bc2037cee691ff1678296f5351e6491aa34dcb08ecbe1dcb7",
"zh:80741c78179788be8d7e33e471e1311197cd4e1067803d438463d0a8ac871a60",
"zh:89178d30f5ec49551e6a6ebc5eb589ab6631012dcec0d03ea7130b1029890e51",
"zh:94cd3b1fe3d1d39bcb3b70208b044bde4c5ce5152e12b29f0fa0ff1085e12863",
"zh:97299c172ada852705f8ca9fa91eeee12c6259263baae3ca53cf41e3130b1731",
"zh:a33d53acc640dc93b81352ba633cf392bc8c7614a72d320d59d3dcdb22d73fc4",
"zh:a95c15960baf8157f79a6490361455767d48e4dd3ce2ef1d0051743f6152733b",
"zh:ae66ad95c7039e6ef844c39389c9077ce7dbb501b6af02afb26a223fd289dbcb",
"zh:b8a9cb3b53653c06d52607368c406112ee1abc6d66dc4aedaedddbb46a66ea8f",
"zh:d48693ecdc985bb4167af0c3164240c13c4ea48167d28f706e7893cbdb20540a",
"zh:f6db1ec30bfbcf4423ab2d29979b775423ba37008fd48a766b5a1cf87a131859",
"zh:fed4e95dc9aaf361c8ff57f819d31fa25152b9e6cb90b7202d8be9ab1446b081",
]
}

View File

@ -1,11 +1,54 @@
locals {
tld = "fourlights.dev"
cluster_dns = "venus.${local.tld}"
bridge_dns = "bridge.${local.cluster_dns}"
is_installed = true
node_count = 3
}
module "registry" {
source = "../../infra/modules/zot"
service_uri = "registry.${local.cluster_dns}"
}
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
@ -32,6 +75,8 @@ module "minio" {
admin = true
ingressClass = "traefik"
storageSize = "10Gi"
displayOnHomepage = true
}
module "mongodb" {
@ -57,3 +102,105 @@ module "rabbitmq" {
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-bootstrap" {
source = "../../infra/tenants/fourlights/zitadel"
domain = module.zitadel.server
jwt_profile_file = module.zitadel.jwt_profile_file
}
module "redis" {
source = "../../infra/modules/redis"
namespace = "redis"
k8s_config_yaml = local.k8s_config_yaml
}
module "tenant-365zon" {
source = "../../infra/tenants/365zon"
org_id = module.zitadel-bootstrap.org_id
user_id = module.zitadel-bootstrap.user_id
domain = module.zitadel.server
jwt_profile_file = module.zitadel.jwt_profile_file
minio_access_key = module.minio.minio_access_key
minio_secret_key = module.minio.minio_secret_key
minio_service_uri = module.minio.minio_api_uri
mongodb_connection_string = module.mongodb.connection_string
rabbitmq_connection_string = module.rabbitmq.connection_string
}
module "zitadel-argocd" {
source = "../../infra/tenants/argocd/zitadel"
org_id = module.zitadel-bootstrap.org_id
user_id = module.zitadel-bootstrap.user_id
domain = module.zitadel.server
jwt_profile_file = module.zitadel.jwt_profile_file
argocd_service_domain = "argocd.${ local.cluster_dns}"
}
module "argocd" {
source = "../../infra/modules/argocd"
wait_on = module.zitadel-argocd.installed
namespace = "argocd"
k8s_config_yaml = local.k8s_config_yaml
redis_db_start_index = 0
redis_password = module.redis.password
server_dns = local.cluster_dns
oauth_uri = module.zitadel.server
oauth_client_id = module.zitadel-argocd.client_id
oauth_client_secret = module.zitadel-argocd.client_secret
oauth_redirect_uri = "https://${module.zitadel.server}/${module.zitadel-argocd.logoutSuffix}"
oauth_issuer = "https://${module.zitadel.server}"
}
/*
argocd project
*/
output "argocd-root-password" {
value = module.argocd.admin_password
sensitive = true
}

View File

@ -0,0 +1 @@
{"type":"serviceaccount","keyId":"308542807332225577","key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAzjNVWZ/vYu2AyrYpDa36tOc7tdRfnTXxUUpUX4Yp8uVnZe2M\nlRJcMl+B4WXiVINhb2cJqKKHp+zzYiUJBCtLACXHWEPBsuNk+mmP+cot5q8lf9VY\nY7jKiUlKSFhuau0bkXTuxsest8Tau9kwt8z0f0N8Rh4Tsqg8uAMjxIfVg+NYOPrV\nVkyCZRowuquF7xyS+qm15HDIhao5y7QcuqSSr/V0DC46iVk/SiSAMSBCKwmgFL5e\naG/dcaT1euzxBxJX/ViAPiOL6rX1v3EjHm9HPhdQd7UuYiueqKPd8nnqBElTG8pV\n3JXcP00Rx8g89IGnYJFlaUrzOkatKBwonzVp0wIDAQABAoIBADr4tWUvd3AsipPu\n6ujNpBDOvOXwufOlNMHR5zV2klK0RxEAkh8kJBgH9oY29DbFaD1LE5kV+oQNIUIl\nY2G82/NL2qoknqYBoKR5QkLnDp+V4bygkGnctJf0zPjJybJs42CDN7LD8mKQOxpU\nMDmwtRAchdMr0OhccxkRVM5lJqxT+ixmwnD9hhhZQsfvrj4j9ALFTDYhdteJpC3t\n4KUMBmlV6PWwXw3gAstlZTXkHlp/BKrY407fOs9GYm5yAz0yoUgoSRawepQWX4Vp\n4qp8l5oI7veGIBE8H0Om9P8O7JoVWzH97iO3nHe0BYekInQgn60C0SshVaDVk8zc\nJ6UsmtkCgYEA7f5H2WI5yG4yO8IxRbx86S0xME+pjkw7GABLl3Ug8N7AKawh3BoU\n2ve5kPo6TA9l3MuUOqYRDAGTzvdhhgEC/V8oaYXKT/73CIcUgoF3cI642Ugo/UIo\n7LqaGy5qaoy2LqwQp+WVEefQ0qImz1c6sVXEBo1kPThxohbaCkYs/x8CgYEA3c1B\nSyX+xTGF8TY8EzyT+DguDmfOMZCb+fxw96J23m2svj7CGQvjM/ctQiGBMyOcdA4Z\nFAOtezuFNczJ6OIc3oiyBJ9/Anc9czZZNjH6ZDuCyIdzuLiWFwvZ+Yk1Gq5w9xJv\nvqIJkfuVsGvFbjY1qJcYTFo/nfPNW8hkIoJVIs0CgYEA0Czy0DXZcXbivd75lpRv\nds+vDSFBoVURA8eOV6d+7vMJh+onnA28XGUAjs4ynEGDyoTQ3hRRKP1gO1OsnLjZ\n0qOgB92dwCe4El+GEzoILg7JplY2dIGgpqH7Fvec4iK/YUflMdfic00hHn30EL2u\ne1wYIdsf6WUsEKqIgyArT+UCgYBMzhS8Fw4f7sQ5ANTQvHtoytt39Y47L54zdK0R\ns1qCL2xP/J9t2OX2SrOLYNrCgKRes7sPaS6bq8K3HJEWaaYhkShD3Y83pV7MFJfT\n4n6YUts44V67KoAevuXeORsAgENx6xpy4t15hasSCl+1iGQQWzH4zo+U/KWyELRM\nFuGwSQKBgFVWScjsWJnWyoZua/+F/r6FvPp8Bknm7N66Wl3Rb6h1q0CoQHnMx3HR\nPvJ2A6caaOt8QiBQDgu+23DaGBjRe8RxDkc4tVTDUWv4RCqub5gELkRW2DYtESdg\nvhMnXkYVubBZj8IRcYPyNddjV4SzlK67RIL/bMrYisAz+PHRYZFU\n-----END RSA PRIVATE KEY-----\n","expirationDate":"2026-01-01T00:00:00Z","userId":"308542807332160041"}