diff --git a/infrastructure/main.tf b/infrastructure/main.tf index e6c8ac8..2981c6b 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 @@ -171,8 +171,11 @@ module "ferretdb" { # Keycloak Cluster Deployment for Identity Solution module "keycloak" { - source = "git::https://github.com/necro-cloud/modules//modules/keycloak?ref=main" + 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 diff --git a/modules/cnpg/README.md b/modules/cnpg/README.md index da5c2d9..fb072bb 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\_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 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\_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 |
+| [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\_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/cnpg/certificates.tf b/modules/cnpg/certificates.tf
index ec5261b..1ff51f4 100644
--- a/modules/cnpg/certificates.tf
+++ b/modules/cnpg/certificates.tf
@@ -98,6 +98,50 @@ 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"
+ 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 = {
@@ -339,11 +383,6 @@ resource "kubernetes_manifest" "client_keycloak_certificate" {
"reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces" = "keycloak"
}
}
- "additionalOutputFormats" = [
- {
- "type" : "DER"
- }
- ]
"privateKey" = {
"encoding" = "PKCS8"
}
@@ -367,6 +406,53 @@ 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"
+ 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}"
+ }
+ }
+ metadata = {
+ encoding = "base64"
+ }
+ }
+ ]
+ }
+ }
+
+ // 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,51 @@ resource "kubernetes_manifest" "client_certificates" {
}
}
+// Pushing the certificate to OpenBao for distribution
+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/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..0773f17 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 index, 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[index].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"
}
}
}
diff --git a/modules/cnpg/secrets.tf b/modules/cnpg/secrets.tf
index b1fdded..fda0c7b 100644
--- a/modules/cnpg/secrets.tf
+++ b/modules/cnpg/secrets.tf
@@ -38,95 +38,225 @@ resource "kubernetes_manifest" "garage_configuration_sync" {
}
}
-// Database credentials configuration for Keycloak
-resource "random_password" "keycloak_password" {
- length = 20
- lower = true
- numeric = true
- special = false
+// Password Generator for generating random passwords
+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"
+// PGAdmin UI Credentials
+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]
}
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/keycloak/certificates.tf b/modules/keycloak/certificates.tf
index 14aa423..89d0b56 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/v1"
+ 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/v1"
+ 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..be58a91 100644
--- a/modules/keycloak/deployment.tf
+++ b/modules/keycloak/deployment.tf
@@ -45,6 +45,40 @@ 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"
+ 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 {
@@ -129,7 +163,7 @@ resource "kubernetes_stateful_set" "keycloak_cluster" {
name = "KC_DB_USERNAME"
value_from {
secret_key_ref {
- name = kubernetes_secret.database_credentials.metadata[0].name
+ name = kubernetes_manifest.database_credentials_sync.object.spec.target.name
key = "username"
}
}
@@ -139,7 +173,7 @@ resource "kubernetes_stateful_set" "keycloak_cluster" {
name = "KC_DB_PASSWORD"
value_from {
secret_key_ref {
- name = kubernetes_secret.database_credentials.metadata[0].name
+ name = kubernetes_manifest.database_credentials_sync.object.spec.target.name
key = "password"
}
}
@@ -210,12 +244,18 @@ resource "kubernetes_stateful_set" "keycloak_cluster" {
// Volume mounts
volume_mount {
- name = kubernetes_secret.database_server_certificate_authority.metadata[0].name
+ name = "database-certificate-authority"
mount_path = "/mnt/certs/database/certificate-authority"
}
volume_mount {
- name = kubernetes_secret.database_client_certificate.metadata[0].name
+ name = "database-der-key"
+ mount_path = "/mnt/der"
+ read_only = true
+ }
+
+ volume_mount {
+ name = "database-client-certificate"
mount_path = "/mnt/certs/database/certificate"
}
@@ -232,16 +272,21 @@ resource "kubernetes_stateful_set" "keycloak_cluster" {
// Volumes
volume {
- name = kubernetes_secret.database_server_certificate_authority.metadata[0].name
+ name = "database-certificate-authority"
secret {
- secret_name = kubernetes_secret.database_server_certificate_authority.metadata[0].name
+ secret_name = kubernetes_manifest.database_server_certificate_authority_sync.object.spec.target.name
}
}
volume {
- name = kubernetes_secret.database_client_certificate.metadata[0].name
+ name = "database-der-key"
+ empty_dir {}
+ }
+
+ volume {
+ name = "database-client-certificate"
secret {
- secret_name = kubernetes_secret.database_client_certificate.metadata[0].name
+ secret_name = kubernetes_manifest.database_client_certificate_sync.object.spec.target.name
}
}
@@ -275,5 +320,11 @@ resource "kubernetes_stateful_set" "keycloak_cluster" {
}
}
- depends_on = [kubernetes_service.keycloak_service, kubernetes_service.keycloak_discovery]
+ 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..4499364 100644
--- a/modules/keycloak/secrets.tf
+++ b/modules/keycloak/secrets.tf
@@ -1,30 +1,43 @@
// Database Credentials to connect to the PostgreSQL Database
-resource "kubernetes_secret" "database_credentials" {
- metadata {
- name = var.database_credentials
- namespace = kubernetes_namespace.namespace.metadata[0].name
-
- labels = {
- app = var.app_name
- component = "secret"
+resource "kubernetes_manifest" "database_credentials_sync" {
+ manifest = {
+ apiVersion = "external-secrets.io/v1"
+ kind = "ExternalSecret"
+ 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}"
+ 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}"
+ }
+ }]
}
-
-
- }
-
- data = {
- username = ""
- password = ""
}
- type = "Opaque"
-
- lifecycle {
- ignore_changes = [metadata[0].annotations]
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
}
}
diff --git a/modules/keycloak/variables.tf b/modules/keycloak/variables.tf
index 59dfb59..46313f4 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"
@@ -198,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"
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