diff --git a/README.md b/README.md index 5413cc0..66c28ef 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,9 @@ The following modules have been implemented and their usage instructions written 1. [Helm](modules/helm) 2. [Cluster Issuer for internal certificates](modules/cluster-issuer) 3. [Observability](modules/observability) -4. [Garage Storage](modules/garage) -5. [Cloudnative PG PostgreSQL Database](modules/cnpg) -6. [FerretDB (MongoDB) Database](modules/ferretdb) -7. [Valkey In Memory Database](modules/valkey) -8. [Keycloak Identity Management](modules/keycloak) +4. [OpenBao Secrets Management](modules/openbao) +5. [Garage Storage](modules/garage) +6. [Cloudnative PG PostgreSQL Database](modules/cnpg) +7. [FerretDB (MongoDB) Database](modules/ferretdb) +8. [Valkey In Memory Database](modules/valkey) +9. [Keycloak Identity Management](modules/keycloak) diff --git a/infrastructure/main.tf b/infrastructure/main.tf index c09b011..9192b61 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -33,6 +33,30 @@ module "observability" { depends_on = [module.cluster-issuer] } +# OpenBao Secrets Management Solution deployment +module "secrets" { + source = "git::https://github.com/necro-cloud/modules//modules/openbao?ref=task/110/openbao-deployment" + + // Certificates Details + cluster_issuer_name = module.cluster-issuer.cluster-issuer-name + cloudflare_token = var.cloudflare_token + cloudflare_email = var.cloudflare_email + domain = var.domain + + // Observability details + observability_namespace = module.observability.observability_namespace + + // Granting required namespaces access to the OpenBao cluster + access_namespaces = "external-secrets,cloud" + + // 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)) + + depends_on = [module.observability] +} + # Garage Deployment for an S3 compatible object storage solution module "garage" { source = "git::https://github.com/necro-cloud/modules//modules/garage?ref=main" diff --git a/modules/openbao/README.md b/modules/openbao/README.md new file mode 100644 index 0000000..7778a56 --- /dev/null +++ b/modules/openbao/README.md @@ -0,0 +1,71 @@ +## necronizer's cloud openbao module + +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) + +## Providers + +| Name | Version | +|------|---------| +| [helm](#provider\_helm) | 3.1.1 | +| [kubernetes](#provider\_kubernetes) | 2.38.0 | +| [random](#provider\_random) | 3.7.2 | + +## Resources + +| Name | Type | +|------|------| +| [helm_release.openbao](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [kubernetes_config_map.configurator_script](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map) | resource | +| [kubernetes_ingress_v1.ui_ingress](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/ingress_v1) | resource | +| [kubernetes_job.configurator](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/job) | resource | +| [kubernetes_manifest.certificate_authority](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.internal_certificate](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_manifest.issuer](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_namespace.namespace](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | +| [kubernetes_network_policy.openbao_network_access_policy](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/network_policy) | resource | +| [kubernetes_role.configurator](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role) | resource | +| [kubernetes_role_binding.configurator](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_binding) | resource | +| [kubernetes_secret.cloudflare_token](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | +| [kubernetes_secret.static_unseal_key](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | +| [kubernetes_service_account.configurator](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account) | resource | +| [random_id.static_unseal_key](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_namespaces](#input\_access\_namespaces) | Namespaces requiring accesses to the OpenBao Cluster in a comma seperated list | `string` | +| [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 OpenBao Secrets Management Solution | `string` | `"openbao"` | no | +| [certificate\_authority\_name](#input\_certificate\_authority\_name) | Name of the Certificate Authority to be associated with OpenBao +| [cloudflare\_email](#input\_cloudflare\_email) | Email for generating Ingress Certificates to be associated with OpenBao Secrets Management Solu +| [cloudflare\_issuer\_name](#input\_cloudflare\_issuer\_name) | Name of the Cloudflare Issuer to be associated with OpenBao Secrets Management Solution | `string` | `"secrets-cloudflare-issuer"` | no | +| [cloudflare\_token](#input\_cloudflare\_token) | Token for generating Ingress Certificates to be associated with OpenBao Secrets Management Solution | `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 | +| [cluster\_size](#input\_cluster\_size) | Number of pods to be deployed for High Availability for OpenBao Secrets Management Solution | `number` | `3` | no | +| [configurator\_image](#input\_configurator\_image) | Docker image to be used for deployment of OpenBao Configurator | `string` | `"openbao"` | no | +| [configurator\_repository](#input\_configurator\_repository) | Repository to be used for deployment of OpenBao Configurator | `string` | `"quay.io/openbao"` | no | +| [configurator\_tag](#input\_configurator\_tag) | Docker tag to be used for deployment of OpenBao Configurator | `string` | `"2.5.1"` | no | +| [country\_name](#input\_country\_name) | Country name for deploying OpenBao Secrets Management Solution | `string` | `"India"` | no | +| [domain](#input\_domain) | Domain for which Ingress Certificate is to be generated for | `string` | n/a | yes | +| [host\_name](#input\_host\_name) | Host name for which Ingress Certificate is to be generated for | `string` | `"secrets"` | no | +| [ingress\_certificate\_name](#input\_ingress\_certificate\_name) | Name of the Ingress Certificate to be associated with OpenBao Secrets Management Solution | `string` | `"secrets-ingress-certificate"` | no | +| [internal\_certificate\_name](#input\_internal\_certificate\_name) | Name of the Internal Certificate to be associated with OpenBao Secrets Management Solution | `string` | `"secrets-internal-certificate"` | no | +| [issuer\_name](#input\_issuer\_name) | Name of the Issuer to be associated with OpenBao Secrets Management Solution | `string` | `"secrets-certificate-issuer"` | no | +| [kubernetes\_api\_ip](#input\_kubernetes\_api\_ip) | IP Address for the Kubernetes API | `string` | n/a | yes | +| [kubernetes\_api\_port](#input\_kubernetes\_api\_port) | Port for the Kubernetes API | `number` | n/a | yes | +| [kubernetes\_api\_protocol](#input\_kubernetes\_api\_protocol) | Protocol for the Kubernetes API | `string` | n/a | yes | +| [namespace](#input\_namespace) | Namespace to be used for deploying OpenBao Secrets Management Solution | `string` | `"openbao"` | no | +| [observability\_namespace](#input\_observability\_namespace) | Namespace where all components for observability are deployed | `string` | n/a | yes | +| [openbao\_configuration](#input\_openbao\_configuration) | Dictionary filled with OpenBao Configuration Details | `map(string)` |
{
"chart": "openbao",
"name": "openbao",
"repository": "https://openbao.github.io/openbao-helm",
"version": "0.25.6"
} | no |
+| [organization\_name](#input\_organization\_name) | Organization name for deploying OpenBao Secrets Management Solution | `string` | `"cloud"` | no |
+
+## Outputs
+
+No outputs.
diff --git a/modules/openbao/certificates.tf b/modules/openbao/certificates.tf
new file mode 100644
index 0000000..92df5ac
--- /dev/null
+++ b/modules/openbao/certificates.tf
@@ -0,0 +1,254 @@
+// Certificate Authority to be used with OpenBao Cluster
+resource "kubernetes_manifest" "certificate_authority" {
+ manifest = {
+ "apiVersion" = "cert-manager.io/v1"
+ "kind" = "Certificate"
+ "metadata" = {
+ "name" = var.certificate_authority_name
+ "namespace" = kubernetes_namespace.namespace.metadata[0].name
+ "labels" = {
+ "app" = var.app_name
+ "component" = "certificate-authority"
+ }
+ }
+ "spec" = {
+ "isCA" = true
+ "subject" = {
+ "organizations" = [var.organization_name]
+ "countries" = [var.country_name]
+ "organizationalUnits" = [var.app_name]
+ }
+ "commonName" = var.certificate_authority_name
+ "secretName" = var.certificate_authority_name
+ "duration" = "70128h"
+ "privateKey" = {
+ "algorithm" = "ECDSA"
+ "size" = 256
+ }
+ "issuerRef" = {
+ "name" = "${var.cluster_issuer_name}"
+ "kind" = "ClusterIssuer"
+ "group" = "cert-manager.io"
+ }
+ }
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "5m"
+ update = "5m"
+ delete = "5m"
+ }
+}
+
+// Issuer for the OpenBao Cluster
+resource "kubernetes_manifest" "issuer" {
+ manifest = {
+ "apiVersion" = "cert-manager.io/v1"
+ "kind" = "Issuer"
+ "metadata" = {
+ "name" = var.issuer_name
+ "namespace" = kubernetes_namespace.namespace.metadata[0].name
+ "labels" = {
+ "app" = var.app_name
+ "component" = "issuer"
+ }
+ }
+ "spec" = {
+ "ca" = {
+ "secretName" = kubernetes_manifest.certificate_authority.manifest.spec.secretName
+ }
+ }
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "5m"
+ update = "5m"
+ delete = "5m"
+ }
+}
+
+// Internal Certificate for OpenBao Cluster
+resource "kubernetes_manifest" "internal_certificate" {
+ manifest = {
+ "apiVersion" = "cert-manager.io/v1"
+ "kind" = "Certificate"
+ "metadata" = {
+ "name" = var.internal_certificate_name
+ "namespace" = kubernetes_namespace.namespace.metadata[0].name
+ "labels" = {
+ "app" = var.app_name
+ "component" = "internal-certificate"
+ }
+ }
+ "spec" = {
+ "dnsNames" = [
+ "${var.host_name}.${var.domain}",
+ "localhost",
+ "127.0.0.1",
+ "*.${kubernetes_namespace.namespace.metadata[0].name}.svc.cluster.local",
+ "openbao-internal",
+ "openbao-internal.${kubernetes_namespace.namespace.metadata[0].name}.svc",
+ "openbao-internal.${kubernetes_namespace.namespace.metadata[0].name}.svc.cluster.local",
+ "*.openbao-internal.${kubernetes_namespace.namespace.metadata[0].name}.svc.cluster.local",
+ ]
+ "subject" = {
+ "organizations" = [var.organization_name]
+ "countries" = [var.country_name]
+ "organizationalUnits" = [var.app_name]
+ }
+ "commonName" = var.internal_certificate_name
+ "secretName" = var.internal_certificate_name
+ "issuerRef" = {
+ "name" = kubernetes_manifest.issuer.manifest.metadata.name
+ }
+ }
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+ timeouts {
+ create = "5m"
+ update = "5m"
+ delete = "5m"
+ }
+}
+
+// Kubernetes Secret for Cloudflare Tokens
+resource "kubernetes_secret" "cloudflare_token" {
+ metadata {
+ name = "cloudflare-token"
+ namespace = kubernetes_namespace.namespace.metadata[0].name
+ labels = {
+ "app" = var.app_name
+ "component" = "secret"
+ }
+ }
+
+ data = {
+ cloudflare-token = var.cloudflare_token
+ }
+
+ type = "Opaque"
+}
+
+// Cloudflare Issuer for Openbao Ingress Service
+resource "kubernetes_manifest" "public_issuer" {
+ manifest = {
+ "apiVersion" = "cert-manager.io/v1"
+ "kind" = "Issuer"
+ "metadata" = {
+ "name" = var.cloudflare_issuer_name
+ "namespace" = kubernetes_namespace.namespace.metadata[0].name
+ "labels" = {
+ "app" = var.app_name
+ "component" = "cloudflare-issuer"
+ }
+ }
+ "spec" = {
+ "acme" = {
+ "email" = var.cloudflare_email
+ "server" = var.acme_server
+ "privateKeySecretRef" = {
+ "name" = var.cloudflare_issuer_name
+ }
+ "solvers" = [
+ {
+ "dns01" = {
+ "cloudflare" = {
+ "email" = var.cloudflare_email
+ "apiTokenSecretRef" = {
+ "name" = "cloudflare-token"
+ "key" = "cloudflare-token"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+
+ depends_on = [kubernetes_secret.cloudflare_token]
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "5m"
+ update = "5m"
+ delete = "5m"
+ }
+}
+
+// Certificate to be used for OpenBao Ingress
+resource "kubernetes_manifest" "ingress_certificate" {
+
+ manifest = {
+ "apiVersion" = "cert-manager.io/v1"
+ "kind" = "Certificate"
+ "metadata" = {
+ "name" = var.ingress_certificate_name
+ "namespace" = kubernetes_namespace.namespace.metadata[0].name
+ "labels" = {
+ "app" = var.app_name
+ "component" = "ingress-certificate"
+ }
+ }
+ "spec" = {
+ "duration" = "2160h"
+ "renewBefore" = "360h"
+ "subject" = {
+ "organizations" = [var.organization_name]
+ "countries" = [var.country_name]
+ "organizationalUnits" = [var.app_name]
+ }
+ "privateKey" = {
+ "algorithm" = "RSA"
+ "encoding" = "PKCS1"
+ "size" = "2048"
+ }
+ "dnsNames" = ["${var.host_name}.${var.domain}"]
+ "secretName" = var.ingress_certificate_name
+ "issuerRef" = {
+ "name" = kubernetes_manifest.public_issuer.manifest.metadata.name
+ "kind" = "Issuer"
+ "group" = "cert-manager.io"
+ }
+ }
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "5m"
+ update = "5m"
+ delete = "5m"
+ }
+}
diff --git a/modules/openbao/config/configurator.sh b/modules/openbao/config/configurator.sh
new file mode 100644
index 0000000..307cd9f
--- /dev/null
+++ b/modules/openbao/config/configurator.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+set -e
+
+# Installing required dependencies
+echo "Installing required dependencies..."
+apk add --no-cache curl jq
+
+# Required Environment Variables
+NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
+K8S_API="https://kubernetes.default.svc"
+K8S_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
+K8S_CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+
+# OpenBao TLS Setup
+BAO_ADDR="https://openbao-internal.$NAMESPACE.svc:8200"
+BAO_CACERT="/openbao/userconfig/${cert_secret_name}/ca.crt"
+
+export BAO_ADDR=$BAO_ADDR
+export BAO_CACERT=$BAO_CACERT
+
+# Wait for OpenBao API to respond over HTTPS
+echo "Waiting for OpenBao API at $BAO_ADDR..."
+until curl -s --cacert "$BAO_CACERT" "$BAO_ADDR/v1/sys/health" | grep -q 'initialized'; do
+ echo "Still waiting..."
+ sleep 5
+done
+
+# Initialize the Cluster
+if ! bao operator init -status > /dev/null 2>&1; then
+ echo "Initializing OpenBao Cluster..."
+ bao operator init -format=json > /tmp/keys.json
+
+ ROOT_TOKEN=$(jq -r '.root_token' /tmp/keys.json)
+ export BAO_TOKEN=$ROOT_TOKEN
+
+ # Save Keys to K8s Secret
+ echo "Persisting recovery keys to Kubernetes..."
+ B64_DATA=$(cat /tmp/keys.json | base64 | tr -d '\n')
+
+ curl -s --cacert "$K8S_CACERT" \
+ -X POST \
+ -H "Authorization: Bearer $K8S_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"apiVersion\": \"v1\",
+ \"kind\": \"Secret\",
+ \"metadata\": { \"name\": \"bao-init-recovery\" },
+ \"data\": { \"keys.json\": \"$B64_DATA\" }
+ }" \
+ "$K8S_API/api/v1/namespaces/$NAMESPACE/secrets"
+
+ # Configure Kubernetes Auth for External Secrets Operator
+ echo "Configuring Kubernetes Auth..."
+ bao auth enable kubernetes
+ bao write auth/kubernetes/config \
+ kubernetes_host="$K8S_API" \
+ kubernetes_ca_cert="@$K8S_CACERT" \
+ disable_iss_validation=true
+
+ # Setup ESO Access
+ bao secrets enable -path=secret kv-v2
+ bao policy write eso-policy - <