From fc271aa626723728911e185955eadc15e1313673 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 11:39:55 +0530 Subject: [PATCH 01/19] feat(cnpg): client and secrets push to openbao --- modules/cnpg/certificates.tf | 130 +++++++++++++++++ modules/cnpg/secrets.tf | 266 ++++++++++++++++++++++++++--------- 2 files changed, 327 insertions(+), 69 deletions(-) diff --git a/modules/cnpg/certificates.tf b/modules/cnpg/certificates.tf index ec5261b..b9b8a5c 100644 --- a/modules/cnpg/certificates.tf +++ b/modules/cnpg/certificates.tf @@ -98,6 +98,49 @@ resource "kubernetes_manifest" "server_certificate_authority" { } } +resource "kubernetes_manifest" "push_server_certificate_authority" { + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-server-certificate-authority" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.server_certificate_authority.object.spec.secretName + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/certificates/${kubernetes_manifest.server_certificate_authority.object.spec.secretName}" + } + } + } + ] + } + } + + // Ensure the certificate is actually issued before trying to push it + depends_on = [kubernetes_manifest.server_certificate_authority] + + wait { + condition { + type = "Ready" + status = "True" + } + } +} + // Issuer to be used with PostgreSQL Server resource "kubernetes_manifest" "server_issuer" { manifest = { @@ -367,6 +410,49 @@ resource "kubernetes_manifest" "client_keycloak_certificate" { } } +resource "kubernetes_manifest" "push_client_keycloak_certificate" { + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-client-keycloak-certificate" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.client_keycloak_certificate.object.spec.secretName + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/certificates/${kubernetes_manifest.client_keycloak_certificate.object.spec.secretName}" + } + } + } + ] + } + } + + // Ensure the certificate is actually issued before trying to push it + depends_on = [kubernetes_manifest.client_keycloak_certificate] + + wait { + condition { + type = "Ready" + status = "True" + } + } +} + // Certificates for all clients resource "kubernetes_manifest" "client_certificates" { count = length(var.clients) @@ -424,6 +510,50 @@ resource "kubernetes_manifest" "client_certificates" { } } +resource "kubernetes_manifest" "push_client_certificates" { + count = length(var.clients) + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-${var.clients[count.index].user}-client-certificate" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.client_certificates[count.index].object.spec.secretName + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/certificates/${kubernetes_manifest.client_certificates[count.index].object.spec.secretName}" + } + } + } + ] + } + } + + // Ensure the certificate is actually issued before trying to push it + depends_on = [kubernetes_manifest.client_certificates] + + wait { + condition { + type = "Ready" + status = "True" + } + } +} + // Internal Certificate for PGAdmin resource "kubernetes_manifest" "pgadmin_internal_certificate" { manifest = { diff --git a/modules/cnpg/secrets.tf b/modules/cnpg/secrets.tf index b1fdded..2c4501f 100644 --- a/modules/cnpg/secrets.tf +++ b/modules/cnpg/secrets.tf @@ -38,95 +38,223 @@ resource "kubernetes_manifest" "garage_configuration_sync" { } } -// Database credentials configuration for Keycloak -resource "random_password" "keycloak_password" { - length = 20 - lower = true - numeric = true - special = false +resource "kubernetes_manifest" "password_generator" { + manifest = { + apiVersion = "generators.external-secrets.io/v1alpha1" + kind = "Password" + metadata = { + name = "password-generator" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + length = 20 + digits = 5 + symbols = 0 + noUpper = true + } + } } -resource "kubernetes_secret" "keycloak_database_credentials" { - metadata { - name = "credentials-keycloak" - namespace = kubernetes_namespace.namespace.metadata[0].name - - labels = { - app = var.app_name - component = "secret" +// Database credentials configuration for Keycloak +resource "kubernetes_manifest" "keycloak_database_credentials_sync" { + manifest = { + apiVersion = "external-secrets.io/v1" + kind = "ExternalSecret" + metadata = { + name = "credentials-keycloak" + namespace = kubernetes_namespace.namespace.metadata[0].name } - - annotations = { - "reflector.v1.k8s.emberstack.com/reflection-allowed" = "true" - "reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces" = "keycloak" + spec = { + refreshInterval = "0" + target = { + name = "credentials-keycloak" + template = { + type = "kubernetes.io/basic-auth" + data = { + username = "keycloak" + password = "{{ .password }}" + } + } + } + dataFrom = [{ + sourceRef = { + generatorRef = { + apiVersion = "generators.external-secrets.io/v1alpha1" + kind = "Password" + name = kubernetes_manifest.password_generator.object.metadata.name + } + } + }] } } +} - data = { - "username" = "keycloak" - "password" = random_password.keycloak_password.result +resource "kubernetes_manifest" "push_keycloak_database_credentials" { + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-${kubernetes_manifest.keycloak_database_credentials_sync.object.spec.target.name}" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.keycloak_database_credentials_sync.object.spec.target.name + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/credentials/${kubernetes_manifest.keycloak_database_credentials_sync.object.spec.target.name}" + } + } + } + ] + } } - - type = "kubernetes.io/basic-auth" + depends_on = [kubernetes_manifest.keycloak_database_credentials_sync] } // Database credentials configuration for all clients -resource "random_password" "client_password" { +resource "kubernetes_manifest" "client_database_credentials_sync" { count = length(var.clients) - length = 20 - lower = true - numeric = true - special = false -} - -resource "kubernetes_secret" "client_database_credentials" { - count = length(var.clients) - metadata { - name = "credentials-${var.clients[count.index].user}" - namespace = kubernetes_namespace.namespace.metadata[0].name - - labels = { - app = var.app_name - component = "secret" + manifest = { + apiVersion = "external-secrets.io/v1" + kind = "ExternalSecret" + metadata = { + name = "credentials-${var.clients[count.index].user}" + namespace = kubernetes_namespace.namespace.metadata[0].name } - - annotations = { - "reflector.v1.k8s.emberstack.com/reflection-allowed" = "true" - "reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces" = var.clients[count.index].namespace + spec = { + refreshInterval = "0" + target = { + name = "credentials-${var.clients[count.index].user}" + template = { + type = "kubernetes.io/basic-auth" + data = { + username = var.clients[count.index].user + password = "{{ .password }}" + } + } + } + dataFrom = [{ + sourceRef = { + generatorRef = { + apiVersion = "generators.external-secrets.io/v1alpha1" + kind = "Password" + name = kubernetes_manifest.password_generator.object.metadata.name + } + } + }] } } - - data = { - "username" = var.clients[count.index].user - "password" = random_password.client_password[count.index].result - } - - type = "kubernetes.io/basic-auth" } - -resource "random_password" "pgadmin_password" { - length = 20 - lower = true - numeric = true - special = false +resource "kubernetes_manifest" "push_client_database_credentials" { + count = length(var.clients) + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-${kubernetes_manifest.client_database_credentials_sync[count.index].object.spec.target.name}" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.client_database_credentials_sync[count.index].object.spec.target.name + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/credentials/${kubernetes_manifest.client_database_credentials_sync[count.index].object.spec.target.name}" + } + } + } + ] + } + } + depends_on = [kubernetes_manifest.client_database_credentials_sync] } -resource "kubernetes_secret" "pgadmin_credentials" { - metadata { - name = "pgadmin-credentials" - namespace = kubernetes_namespace.namespace.metadata[0].name - - labels = { - app = var.app_name - component = "secret" +resource "kubernetes_manifest" "pgadmin_credentials_sync" { + manifest = { + apiVersion = "external-secrets.io/v1" + kind = "ExternalSecret" + metadata = { + name = "pgadmin-credentials" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "0" + target = { + name = "pgadmin-credentials" + template = { + data = { + "PGADMIN_DEFAULT_EMAIL" = "noreply@${var.organization_name}.com" + "PGADMIN_DEFAULT_PASSWORD" = "{{ .password }}" + } + } + } + dataFrom = [{ + sourceRef = { + generatorRef = { + apiVersion = "generators.external-secrets.io/v1alpha1" + kind = "Password" + name = kubernetes_manifest.password_generator.object.metadata.name + } + } + }] } } +} - data = { - "PGADMIN_DEFAULT_EMAIL" = "noreply@${var.organization_name}.com" - "PGADMIN_DEFAULT_PASSWORD" = random_password.pgadmin_password.result +resource "kubernetes_manifest" "push_pgadmin_credentials" { + manifest = { + apiVersion = "external-secrets.io/v1alpha1" + kind = "PushSecret" + metadata = { + name = "push-${kubernetes_manifest.pgadmin_credentials_sync.object.spec.target.name}" + namespace = kubernetes_namespace.namespace.metadata[0].name + } + spec = { + refreshInterval = "1h" + deletionPolicy = "None" + secretStoreRefs = [{ + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + }] + selector = { + secret = { + name = kubernetes_manifest.pgadmin_credentials_sync.object.spec.target.name + } + } + data = [ + { + match = { + remoteRef = { + remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/credentials/ui/${kubernetes_manifest.pgadmin_credentials_sync.object.spec.target.name}" + } + } + } + ] + } } - - type = "Opaque" + depends_on = [kubernetes_manifest.pgadmin_credentials_sync] } From f93cdca4c85d67131159b6cfee94f6d7a65bdad5 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 11:40:13 +0530 Subject: [PATCH 02/19] feat(cnpg): references update --- infrastructure/main.tf | 162 ++++++++++++++++++++-------------------- modules/cnpg/cluster.tf | 9 ++- modules/cnpg/locals.tf | 8 +- modules/cnpg/pgadmin.tf | 10 +-- 4 files changed, 98 insertions(+), 91 deletions(-) diff --git a/infrastructure/main.tf b/infrastructure/main.tf index e6c8ac8..874a0b5 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -90,7 +90,7 @@ module "garage" { # Cloudnative PG Deployment for PostgreSQL Database Solution module "cnpg" { - source = "git::https://github.com/necro-cloud/modules//modules/cnpg?ref=main" + source = "git::https://github.com/necro-cloud/modules//modules/cnpg?ref=task/117/cnpg-external-secrets" // Cluster Secret Store Details cluster_secret_store_name = module.openbao.cluster_secret_store_name @@ -131,83 +131,83 @@ module "cnpg" { } # FerretDB Deployment for MongoDB Database Solution -module "ferretdb" { - source = "git::https://github.com/necro-cloud/modules//modules/ferretdb?ref=main" - - // Cluster Secret Store Details - cluster_secret_store_name = module.openbao.cluster_secret_store_name - - // Garage Cluster Details for configuration of PITR Backups - garage_certificate_authority = module.garage.garage_internal_certificate_secret - garage_namespace = module.garage.garage_namespace - garage_configuration = "walbackups" - backup_bucket_name = "ferret" - - // Observability details - observability_namespace = module.observability.observability_namespace - - // Required client details to allow access and generate credentials and certificates for - clients = [ - { - namespace = "cloud" - user = "cloud" - } - ] - - // Certificate details for internal and ingress certificates - cluster_issuer_name = module.cluster-issuer.cluster-issuer-name - cloudflare_token = var.cloudflare_token - cloudflare_email = var.cloudflare_email - domain = var.domain - - // Whitelisting Kubernetes API Endpoints in the Network Policy - kubernetes_api_ip = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].address[*].ip)) - kubernetes_api_protocol = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].protocol)) - kubernetes_api_port = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].port)) - - // Dependency on Garage Deployment - depends_on = [module.garage, module.observability, module.openbao] -} - -# Keycloak Cluster Deployment for Identity Solution -module "keycloak" { - source = "git::https://github.com/necro-cloud/modules//modules/keycloak?ref=main" - - // PostgreSQL Database Details for database details - cluster_issuer_name = module.cluster-issuer.cluster-issuer-name - postgres_namespace = module.cnpg.namespace - cluster_name = module.cnpg.cluster_name - database_server_certificate_authority_name = module.cnpg.server-certificate-authority - database_client_certificate_name = "postgresql-keycloak-client-certificate" - database_credentials = "credentials-keycloak" - - // Certificate details for ingress - cloudflare_token = var.cloudflare_token - cloudflare_email = var.cloudflare_email - domain = var.domain - - // Observability details - observability_namespace = module.observability.observability_namespace - - // Realm Settings for auto configuration of required clients - realm_settings = local.keycloak_realm_settings - - // Dependency on CNPG PostgreSQL Deployment - depends_on = [module.cnpg, module.observability] -} - -# Valkey Deployment for In Memory Storage Solution -module "valkey" { - source = "git::https://github.com/necro-cloud/modules//modules/valkey?ref=main" - - // Certificate details for TLS Authentication - cluster_issuer_name = module.cluster-issuer.cluster-issuer-name - - // Granting required namespaces access to the Valkey - access_namespaces = "cloud" - - // Observability details - observability_namespace = module.observability.observability_namespace - - depends_on = [module.observability] -} +# module "ferretdb" { +# source = "git::https://github.com/necro-cloud/modules//modules/ferretdb?ref=main" + +# // Cluster Secret Store Details +# cluster_secret_store_name = module.openbao.cluster_secret_store_name + +# // Garage Cluster Details for configuration of PITR Backups +# garage_certificate_authority = module.garage.garage_internal_certificate_secret +# garage_namespace = module.garage.garage_namespace +# garage_configuration = "walbackups" +# backup_bucket_name = "ferret" + +# // Observability details +# observability_namespace = module.observability.observability_namespace + +# // Required client details to allow access and generate credentials and certificates for +# clients = [ +# { +# namespace = "cloud" +# user = "cloud" +# } +# ] + +# // Certificate details for internal and ingress certificates +# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name +# cloudflare_token = var.cloudflare_token +# cloudflare_email = var.cloudflare_email +# domain = var.domain + +# // Whitelisting Kubernetes API Endpoints in the Network Policy +# kubernetes_api_ip = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].address[*].ip)) +# kubernetes_api_protocol = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].protocol)) +# kubernetes_api_port = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].port)) + +# // Dependency on Garage Deployment +# depends_on = [module.garage, module.observability, module.openbao] +# } + +# # Keycloak Cluster Deployment for Identity Solution +# module "keycloak" { +# source = "git::https://github.com/necro-cloud/modules//modules/keycloak?ref=main" + +# // PostgreSQL Database Details for database details +# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name +# postgres_namespace = module.cnpg.namespace +# cluster_name = module.cnpg.cluster_name +# database_server_certificate_authority_name = module.cnpg.server-certificate-authority +# database_client_certificate_name = "postgresql-keycloak-client-certificate" +# database_credentials = "credentials-keycloak" + +# // Certificate details for ingress +# cloudflare_token = var.cloudflare_token +# cloudflare_email = var.cloudflare_email +# domain = var.domain + +# // Observability details +# observability_namespace = module.observability.observability_namespace + +# // Realm Settings for auto configuration of required clients +# realm_settings = local.keycloak_realm_settings + +# // Dependency on CNPG PostgreSQL Deployment +# depends_on = [module.cnpg, module.observability] +# } + +# # Valkey Deployment for In Memory Storage Solution +# module "valkey" { +# source = "git::https://github.com/necro-cloud/modules//modules/valkey?ref=main" + +# // Certificate details for TLS Authentication +# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name + +# // Granting required namespaces access to the Valkey +# access_namespaces = "cloud" + +# // Observability details +# observability_namespace = module.observability.observability_namespace + +# depends_on = [module.observability] +# } diff --git a/modules/cnpg/cluster.tf b/modules/cnpg/cluster.tf index e62dade..77192cb 100644 --- a/modules/cnpg/cluster.tf +++ b/modules/cnpg/cluster.tf @@ -56,7 +56,7 @@ resource "kubernetes_manifest" "cluster" { "login" = true "name" = "keycloak" "passwordSecret" = { - "name" = kubernetes_secret.keycloak_database_credentials.metadata[0].name + "name" = kubernetes_manifest.keycloak_database_credentials_sync.object.spec.target.name } "replication" = false "superuser" = false @@ -123,4 +123,11 @@ resource "kubernetes_manifest" "cluster" { update = "10m" delete = "10m" } + + depends_on = [ + kubernetes_manifest.barman_object_store, + kubernetes_manifest.keycloak_database_credentials_sync, + kubernetes_manifest.client_database_credentials_sync, + kubernetes_manifest.garage_configuration_sync + ] } diff --git a/modules/cnpg/locals.tf b/modules/cnpg/locals.tf index 184ffcb..1752e46 100644 --- a/modules/cnpg/locals.tf +++ b/modules/cnpg/locals.tf @@ -1,17 +1,17 @@ locals { access_namespaces = [for config in var.clients : config.namespace] - managed_roles = [for secret in kubernetes_secret.client_database_credentials : { + managed_roles = [for client in var.clients : { "bypassrls" = false - "comment" = "${secret.data.username} user for postgresql" + "comment" = "${client.user} user for postgresql" "connectionLimit" = -1 "createdb" = true "createrole" = true "ensure" = "present" "inherit" = true "login" = true - "name" = secret.data.username + "name" = client.user "passwordSecret" = { - "name" = secret.metadata[0].name + "name" = kubernetes_manifest.client_database_credentials_sync[client.user].object.spec.target.name } "replication" = false "superuser" = false diff --git a/modules/cnpg/pgadmin.tf b/modules/cnpg/pgadmin.tf index d14006d..166beb7 100644 --- a/modules/cnpg/pgadmin.tf +++ b/modules/cnpg/pgadmin.tf @@ -30,7 +30,7 @@ resource "kubernetes_deployment" "pgadmin" { env_from { secret_ref { - name = kubernetes_secret.pgadmin_credentials.metadata[0].name + name = kubernetes_manifest.pgadmin_credentials_sync.object.spec.target.name } } @@ -175,7 +175,7 @@ resource "kubernetes_deployment" "pgadmin" { projected { sources { secret { - name = kubernetes_secret.keycloak_database_credentials.metadata[0].name + name = kubernetes_manifest.keycloak_database_credentials_sync.object.spec.target.name items { key = "password" path = "keycloak/password" @@ -183,12 +183,12 @@ resource "kubernetes_deployment" "pgadmin" { } dynamic "secret" { - for_each = kubernetes_secret.client_database_credentials + for_each = kubernetes_manifest.client_database_credentials_sync content { - name = secret.value.metadata[0].name + name = secret.value.object.spec.target.name items { key = "password" - path = "${split("-", secret.value.metadata[0].name)[1]}/password" + path = "${split("-", secret.value.object.spec.target.name)[1]}/password" } } } From b131180bb41696002dba2160125eee3eb3d649e9 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 11:47:35 +0530 Subject: [PATCH 03/19] fix(cnpg): for looping fix --- modules/cnpg/locals.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cnpg/locals.tf b/modules/cnpg/locals.tf index 1752e46..0773f17 100644 --- a/modules/cnpg/locals.tf +++ b/modules/cnpg/locals.tf @@ -1,6 +1,6 @@ locals { access_namespaces = [for config in var.clients : config.namespace] - managed_roles = [for client in var.clients : { + managed_roles = [for index, client in var.clients : { "bypassrls" = false "comment" = "${client.user} user for postgresql" "connectionLimit" = -1 @@ -11,7 +11,7 @@ locals { "login" = true "name" = client.user "passwordSecret" = { - "name" = kubernetes_manifest.client_database_credentials_sync[client.user].object.spec.target.name + "name" = kubernetes_manifest.client_database_credentials_sync[index].object.spec.target.name } "replication" = false "superuser" = false From c04c9ee7860739c377ee93750c6b587cffd4cda7 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:21:58 +0530 Subject: [PATCH 04/19] feat(keycloak): certificates and secrets update to pull from openbao --- infrastructure/main.tf | 163 ++++----- modules/keycloak/certificates.tf | 109 ++++-- modules/keycloak/deployment.tf | 564 ++++++++++++++++--------------- modules/keycloak/secrets.tf | 42 +++ modules/keycloak/variables.tf | 7 + 5 files changed, 489 insertions(+), 396 deletions(-) diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 874a0b5..2981c6b 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -131,83 +131,86 @@ module "cnpg" { } # FerretDB Deployment for MongoDB Database Solution -# module "ferretdb" { -# source = "git::https://github.com/necro-cloud/modules//modules/ferretdb?ref=main" - -# // Cluster Secret Store Details -# cluster_secret_store_name = module.openbao.cluster_secret_store_name - -# // Garage Cluster Details for configuration of PITR Backups -# garage_certificate_authority = module.garage.garage_internal_certificate_secret -# garage_namespace = module.garage.garage_namespace -# garage_configuration = "walbackups" -# backup_bucket_name = "ferret" - -# // Observability details -# observability_namespace = module.observability.observability_namespace - -# // Required client details to allow access and generate credentials and certificates for -# clients = [ -# { -# namespace = "cloud" -# user = "cloud" -# } -# ] - -# // Certificate details for internal and ingress certificates -# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name -# cloudflare_token = var.cloudflare_token -# cloudflare_email = var.cloudflare_email -# domain = var.domain - -# // Whitelisting Kubernetes API Endpoints in the Network Policy -# kubernetes_api_ip = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].address[*].ip)) -# kubernetes_api_protocol = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].protocol)) -# kubernetes_api_port = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].port)) - -# // Dependency on Garage Deployment -# depends_on = [module.garage, module.observability, module.openbao] -# } - -# # Keycloak Cluster Deployment for Identity Solution -# module "keycloak" { -# source = "git::https://github.com/necro-cloud/modules//modules/keycloak?ref=main" - -# // PostgreSQL Database Details for database details -# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name -# postgres_namespace = module.cnpg.namespace -# cluster_name = module.cnpg.cluster_name -# database_server_certificate_authority_name = module.cnpg.server-certificate-authority -# database_client_certificate_name = "postgresql-keycloak-client-certificate" -# database_credentials = "credentials-keycloak" - -# // Certificate details for ingress -# cloudflare_token = var.cloudflare_token -# cloudflare_email = var.cloudflare_email -# domain = var.domain - -# // Observability details -# observability_namespace = module.observability.observability_namespace - -# // Realm Settings for auto configuration of required clients -# realm_settings = local.keycloak_realm_settings - -# // Dependency on CNPG PostgreSQL Deployment -# depends_on = [module.cnpg, module.observability] -# } - -# # Valkey Deployment for In Memory Storage Solution -# module "valkey" { -# source = "git::https://github.com/necro-cloud/modules//modules/valkey?ref=main" - -# // Certificate details for TLS Authentication -# cluster_issuer_name = module.cluster-issuer.cluster-issuer-name - -# // Granting required namespaces access to the Valkey -# access_namespaces = "cloud" - -# // Observability details -# observability_namespace = module.observability.observability_namespace - -# depends_on = [module.observability] -# } +module "ferretdb" { + source = "git::https://github.com/necro-cloud/modules//modules/ferretdb?ref=main" + + // Cluster Secret Store Details + cluster_secret_store_name = module.openbao.cluster_secret_store_name + + // Garage Cluster Details for configuration of PITR Backups + garage_certificate_authority = module.garage.garage_internal_certificate_secret + garage_namespace = module.garage.garage_namespace + garage_configuration = "walbackups" + backup_bucket_name = "ferret" + + // Observability details + observability_namespace = module.observability.observability_namespace + + // Required client details to allow access and generate credentials and certificates for + clients = [ + { + namespace = "cloud" + user = "cloud" + } + ] + + // Certificate details for internal and ingress certificates + cluster_issuer_name = module.cluster-issuer.cluster-issuer-name + cloudflare_token = var.cloudflare_token + cloudflare_email = var.cloudflare_email + domain = var.domain + + // Whitelisting Kubernetes API Endpoints in the Network Policy + kubernetes_api_ip = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].address[*].ip)) + kubernetes_api_protocol = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].protocol)) + kubernetes_api_port = one(flatten(data.kubernetes_endpoints_v1.kubernetes_api_endpoint.subset[*].port[*].port)) + + // Dependency on Garage Deployment + depends_on = [module.garage, module.observability, module.openbao] +} + +# Keycloak Cluster Deployment for Identity Solution +module "keycloak" { + source = "git::https://github.com/necro-cloud/modules//modules/keycloak?ref=task/117/cnpg-external-secrets" + + // Cluster Secret Store Details + cluster_secret_store_name = module.openbao.cluster_secret_store_name + + // PostgreSQL Database Details for database details + cluster_issuer_name = module.cluster-issuer.cluster-issuer-name + postgres_namespace = module.cnpg.namespace + cluster_name = module.cnpg.cluster_name + database_server_certificate_authority_name = module.cnpg.server-certificate-authority + database_client_certificate_name = "postgresql-keycloak-client-certificate" + database_credentials = "credentials-keycloak" + + // Certificate details for ingress + cloudflare_token = var.cloudflare_token + cloudflare_email = var.cloudflare_email + domain = var.domain + + // Observability details + observability_namespace = module.observability.observability_namespace + + // Realm Settings for auto configuration of required clients + realm_settings = local.keycloak_realm_settings + + // Dependency on CNPG PostgreSQL Deployment + depends_on = [module.cnpg, module.observability] +} + +# Valkey Deployment for In Memory Storage Solution +module "valkey" { + source = "git::https://github.com/necro-cloud/modules//modules/valkey?ref=main" + + // Certificate details for TLS Authentication + cluster_issuer_name = module.cluster-issuer.cluster-issuer-name + + // Granting required namespaces access to the Valkey + access_namespaces = "cloud" + + // Observability details + observability_namespace = module.observability.observability_namespace + + depends_on = [module.observability] +} diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index 14aa423..c699c6d 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -1,53 +1,88 @@ // Database Certificate Authority to be used for database connections -resource "kubernetes_secret" "database_server_certificate_authority" { - metadata { - name = var.database_server_certificate_authority_name - namespace = kubernetes_namespace.namespace.metadata[0].name - labels = { - app = var.app_name - component = "secret" +resource "kubernetes_manifest" "database_server_certificate_authority_sync" { + manifest = { + apiVersion = "external-secrets.io/v1beta1" + kind = "ExternalSecret" + metadata = { + name = var.database_server_certificate_authority_name + namespace = kubernetes_namespace.namespace.metadata[0].name + labels = { + app = var.app_name + component = "secret" + } } - - annotations = { - "reflector.v1.k8s.emberstack.com/reflects" = "${var.postgres_namespace}/${var.database_server_certificate_authority_name}" + spec = { + refreshInterval = "1h" + secretStoreRef = { + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + } + target = { + name = var.database_server_certificate_authority_name + template = { + type = "kubernetes.io/tls" + engineVersion = "v2" + } + } + dataFrom = [ + { + extract = { + key = "${var.postgres_namespace}/certificates/${var.database_server_certificate_authority_name}" + } + } + ] } } - data = { - "ca.crt" = "" - "tls.crt" = "" - "tls.key" = "" - } - - lifecycle { - ignore_changes = [metadata[0].annotations] + wait { + condition { + type = "Ready" + status = "True" + } } } // Database Client Certificate to be used for database connections -resource "kubernetes_secret" "database_client_certificate" { - metadata { - name = var.database_client_certificate_name - namespace = kubernetes_namespace.namespace.metadata[0].name - labels = { - app = var.app_name - component = "secret" +resource "kubernetes_manifest" "database_client_certificate_sync" { + manifest = { + apiVersion = "external-secrets.io/v1beta1" + kind = "ExternalSecret" + metadata = { + name = var.database_client_certificate_name + namespace = kubernetes_namespace.namespace.metadata[0].name + labels = { + app = var.app_name + component = "secret" + } } - - annotations = { - "reflector.v1.k8s.emberstack.com/reflects" = "${var.postgres_namespace}/${var.database_client_certificate_name}" + spec = { + refreshInterval = "1h" + secretStoreRef = { + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + } + target = { + name = var.database_client_certificate_name + template = { + type = "kubernetes.io/tls" + engineVersion = "v2" + } + } + dataFrom = [ + { + extract = { + key = "${var.postgres_namespace}/certificates/${var.database_client_certificate_name}" + } + } + ] } } - data = { - "ca.crt" = "" - "tls.crt" = "" - "tls.key" = "" - "key.der" = "" - } - - lifecycle { - ignore_changes = [metadata[0].annotations] + wait { + condition { + type = "Ready" + status = "True" + } } } diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index f90a298..2231c3a 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -1,279 +1,285 @@ -// Keycloak Stateful Set Cluster -resource "kubernetes_stateful_set" "keycloak_cluster" { - metadata { - name = "keycloak-cluster" - namespace = var.namespace - labels = { - app = "keycloak" - component = "statefulset" - } - } - spec { - replicas = var.replicas - service_name = "" - - // Stateful Set Pod Selector - selector { - match_labels = { - app = "keycloak" - component = "pod" - "part-of" = "keycloak" - "pg-access" = true - } - } - - // Pod Template - template { - - // Pod Metadata - metadata { - labels = { - app = "keycloak" - component = "pod" - "part-of" = "keycloak" - "pg-access" = true - } - - annotations = { - "prometheus.io/scrape" = "true" - "prometheus.io/path" = "/metrics" - "prometheus.io/port" = "9000" - "prometheus.io/scheme" = "https" - } - } - - // Pod Spec - spec { - - // Node Affinity rule to run only on worker nodes - affinity { - node_affinity { - required_during_scheduling_ignored_during_execution { - node_selector_term { - match_expressions { - key = "worker" - operator = "Exists" - } - } - } - } - } - - // Topology Spread to ensure pods are running on seperate nodes - topology_spread_constraint { - max_skew = 1 - topology_key = "kubernetes.io/hostname" - when_unsatisfiable = "DoNotSchedule" - label_selector { - match_labels = { - app = var.app_name - component = "pod" - "part-of" = "keycloak" - } - } - } - - // Container Details - container { - name = "keycloak" - image = "${var.repository}/${var.image}:${var.tag}" - args = ["--verbose", "start", "--import-realm"] - - // Environment Variables - env { - name = "KC_HOSTNAME" - value = "${var.host_name}.${var.domain}" - } - - dynamic "env" { - for_each = var.keycloak_environment_variables - content { - name = env.value["name"] - value = env.value["value"] - } - } - - env_from { - secret_ref { - name = kubernetes_secret.keycloak_credentials.metadata[0].name - } - } - - env_from { - secret_ref { - name = kubernetes_secret.realm_secrets.metadata[0].name - } - } - - env { - name = "KC_DB" - value = "postgres" - } - - env { - name = "KC_METRICS_ENABLED" - value = "true" - } - - env { - name = "KC_EVENT_METRICS_USER_ENABLED" - value = "true" - } - - env { - name = "KC_EVENT_METRICS_USER_TAGS" - value = "realm,idp,clientId" - } - - env { - name = "KC_DB_USERNAME" - value_from { - secret_key_ref { - name = kubernetes_secret.database_credentials.metadata[0].name - key = "username" - } - } - } - - env { - name = "KC_DB_PASSWORD" - value_from { - secret_key_ref { - name = kubernetes_secret.database_credentials.metadata[0].name - key = "password" - } - } - } - - // Port Mappings - dynamic "port" { - for_each = var.keycloak_ports - content { - name = port.value["name"] - container_port = port.value["containerPort"] - protocol = port.value["protocol"] - } - } - - // Startup, Liveness and Readiness Probes - startup_probe { - failure_threshold = 3 - http_get { - path = "/health/started" - port = "management" - scheme = "HTTPS" - } - period_seconds = 10 - success_threshold = 1 - timeout_seconds = 10 - initial_delay_seconds = 60 - } - - readiness_probe { - failure_threshold = 3 - http_get { - path = "/health/ready" - port = "management" - scheme = "HTTPS" - } - period_seconds = 10 - success_threshold = 1 - timeout_seconds = 10 - initial_delay_seconds = 60 - } - - liveness_probe { - failure_threshold = 3 - http_get { - path = "/health/live" - port = "management" - scheme = "HTTPS" - } - period_seconds = 10 - success_threshold = 1 - timeout_seconds = 10 - initial_delay_seconds = 60 - } - - // Resource limitations - resources { - requests = { - "cpu" = "500m" - "memory" = "1Gi" - } - - limits = { - "cpu" = "500m" - "memory" = "1Gi" - } - } - - // Volume mounts - volume_mount { - name = kubernetes_secret.database_server_certificate_authority.metadata[0].name - mount_path = "/mnt/certs/database/certificate-authority" - } - - volume_mount { - name = kubernetes_secret.database_client_certificate.metadata[0].name - mount_path = "/mnt/certs/database/certificate" - } - - volume_mount { - name = kubernetes_manifest.internal_certificate.manifest.spec.secretName - mount_path = "/mnt/certs/tls" - } - - volume_mount { - name = kubernetes_config_map.realm_configuration.metadata[0].name - mount_path = "/opt/keycloak/data/import" - } - } - - // Volumes - volume { - name = kubernetes_secret.database_server_certificate_authority.metadata[0].name - secret { - secret_name = kubernetes_secret.database_server_certificate_authority.metadata[0].name - } - } - - volume { - name = kubernetes_secret.database_client_certificate.metadata[0].name - secret { - secret_name = kubernetes_secret.database_client_certificate.metadata[0].name - } - } - - volume { - name = kubernetes_manifest.internal_certificate.manifest.spec.secretName - secret { - secret_name = kubernetes_manifest.internal_certificate.manifest.spec.secretName - } - } - - volume { - name = kubernetes_config_map.realm_configuration.metadata[0].name - config_map { - name = kubernetes_config_map.realm_configuration.metadata[0].name - } - } - - security_context { - fs_group = 1000 - run_as_user = 1000 - } - - } - } - - update_strategy { - rolling_update { - partition = 0 - } - type = "RollingUpdate" - } - } - - depends_on = [kubernetes_service.keycloak_service, kubernetes_service.keycloak_discovery] -} +# // Keycloak Stateful Set Cluster +# resource "kubernetes_stateful_set" "keycloak_cluster" { +# metadata { +# name = "keycloak-cluster" +# namespace = var.namespace +# labels = { +# app = "keycloak" +# component = "statefulset" +# } +# } +# spec { +# replicas = var.replicas +# service_name = "" + +# // Stateful Set Pod Selector +# selector { +# match_labels = { +# app = "keycloak" +# component = "pod" +# "part-of" = "keycloak" +# "pg-access" = true +# } +# } + +# // Pod Template +# template { + +# // Pod Metadata +# metadata { +# labels = { +# app = "keycloak" +# component = "pod" +# "part-of" = "keycloak" +# "pg-access" = true +# } + +# annotations = { +# "prometheus.io/scrape" = "true" +# "prometheus.io/path" = "/metrics" +# "prometheus.io/port" = "9000" +# "prometheus.io/scheme" = "https" +# } +# } + +# // Pod Spec +# spec { + +# // Node Affinity rule to run only on worker nodes +# affinity { +# node_affinity { +# required_during_scheduling_ignored_during_execution { +# node_selector_term { +# match_expressions { +# key = "worker" +# operator = "Exists" +# } +# } +# } +# } +# } + +# // Topology Spread to ensure pods are running on seperate nodes +# topology_spread_constraint { +# max_skew = 1 +# topology_key = "kubernetes.io/hostname" +# when_unsatisfiable = "DoNotSchedule" +# label_selector { +# match_labels = { +# app = var.app_name +# component = "pod" +# "part-of" = "keycloak" +# } +# } +# } + +# // Container Details +# container { +# name = "keycloak" +# image = "${var.repository}/${var.image}:${var.tag}" +# args = ["--verbose", "start", "--import-realm"] + +# // Environment Variables +# env { +# name = "KC_HOSTNAME" +# value = "${var.host_name}.${var.domain}" +# } + +# dynamic "env" { +# for_each = var.keycloak_environment_variables +# content { +# name = env.value["name"] +# value = env.value["value"] +# } +# } + +# env_from { +# secret_ref { +# name = kubernetes_secret.keycloak_credentials.metadata[0].name +# } +# } + +# env_from { +# secret_ref { +# name = kubernetes_secret.realm_secrets.metadata[0].name +# } +# } + +# env { +# name = "KC_DB" +# value = "postgres" +# } + +# env { +# name = "KC_METRICS_ENABLED" +# value = "true" +# } + +# env { +# name = "KC_EVENT_METRICS_USER_ENABLED" +# value = "true" +# } + +# env { +# name = "KC_EVENT_METRICS_USER_TAGS" +# value = "realm,idp,clientId" +# } + +# env { +# name = "KC_DB_USERNAME" +# value_from { +# secret_key_ref { +# name = kubernetes_manifest.database_credentials_sync.object.spec.target.name +# key = "username" +# } +# } +# } + +# env { +# name = "KC_DB_PASSWORD" +# value_from { +# secret_key_ref { +# name = kubernetes_manifest.database_credentials_sync.object.spec.target.name +# key = "password" +# } +# } +# } + +# // Port Mappings +# dynamic "port" { +# for_each = var.keycloak_ports +# content { +# name = port.value["name"] +# container_port = port.value["containerPort"] +# protocol = port.value["protocol"] +# } +# } + +# // Startup, Liveness and Readiness Probes +# startup_probe { +# failure_threshold = 3 +# http_get { +# path = "/health/started" +# port = "management" +# scheme = "HTTPS" +# } +# period_seconds = 10 +# success_threshold = 1 +# timeout_seconds = 10 +# initial_delay_seconds = 60 +# } + +# readiness_probe { +# failure_threshold = 3 +# http_get { +# path = "/health/ready" +# port = "management" +# scheme = "HTTPS" +# } +# period_seconds = 10 +# success_threshold = 1 +# timeout_seconds = 10 +# initial_delay_seconds = 60 +# } + +# liveness_probe { +# failure_threshold = 3 +# http_get { +# path = "/health/live" +# port = "management" +# scheme = "HTTPS" +# } +# period_seconds = 10 +# success_threshold = 1 +# timeout_seconds = 10 +# initial_delay_seconds = 60 +# } + +# // Resource limitations +# resources { +# requests = { +# "cpu" = "500m" +# "memory" = "1Gi" +# } + +# limits = { +# "cpu" = "500m" +# "memory" = "1Gi" +# } +# } + +# // Volume mounts +# volume_mount { +# name = "database-certificate-authority" +# mount_path = "/mnt/certs/database/certificate-authority" +# } + +# volume_mount { +# name = "database-client-certificate" +# mount_path = "/mnt/certs/database/certificate" +# } + +# volume_mount { +# name = kubernetes_manifest.internal_certificate.manifest.spec.secretName +# mount_path = "/mnt/certs/tls" +# } + +# volume_mount { +# name = kubernetes_config_map.realm_configuration.metadata[0].name +# mount_path = "/opt/keycloak/data/import" +# } +# } + +# // Volumes +# volume { +# name = "database-certificate-authority" +# secret { +# secret_name = kubernetes_manifest.database_server_certificate_authority_sync.object.spec.target.name +# } +# } + +# volume { +# name = "database-client-certificate" +# secret { +# secret_name = kubernetes_manifest.database_client_certificate_sync.object.spec.target.name +# } +# } + +# volume { +# name = kubernetes_manifest.internal_certificate.manifest.spec.secretName +# secret { +# secret_name = kubernetes_manifest.internal_certificate.manifest.spec.secretName +# } +# } + +# volume { +# name = kubernetes_config_map.realm_configuration.metadata[0].name +# config_map { +# name = kubernetes_config_map.realm_configuration.metadata[0].name +# } +# } + +# security_context { +# fs_group = 1000 +# run_as_user = 1000 +# } + +# } +# } + +# update_strategy { +# rolling_update { +# partition = 0 +# } +# type = "RollingUpdate" +# } +# } + +# depends_on = [ +# kubernetes_service.keycloak_service, +# kubernetes_service.keycloak_discovery, +# kubernetes_manifest.database_credentials_sync, +# kubernetes_manifest.database_client_certificate_sync, +# kubernetes_manifest.database_server_certificate_authority_sync +# ] +# } diff --git a/modules/keycloak/secrets.tf b/modules/keycloak/secrets.tf index e4c8a96..6a492b9 100644 --- a/modules/keycloak/secrets.tf +++ b/modules/keycloak/secrets.tf @@ -1,4 +1,46 @@ // Database Credentials to connect to the PostgreSQL Database +resource "kubernetes_manifest" "database_credentials_sync" { + manifest = { + apiVersion = "external-secrets.io/v1beta1" + kind = "ExternalSecret" + metadata = { + name = var.database_credentials + namespace = kubernetes_namespace.namespace.metadata[0].name + labels = { + app = var.app_name + component = "secret" + } + } + spec = { + refreshInterval = "1h" + secretStoreRef = { + name = var.cluster_secret_store_name + kind = "ClusterSecretStore" + } + target = { + name = var.database_credentials + template = { + # Using Opaque since the original secret was Opaque + type = "Opaque" + engineVersion = "v2" + } + } + dataFrom = [{ + extract = { + key = "${var.postgres_namespace}/credentials/${var.database_credentials}" + } + }] + } + } + + wait { + condition { + type = "Ready" + status = "True" + } + } +} + resource "kubernetes_secret" "database_credentials" { metadata { name = var.database_credentials diff --git a/modules/keycloak/variables.tf b/modules/keycloak/variables.tf index 59dfb59..78d6297 100644 --- a/modules/keycloak/variables.tf +++ b/modules/keycloak/variables.tf @@ -36,6 +36,13 @@ variable "observability_namespace" { nullable = false } +# --------------- CLUSTER SECRET STORE VARIABLES --------------- # +variable "cluster_secret_store_name" { + description = "Name of the cluster secret store to be used for pulling and pushing secrets to OpenBao" + type = string + nullable = false +} + # --------------- DATABASE VARIABLES --------------- # variable "cluster_name" { description = "Database Cluster Name to allow Network Connections to" From faf73524bc774c5b3a7067069d87980f8ea90a59 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:24:54 +0530 Subject: [PATCH 05/19] fix(keycloak): dependencies fix --- modules/keycloak/poddisruptionbudget.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/keycloak/poddisruptionbudget.tf b/modules/keycloak/poddisruptionbudget.tf index d330d85..c51aacd 100644 --- a/modules/keycloak/poddisruptionbudget.tf +++ b/modules/keycloak/poddisruptionbudget.tf @@ -19,5 +19,5 @@ resource "kubernetes_pod_disruption_budget_v1" "keycloak_pdb" { } } - depends_on = [kubernetes_stateful_set.keycloak_cluster] + # depends_on = [kubernetes_stateful_set.keycloak_cluster] } From 465428d0ded49181a52140a732648f6d4232b81b Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:27:13 +0530 Subject: [PATCH 06/19] fix(keycloak): api versions fix --- modules/keycloak/certificates.tf | 4 ++-- modules/keycloak/secrets.tf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index c699c6d..89d0b56 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -1,7 +1,7 @@ // Database Certificate Authority to be used for database connections resource "kubernetes_manifest" "database_server_certificate_authority_sync" { manifest = { - apiVersion = "external-secrets.io/v1beta1" + apiVersion = "external-secrets.io/v1" kind = "ExternalSecret" metadata = { name = var.database_server_certificate_authority_name @@ -45,7 +45,7 @@ resource "kubernetes_manifest" "database_server_certificate_authority_sync" { // Database Client Certificate to be used for database connections resource "kubernetes_manifest" "database_client_certificate_sync" { manifest = { - apiVersion = "external-secrets.io/v1beta1" + apiVersion = "external-secrets.io/v1" kind = "ExternalSecret" metadata = { name = var.database_client_certificate_name diff --git a/modules/keycloak/secrets.tf b/modules/keycloak/secrets.tf index 6a492b9..5c14190 100644 --- a/modules/keycloak/secrets.tf +++ b/modules/keycloak/secrets.tf @@ -1,7 +1,7 @@ // Database Credentials to connect to the PostgreSQL Database resource "kubernetes_manifest" "database_credentials_sync" { manifest = { - apiVersion = "external-secrets.io/v1beta1" + apiVersion = "external-secrets.io/v1" kind = "ExternalSecret" metadata = { name = var.database_credentials From efe61311cf4cd127fa43c1234664d5acfac71d12 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:38:18 +0530 Subject: [PATCH 07/19] fix(keycloak): key.der messed up --- modules/keycloak/certificates.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index 89d0b56..f91e509 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -66,6 +66,12 @@ resource "kubernetes_manifest" "database_client_certificate_sync" { template = { type = "kubernetes.io/tls" engineVersion = "v2" + data = { + "ca.crt" = "{{ .ca_crt }}" + "tls.crt" = "{{ .tls_crt }}" + "tls.key" = "{{ .tls_key }}" + "key.der" = "{{ .key_der }}" + } } } dataFrom = [ From 919555682db14dd0d17e7af7af9bf9e0234d5b00 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:41:33 +0530 Subject: [PATCH 08/19] fix(keycloak): key.der messed up --- modules/cnpg/certificates.tf | 3 +++ modules/keycloak/certificates.tf | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cnpg/certificates.tf b/modules/cnpg/certificates.tf index b9b8a5c..546a5a0 100644 --- a/modules/cnpg/certificates.tf +++ b/modules/cnpg/certificates.tf @@ -437,6 +437,9 @@ resource "kubernetes_manifest" "push_client_keycloak_certificate" { remoteKey = "${kubernetes_namespace.namespace.metadata[0].name}/certificates/${kubernetes_manifest.client_keycloak_certificate.object.spec.secretName}" } } + metadata = { + encoding = "base64" + } } ] } diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index f91e509..2629ed0 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -70,7 +70,7 @@ resource "kubernetes_manifest" "database_client_certificate_sync" { "ca.crt" = "{{ .ca_crt }}" "tls.crt" = "{{ .tls_crt }}" "tls.key" = "{{ .tls_key }}" - "key.der" = "{{ .key_der }}" + "key.der" = "{{ .key_der | base64decode }}" } } } From 10d3063c438ed3e1a590b1181e13ed6dde212782 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:45:32 +0530 Subject: [PATCH 09/19] fix(keycloak): key.der messed up --- modules/keycloak/certificates.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index 2629ed0..2178bd4 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -67,10 +67,10 @@ resource "kubernetes_manifest" "database_client_certificate_sync" { type = "kubernetes.io/tls" engineVersion = "v2" data = { - "ca.crt" = "{{ .ca_crt }}" - "tls.crt" = "{{ .tls_crt }}" - "tls.key" = "{{ .tls_key }}" - "key.der" = "{{ .key_der | base64decode }}" + "ca.crt" = "{{ index . \"ca.crt\" }}" + "tls.crt" = "{{ index . \"tls.crt\" }}" + "tls.key" = "{{ index . \"tls.key\" }}" + "key.der" = "{{ index . \"key.der\" | b64dec }}" } } } From c2d31fd21f2089cc461044c6cd59013bc0904ecf Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 12:53:25 +0530 Subject: [PATCH 10/19] fix(keycloak cnpg): switch to proper pem certificates --- modules/cnpg/certificates.tf | 5 ----- modules/keycloak/certificates.tf | 6 ------ modules/keycloak/secrets.tf | 29 ----------------------------- 3 files changed, 40 deletions(-) diff --git a/modules/cnpg/certificates.tf b/modules/cnpg/certificates.tf index 546a5a0..706db01 100644 --- a/modules/cnpg/certificates.tf +++ b/modules/cnpg/certificates.tf @@ -382,11 +382,6 @@ resource "kubernetes_manifest" "client_keycloak_certificate" { "reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces" = "keycloak" } } - "additionalOutputFormats" = [ - { - "type" : "DER" - } - ] "privateKey" = { "encoding" = "PKCS8" } diff --git a/modules/keycloak/certificates.tf b/modules/keycloak/certificates.tf index 2178bd4..89d0b56 100644 --- a/modules/keycloak/certificates.tf +++ b/modules/keycloak/certificates.tf @@ -66,12 +66,6 @@ resource "kubernetes_manifest" "database_client_certificate_sync" { template = { type = "kubernetes.io/tls" engineVersion = "v2" - data = { - "ca.crt" = "{{ index . \"ca.crt\" }}" - "tls.crt" = "{{ index . \"tls.crt\" }}" - "tls.key" = "{{ index . \"tls.key\" }}" - "key.der" = "{{ index . \"key.der\" | b64dec }}" - } } } dataFrom = [ diff --git a/modules/keycloak/secrets.tf b/modules/keycloak/secrets.tf index 5c14190..4499364 100644 --- a/modules/keycloak/secrets.tf +++ b/modules/keycloak/secrets.tf @@ -41,35 +41,6 @@ resource "kubernetes_manifest" "database_credentials_sync" { } } -resource "kubernetes_secret" "database_credentials" { - metadata { - name = var.database_credentials - namespace = kubernetes_namespace.namespace.metadata[0].name - - labels = { - app = var.app_name - component = "secret" - } - - annotations = { - "reflector.v1.k8s.emberstack.com/reflects" = "${var.postgres_namespace}/${var.database_credentials}" - } - - - } - - data = { - username = "" - password = "" - } - - type = "Opaque" - - lifecycle { - ignore_changes = [metadata[0].annotations] - } -} - // Keycloak Credentials resource "random_password" "keycloak_password" { length = 20 From 6d86a925a6a3cf27df5a73618aca7dc51f9f4414 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:04:39 +0530 Subject: [PATCH 11/19] fix(keycloak): generating der key on fly --- modules/keycloak/deployment.tf | 602 +++++++++++++++++---------------- modules/keycloak/variables.tf | 2 +- 2 files changed, 318 insertions(+), 286 deletions(-) diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index 2231c3a..5b32b9d 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -1,285 +1,317 @@ -# // Keycloak Stateful Set Cluster -# resource "kubernetes_stateful_set" "keycloak_cluster" { -# metadata { -# name = "keycloak-cluster" -# namespace = var.namespace -# labels = { -# app = "keycloak" -# component = "statefulset" -# } -# } -# spec { -# replicas = var.replicas -# service_name = "" - -# // Stateful Set Pod Selector -# selector { -# match_labels = { -# app = "keycloak" -# component = "pod" -# "part-of" = "keycloak" -# "pg-access" = true -# } -# } - -# // Pod Template -# template { - -# // Pod Metadata -# metadata { -# labels = { -# app = "keycloak" -# component = "pod" -# "part-of" = "keycloak" -# "pg-access" = true -# } - -# annotations = { -# "prometheus.io/scrape" = "true" -# "prometheus.io/path" = "/metrics" -# "prometheus.io/port" = "9000" -# "prometheus.io/scheme" = "https" -# } -# } - -# // Pod Spec -# spec { - -# // Node Affinity rule to run only on worker nodes -# affinity { -# node_affinity { -# required_during_scheduling_ignored_during_execution { -# node_selector_term { -# match_expressions { -# key = "worker" -# operator = "Exists" -# } -# } -# } -# } -# } - -# // Topology Spread to ensure pods are running on seperate nodes -# topology_spread_constraint { -# max_skew = 1 -# topology_key = "kubernetes.io/hostname" -# when_unsatisfiable = "DoNotSchedule" -# label_selector { -# match_labels = { -# app = var.app_name -# component = "pod" -# "part-of" = "keycloak" -# } -# } -# } - -# // Container Details -# container { -# name = "keycloak" -# image = "${var.repository}/${var.image}:${var.tag}" -# args = ["--verbose", "start", "--import-realm"] - -# // Environment Variables -# env { -# name = "KC_HOSTNAME" -# value = "${var.host_name}.${var.domain}" -# } - -# dynamic "env" { -# for_each = var.keycloak_environment_variables -# content { -# name = env.value["name"] -# value = env.value["value"] -# } -# } - -# env_from { -# secret_ref { -# name = kubernetes_secret.keycloak_credentials.metadata[0].name -# } -# } - -# env_from { -# secret_ref { -# name = kubernetes_secret.realm_secrets.metadata[0].name -# } -# } - -# env { -# name = "KC_DB" -# value = "postgres" -# } - -# env { -# name = "KC_METRICS_ENABLED" -# value = "true" -# } - -# env { -# name = "KC_EVENT_METRICS_USER_ENABLED" -# value = "true" -# } - -# env { -# name = "KC_EVENT_METRICS_USER_TAGS" -# value = "realm,idp,clientId" -# } - -# env { -# name = "KC_DB_USERNAME" -# value_from { -# secret_key_ref { -# name = kubernetes_manifest.database_credentials_sync.object.spec.target.name -# key = "username" -# } -# } -# } - -# env { -# name = "KC_DB_PASSWORD" -# value_from { -# secret_key_ref { -# name = kubernetes_manifest.database_credentials_sync.object.spec.target.name -# key = "password" -# } -# } -# } - -# // Port Mappings -# dynamic "port" { -# for_each = var.keycloak_ports -# content { -# name = port.value["name"] -# container_port = port.value["containerPort"] -# protocol = port.value["protocol"] -# } -# } - -# // Startup, Liveness and Readiness Probes -# startup_probe { -# failure_threshold = 3 -# http_get { -# path = "/health/started" -# port = "management" -# scheme = "HTTPS" -# } -# period_seconds = 10 -# success_threshold = 1 -# timeout_seconds = 10 -# initial_delay_seconds = 60 -# } - -# readiness_probe { -# failure_threshold = 3 -# http_get { -# path = "/health/ready" -# port = "management" -# scheme = "HTTPS" -# } -# period_seconds = 10 -# success_threshold = 1 -# timeout_seconds = 10 -# initial_delay_seconds = 60 -# } - -# liveness_probe { -# failure_threshold = 3 -# http_get { -# path = "/health/live" -# port = "management" -# scheme = "HTTPS" -# } -# period_seconds = 10 -# success_threshold = 1 -# timeout_seconds = 10 -# initial_delay_seconds = 60 -# } - -# // Resource limitations -# resources { -# requests = { -# "cpu" = "500m" -# "memory" = "1Gi" -# } - -# limits = { -# "cpu" = "500m" -# "memory" = "1Gi" -# } -# } - -# // Volume mounts -# volume_mount { -# name = "database-certificate-authority" -# mount_path = "/mnt/certs/database/certificate-authority" -# } - -# volume_mount { -# name = "database-client-certificate" -# mount_path = "/mnt/certs/database/certificate" -# } - -# volume_mount { -# name = kubernetes_manifest.internal_certificate.manifest.spec.secretName -# mount_path = "/mnt/certs/tls" -# } - -# volume_mount { -# name = kubernetes_config_map.realm_configuration.metadata[0].name -# mount_path = "/opt/keycloak/data/import" -# } -# } - -# // Volumes -# volume { -# name = "database-certificate-authority" -# secret { -# secret_name = kubernetes_manifest.database_server_certificate_authority_sync.object.spec.target.name -# } -# } - -# volume { -# name = "database-client-certificate" -# secret { -# secret_name = kubernetes_manifest.database_client_certificate_sync.object.spec.target.name -# } -# } - -# volume { -# name = kubernetes_manifest.internal_certificate.manifest.spec.secretName -# secret { -# secret_name = kubernetes_manifest.internal_certificate.manifest.spec.secretName -# } -# } - -# volume { -# name = kubernetes_config_map.realm_configuration.metadata[0].name -# config_map { -# name = kubernetes_config_map.realm_configuration.metadata[0].name -# } -# } - -# security_context { -# fs_group = 1000 -# run_as_user = 1000 -# } - -# } -# } - -# update_strategy { -# rolling_update { -# partition = 0 -# } -# type = "RollingUpdate" -# } -# } - -# depends_on = [ -# kubernetes_service.keycloak_service, -# kubernetes_service.keycloak_discovery, -# kubernetes_manifest.database_credentials_sync, -# kubernetes_manifest.database_client_certificate_sync, -# kubernetes_manifest.database_server_certificate_authority_sync -# ] -# } +// Keycloak Stateful Set Cluster +resource "kubernetes_stateful_set" "keycloak_cluster" { + metadata { + name = "keycloak-cluster" + namespace = var.namespace + labels = { + app = "keycloak" + component = "statefulset" + } + } + spec { + replicas = var.replicas + service_name = "" + + // Stateful Set Pod Selector + selector { + match_labels = { + app = "keycloak" + component = "pod" + "part-of" = "keycloak" + "pg-access" = true + } + } + + // Pod Template + template { + + // Pod Metadata + metadata { + labels = { + app = "keycloak" + component = "pod" + "part-of" = "keycloak" + "pg-access" = true + } + + annotations = { + "prometheus.io/scrape" = "true" + "prometheus.io/path" = "/metrics" + "prometheus.io/port" = "9000" + "prometheus.io/scheme" = "https" + } + } + + // Pod Spec + spec { + + init_container { + name = "certificate-converter" + image = "alpine:3.23.3" + command = ["/bin/sh", "-c"] + + args = [ + "apk add --no-cache openssl && openssl pkcs8 -topk8 -inform PEM -outform DER -in /mnt/certs/database/certificate/tls.key -out /mnt/der/key.der -nocrypt && chown 1000:0 /mnt/der/key.der && chmod 600 /mnt/der/key.der" + ] + + volume_mount { + name = "database-client-certificate" + mount_path = "/mnt/certs/database/certificate" + read_only = true + } + + volume_mount { + name = "database-der-key" + mount_path = "/mnt/der" + } + } + + // Node Affinity rule to run only on worker nodes + affinity { + node_affinity { + required_during_scheduling_ignored_during_execution { + node_selector_term { + match_expressions { + key = "worker" + operator = "Exists" + } + } + } + } + } + + // Topology Spread to ensure pods are running on seperate nodes + topology_spread_constraint { + max_skew = 1 + topology_key = "kubernetes.io/hostname" + when_unsatisfiable = "DoNotSchedule" + label_selector { + match_labels = { + app = var.app_name + component = "pod" + "part-of" = "keycloak" + } + } + } + + // Container Details + container { + name = "keycloak" + image = "${var.repository}/${var.image}:${var.tag}" + args = ["--verbose", "start", "--import-realm"] + + // Environment Variables + env { + name = "KC_HOSTNAME" + value = "${var.host_name}.${var.domain}" + } + + dynamic "env" { + for_each = var.keycloak_environment_variables + content { + name = env.value["name"] + value = env.value["value"] + } + } + + env_from { + secret_ref { + name = kubernetes_secret.keycloak_credentials.metadata[0].name + } + } + + env_from { + secret_ref { + name = kubernetes_secret.realm_secrets.metadata[0].name + } + } + + env { + name = "KC_DB" + value = "postgres" + } + + env { + name = "KC_METRICS_ENABLED" + value = "true" + } + + env { + name = "KC_EVENT_METRICS_USER_ENABLED" + value = "true" + } + + env { + name = "KC_EVENT_METRICS_USER_TAGS" + value = "realm,idp,clientId" + } + + env { + name = "KC_DB_USERNAME" + value_from { + secret_key_ref { + name = kubernetes_manifest.database_credentials_sync.object.spec.target.name + key = "username" + } + } + } + + env { + name = "KC_DB_PASSWORD" + value_from { + secret_key_ref { + name = kubernetes_manifest.database_credentials_sync.object.spec.target.name + key = "password" + } + } + } + + // Port Mappings + dynamic "port" { + for_each = var.keycloak_ports + content { + name = port.value["name"] + container_port = port.value["containerPort"] + protocol = port.value["protocol"] + } + } + + // Startup, Liveness and Readiness Probes + startup_probe { + failure_threshold = 3 + http_get { + path = "/health/started" + port = "management" + scheme = "HTTPS" + } + period_seconds = 10 + success_threshold = 1 + timeout_seconds = 10 + initial_delay_seconds = 60 + } + + readiness_probe { + failure_threshold = 3 + http_get { + path = "/health/ready" + port = "management" + scheme = "HTTPS" + } + period_seconds = 10 + success_threshold = 1 + timeout_seconds = 10 + initial_delay_seconds = 60 + } + + liveness_probe { + failure_threshold = 3 + http_get { + path = "/health/live" + port = "management" + scheme = "HTTPS" + } + period_seconds = 10 + success_threshold = 1 + timeout_seconds = 10 + initial_delay_seconds = 60 + } + + // Resource limitations + resources { + requests = { + "cpu" = "500m" + "memory" = "1Gi" + } + + limits = { + "cpu" = "500m" + "memory" = "1Gi" + } + } + + // Volume mounts + volume_mount { + name = "database-certificate-authority" + mount_path = "/mnt/certs/database/certificate-authority" + } + + volume_mount { + name = "database-der-key" + mount_path = "/mnt/der" + read_only = true + } + + volume_mount { + name = "database-client-certificate" + mount_path = "/mnt/certs/database/certificate" + } + + volume_mount { + name = kubernetes_manifest.internal_certificate.manifest.spec.secretName + mount_path = "/mnt/certs/tls" + } + + volume_mount { + name = kubernetes_config_map.realm_configuration.metadata[0].name + mount_path = "/opt/keycloak/data/import" + } + } + + // Volumes + volume { + name = "database-certificate-authority" + secret { + secret_name = kubernetes_manifest.database_server_certificate_authority_sync.object.spec.target.name + } + } + + volume { + name = "database-der-key" + empty_dir {} + } + + volume { + name = "database-client-certificate" + secret { + secret_name = kubernetes_manifest.database_client_certificate_sync.object.spec.target.name + } + } + + volume { + name = kubernetes_manifest.internal_certificate.manifest.spec.secretName + secret { + secret_name = kubernetes_manifest.internal_certificate.manifest.spec.secretName + } + } + + volume { + name = kubernetes_config_map.realm_configuration.metadata[0].name + config_map { + name = kubernetes_config_map.realm_configuration.metadata[0].name + } + } + + security_context { + fs_group = 1000 + run_as_user = 1000 + } + + } + } + + update_strategy { + rolling_update { + partition = 0 + } + type = "RollingUpdate" + } + } + + depends_on = [ + kubernetes_service.keycloak_service, + kubernetes_service.keycloak_discovery, + kubernetes_manifest.database_credentials_sync, + kubernetes_manifest.database_client_certificate_sync, + kubernetes_manifest.database_server_certificate_authority_sync + ] +} diff --git a/modules/keycloak/variables.tf b/modules/keycloak/variables.tf index 78d6297..46313f4 100644 --- a/modules/keycloak/variables.tf +++ b/modules/keycloak/variables.tf @@ -205,7 +205,7 @@ variable "keycloak_environment_variables" { }, { name = "KC_DB_URL" - value = "jdbc:postgresql://postgresql-cluster-rw.postgres.svc/keycloak?ssl=true&sslmode=verify-full&sslrootcert=/mnt/certs/database/certificate-authority/ca.crt&sslcert=/mnt/certs/database/certificate/tls.crt&sslkey=/mnt/certs/database/certificate/key.der" + value = "jdbc:postgresql://postgresql-cluster-rw.postgres.svc/keycloak?ssl=true&sslmode=verify-full&sslrootcert=/mnt/certs/database/certificate-authority/ca.crt&sslcert=/mnt/certs/database/certificate/tls.crt&sslkey=/mnt/der/key.der" }, { name = "KC_DB_POOL_INITIAL_SIZE" From 501163eea4031e531297bd873816d3495e1f28d2 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:07:23 +0530 Subject: [PATCH 12/19] fix(keycloak): generating der key on fly --- modules/keycloak/deployment.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index 5b32b9d..067fc05 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -50,6 +50,10 @@ resource "kubernetes_stateful_set" "keycloak_cluster" { image = "alpine:3.23.3" command = ["/bin/sh", "-c"] + security_context { + run_as_user = 0 + } + args = [ "apk add --no-cache openssl && openssl pkcs8 -topk8 -inform PEM -outform DER -in /mnt/certs/database/certificate/tls.key -out /mnt/der/key.der -nocrypt && chown 1000:0 /mnt/der/key.der && chmod 600 /mnt/der/key.der" ] From bab98b149ac6a7d1208fa262c4e2cde59fe67d46 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:13:44 +0530 Subject: [PATCH 13/19] fix(keycloak): generating der key on fly --- modules/keycloak/deployment.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index 067fc05..1791dc3 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -47,7 +47,7 @@ resource "kubernetes_stateful_set" "keycloak_cluster" { init_container { name = "certificate-converter" - image = "alpine:3.23.3" + image = "alpine/openssl:3.5.5" command = ["/bin/sh", "-c"] security_context { From bfea314166a2cce76c4a5fe076ddaa355c9da461 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:14:27 +0530 Subject: [PATCH 14/19] fix(keycloak): generating der key on fly --- modules/keycloak/deployment.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index 1791dc3..2611c35 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -55,7 +55,7 @@ resource "kubernetes_stateful_set" "keycloak_cluster" { } args = [ - "apk add --no-cache openssl && openssl pkcs8 -topk8 -inform PEM -outform DER -in /mnt/certs/database/certificate/tls.key -out /mnt/der/key.der -nocrypt && chown 1000:0 /mnt/der/key.der && chmod 600 /mnt/der/key.der" + "echo 'Starting certificate conversion...' && openssl pkcs8 -topk8 -inform PEM -outform DER -in /mnt/certs/database/certificate/tls.key -out /mnt/der/key.der -nocrypt && chown 1000:0 /mnt/der/key.der && chmod 600 /mnt/der/key.der && echo 'Conversion successful!'" ] volume_mount { From 6389e163f76d00f17afb631ea915ea0b45d8ef25 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:22:50 +0530 Subject: [PATCH 15/19] fix(keycloak): pdb dependency --- modules/keycloak/poddisruptionbudget.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/keycloak/poddisruptionbudget.tf b/modules/keycloak/poddisruptionbudget.tf index c51aacd..d330d85 100644 --- a/modules/keycloak/poddisruptionbudget.tf +++ b/modules/keycloak/poddisruptionbudget.tf @@ -19,5 +19,5 @@ resource "kubernetes_pod_disruption_budget_v1" "keycloak_pdb" { } } - # depends_on = [kubernetes_stateful_set.keycloak_cluster] + depends_on = [kubernetes_stateful_set.keycloak_cluster] } From aac2ce25d52e607c6cd33e3755b7c67ab0912107 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:29:22 +0530 Subject: [PATCH 16/19] feat(keycloak): comments --- modules/keycloak/deployment.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/keycloak/deployment.tf b/modules/keycloak/deployment.tf index 2611c35..be58a91 100644 --- a/modules/keycloak/deployment.tf +++ b/modules/keycloak/deployment.tf @@ -45,19 +45,28 @@ resource "kubernetes_stateful_set" "keycloak_cluster" { // Pod Spec spec { + // Using init container to convert + // PEM Key to a DER Key for Keycloak + // to consume init_container { name = "certificate-converter" image = "alpine/openssl:3.5.5" command = ["/bin/sh", "-c"] + // User 1000 does not + // exist in Alpine security_context { run_as_user = 0 } + // Generate some logs + // to indicate signs + // of life args = [ "echo 'Starting certificate conversion...' && openssl pkcs8 -topk8 -inform PEM -outform DER -in /mnt/certs/database/certificate/tls.key -out /mnt/der/key.der -nocrypt && chown 1000:0 /mnt/der/key.der && chmod 600 /mnt/der/key.der && echo 'Conversion successful!'" ] + // Volume Mounts volume_mount { name = "database-client-certificate" mount_path = "/mnt/certs/database/certificate" From bac9f57bff09346604dc992c5ce58986ebd96bf7 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:29:28 +0530 Subject: [PATCH 17/19] feat(cnpg): comments --- modules/cnpg/certificates.tf | 3 +++ modules/cnpg/secrets.tf | 2 ++ 2 files changed, 5 insertions(+) diff --git a/modules/cnpg/certificates.tf b/modules/cnpg/certificates.tf index 706db01..1ff51f4 100644 --- a/modules/cnpg/certificates.tf +++ b/modules/cnpg/certificates.tf @@ -98,6 +98,7 @@ resource "kubernetes_manifest" "server_certificate_authority" { } } +// Pushing the certificate to OpenBao for distribution resource "kubernetes_manifest" "push_server_certificate_authority" { manifest = { apiVersion = "external-secrets.io/v1alpha1" @@ -405,6 +406,7 @@ resource "kubernetes_manifest" "client_keycloak_certificate" { } } +// Pushing the certificate to OpenBao for distribution resource "kubernetes_manifest" "push_client_keycloak_certificate" { manifest = { apiVersion = "external-secrets.io/v1alpha1" @@ -508,6 +510,7 @@ resource "kubernetes_manifest" "client_certificates" { } } +// Pushing the certificate to OpenBao for distribution resource "kubernetes_manifest" "push_client_certificates" { count = length(var.clients) manifest = { diff --git a/modules/cnpg/secrets.tf b/modules/cnpg/secrets.tf index 2c4501f..fda0c7b 100644 --- a/modules/cnpg/secrets.tf +++ b/modules/cnpg/secrets.tf @@ -38,6 +38,7 @@ resource "kubernetes_manifest" "garage_configuration_sync" { } } +// Password Generator for generating random passwords resource "kubernetes_manifest" "password_generator" { manifest = { apiVersion = "generators.external-secrets.io/v1alpha1" @@ -193,6 +194,7 @@ resource "kubernetes_manifest" "push_client_database_credentials" { depends_on = [kubernetes_manifest.client_database_credentials_sync] } +// PGAdmin UI Credentials resource "kubernetes_manifest" "pgadmin_credentials_sync" { manifest = { apiVersion = "external-secrets.io/v1" From 690adca4a0e0c11cd8fefeaf87a277bef98fa92c Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:32:03 +0530 Subject: [PATCH 18/19] docs(cnpg): README update --- modules/cnpg/README.md | 42 ++++++++++++++++++++++----------------- modules/garage/README.md | 1 + modules/openbao/README.md | 7 ++++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/modules/cnpg/README.md b/modules/cnpg/README.md index da5c2d9..5438d12 100644 --- a/modules/cnpg/README.md +++ b/modules/cnpg/README.md @@ -7,6 +7,7 @@ Required Modules to deploy Cloudnative PG PostgreSQL Database: 2. [Cluster Issuer](../cluster-issuer) 3. [Garage](../garage) 4. [Observability](../observability) +5. [OpenBao](../openbao) ## Providers @@ -26,50 +27,55 @@ Required Modules to deploy Cloudnative PG PostgreSQL Database: | [kubernetes_manifest.barman_object_store](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.client_certificate_authority](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.client_certificates](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.client_database_credentials_sync](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.client_issuer](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.client_keycloak_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.client_streaming_replica_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.cluster](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.cluster_image_catalog](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.databases](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.garage_certificate_authority_sync](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.garage_configuration_sync](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.ingress_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.keycloak_database](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.keycloak_database_credentials_sync](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.password_generator](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.pgadmin_credentials_sync](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.pgadmin_internal_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.public_issuer](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_client_certificates](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_client_database_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_client_keycloak_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_keycloak_database_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_pgadmin_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.push_server_certificate_authority](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.server_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.server_certificate_authority](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_manifest.server_issuer](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | | [kubernetes_namespace.namespace](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | | [kubernetes_network_policy.cnpg_network_policy](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/network_policy) | resource | | [kubernetes_pod_disruption_budget_v1.cnpg_pdb](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/pod_disruption_budget_v1) | resource | -| [kubernetes_secret.client_database_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [kubernetes_secret.cloudflare_token](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | -| [kubernetes_secret.garage_certificate_authority](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | -| [kubernetes_secret.garage_configuration](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | -| [kubernetes_secret.keycloak_database_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | -| [kubernetes_secret.pgadmin_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [kubernetes_service.pgadmin4](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | -| [random_password.client_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [random_password.keycloak_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [random_password.pgadmin_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [acme\_server](#input\_acme\_server) | URL for the ACME Server to be used, defaults to production URL for LetsEncrypt | `string` | `"https://acme-v02.api.letsencrypt.org/directory"` | no | +| [acme\_server](#input\_acme\_server) | URL for the ACME Server to be used, defaults to production URL for LetsEncrypt | `string` | `"https://acme-v02 | [app\_name](#input\_app\_name) | App name for deploying PostgreSQL Database | `string` | `"postgres"` | no | | [backup\_bucket\_name](#input\_backup\_bucket\_name) | Name of the bucket for storing PITR Backups in Garage | `string` | n/a | yes | -| [client\_certificate\_authority\_name](#input\_client\_certificate\_authority\_name) | Name of the Certificate Authority to be used with PostgreSQL Client | `string` | `"postgresql-client-certificate-authority"` | no | -| [client\_issuer\_name](#input\_client\_issuer\_name) | Name of the Issuer to be used with PostgreSQL Client | `string` | `"postgresql-client-issuer"` | no | -| [client\_streaming\_replica\_certificate\_name](#input\_client\_streaming\_replica\_certificate\_name) | Name of the Certificate to be used with PostgreSQL Streaming Replica Client | `string` | `"postgresql-streaming-replica-client-certificate"` | no | -| [clients](#input\_clients) | Object List of clients who need databases and users to be configured for |
list(object({
namespace = string
user = string
database = string
derRequired = bool
privateKeyEncoding = string
}))
| `[]` | no | -| [cloudflare\_email](#input\_cloudflare\_email) | Email for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes | -| [cloudflare\_issuer\_name](#input\_cloudflare\_issuer\_name) | Name of the Cloudflare Issuer to be associated with PGAdmin | `string` | `"cnpg-cloudflare-issuer"` | no | -| [cloudflare\_token](#input\_cloudflare\_token) | Token for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes | -| [cluster\_issuer\_name](#input\_cluster\_issuer\_name) | Name for the Cluster Issuer to be used to generate internal self signed certificates | `string` | n/a | yes | +| [client\_certificate\_authority\_name](#input\_client\_certificate\_authority\_name) | Name of the Certificate Authority to be +| [client\_issuer\_name](#input\_client\_issuer\_name) | Name of the Issuer to be used with PostgreSQL Client | `string` | `"postgresql-client-i +| [client\_streaming\_replica\_certificate\_name](#input\_client\_streaming\_replica\_certificate\_name) | Name of the Ce +| [clients](#input\_clients) | Object List of clients who need databases and users to be configured for |
list(object({
namespace = s +| [cloudflare\_email](#input\_cloudflare\_email) | Email for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes +| [cloudflare\_issuer\_name](#input\_cloudflare\_issuer\_name) | Name of the Cloudflare Issuer to be associated with PGAdmin | `string` | `" +| [cloudflare\_token](#input\_cloudflare\_token) | Token for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes +| [cluster\_issuer\_name](#input\_cluster\_issuer\_name) | Name for the Cluster Issuer to be used to generate internal self signed certificates | [cluster\_name](#input\_cluster\_name) | Name of the PostgreSQL Database Cluster to be created | `string` | `"postgresql-cluster"` | no | -| [cluster\_postgresql\_version](#input\_cluster\_postgresql\_version) | Version of PostgreSQL Database to use and deploy | `number` | `17` | no | +| [cluster\_postgresql\_version](#input\_cluster\_postgresql\_version) | Version of PostgreSQL Database to use and deploy | `number` | ` +| [cluster\_secret\_store\_name](#input\_cluster\_secret\_store\_name) | Name of the cluster secret store to be used for pulling and push | [cluster\_size](#input\_cluster\_size) | Number of pods to deploy for the PostgreSQL Cluster | `number` | `2` | no | | [country\_name](#input\_country\_name) | Country name for deploying PostgreSQL Database | `string` | `"India"` | no | | [domain](#input\_domain) | Domain for which Ingress Certificate is to be generated for | `string` | n/a | yes | diff --git a/modules/garage/README.md b/modules/garage/README.md index 2f0dce4..a6d63b0 100644 --- a/modules/garage/README.md +++ b/modules/garage/README.md @@ -5,6 +5,7 @@ OpenTofu Module to deploy [Garage](https://garagehq.deuxfleurs.fr/) Object Stora Required Modules to deploy Garage Object Storage: 1. [Cluster Issuer](../cluster-issuer) 2. [Observability](../observability) +3. [OpenBao](../openbao) ## Providers diff --git a/modules/openbao/README.md b/modules/openbao/README.md index ffcb607..c787e5f 100644 --- a/modules/openbao/README.md +++ b/modules/openbao/README.md @@ -2,9 +2,10 @@ OpenTofu Module to deploy [OpenBao](https://openbao.org/) Secrets Management Solution on the Kubernetes Cluster. -Required Modules to deploy OpenBao Secrets Manageemnt Solution: -1. [Cluster Issuer](../cluster-issuer) -2. [Observability](../observability) +Required Modules to deploy OpenBao Secrets Management Solution: +1. [Helm](../helm) +2. [Cluster Issuer](../cluster-issuer) +3. [Observability](../observability) ## Providers From 843ebebe8764fded1dd2fbb7d01b10bb5518a2a0 Mon Sep 17 00:00:00 2001 From: khatrivarun Date: Sun, 15 Mar 2026 13:38:35 +0530 Subject: [PATCH 19/19] docs(cnpg): README update --- modules/cnpg/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/cnpg/README.md b/modules/cnpg/README.md index 5438d12..fb072bb 100644 --- a/modules/cnpg/README.md +++ b/modules/cnpg/README.md @@ -67,14 +67,14 @@ Required Modules to deploy Cloudnative PG PostgreSQL Database: | [backup\_bucket\_name](#input\_backup\_bucket\_name) | Name of the bucket for storing PITR Backups in Garage | `string` | n/a | yes | | [client\_certificate\_authority\_name](#input\_client\_certificate\_authority\_name) | Name of the Certificate Authority to be | [client\_issuer\_name](#input\_client\_issuer\_name) | Name of the Issuer to be used with PostgreSQL Client | `string` | `"postgresql-client-i -| [client\_streaming\_replica\_certificate\_name](#input\_client\_streaming\_replica\_certificate\_name) | Name of the Ce -| [clients](#input\_clients) | Object List of clients who need databases and users to be configured for |
list(object({
namespace = s +| [client\_streaming\_replica\_certificate\_name](#input\_client\_streaming\_replica\_certificate\_name) | Name of the Certificate to be used with PostgreSQL Streaming Replica Client | `string` | `"postgresql-streaming-replica-client-certificate"` | no | +| [clients](#input\_clients) | Object List of clients who need databases and users to be configured for |
list(object({
namespace = string
user = string
database = string
derRequired = bool
privateKeyEncoding = string
}))
| `[]` | no | | [cloudflare\_email](#input\_cloudflare\_email) | Email for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes -| [cloudflare\_issuer\_name](#input\_cloudflare\_issuer\_name) | Name of the Cloudflare Issuer to be associated with PGAdmin | `string` | `" +| [cloudflare\_issuer\_name](#input\_cloudflare\_issuer\_name) | Name of the Cloudflare Issuer to be associated with PGAdmin | `string` | `"cnpg-cloudflare-issuer"` | no | | [cloudflare\_token](#input\_cloudflare\_token) | Token for generating Ingress Certificates to be associated with PGAdmin | `string` | n/a | yes | [cluster\_issuer\_name](#input\_cluster\_issuer\_name) | Name for the Cluster Issuer to be used to generate internal self signed certificates | [cluster\_name](#input\_cluster\_name) | Name of the PostgreSQL Database Cluster to be created | `string` | `"postgresql-cluster"` | no | -| [cluster\_postgresql\_version](#input\_cluster\_postgresql\_version) | Version of PostgreSQL Database to use and deploy | `number` | ` +| [cluster\_postgresql\_version](#input\_cluster\_postgresql\_version) | Version of PostgreSQL Database to use and deploy | `number` | `17` | no | | [cluster\_secret\_store\_name](#input\_cluster\_secret\_store\_name) | Name of the cluster secret store to be used for pulling and push | [cluster\_size](#input\_cluster\_size) | Number of pods to deploy for the PostgreSQL Cluster | `number` | `2` | no | | [country\_name](#input\_country\_name) | Country name for deploying PostgreSQL Database | `string` | `"India"` | no |