Skip to content

terraform module for base-setup configuration of hashicorp vault.

License

Notifications You must be signed in to change notification settings

stuttgart-things/vault-base-setup

Repository files navigation

stuttgart-things/vault-base-setup

terraform module for base-setup configuration of hashicorp vault.

EXAMPLE USAGE

KUBECONFIG USAGE EXAMPLES

Option 1: Using File Path (Existing Method)

module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.demo-infra.example.com"
  cluster_name    = "kind-dev2"
  context         = "kind-dev2"
  skip_tls_verify = true
  kubeconfig_path = "/home/sthings/.kube/kind-dev2"
  csi_enabled     = true
  namespace_csi   = "vault"
  vso_enabled     = true
  namespace_vso   = "vault"
  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["read-k8s"]
      token_ttl      = 3600
    },
  ]
}

Option 2: Using String Content (New Method)

module "vault-base-setup" {
  source             = "github.com/stuttgart-things/vault-base-setup"
  vault_addr         = "https://vault.demo-infra.example.com"
  cluster_name       = "kind-dev2"
  context            = "kind-dev2"
  skip_tls_verify    = true
  kubeconfig_content = file("/home/sthings/.kube/kind-dev2")
  csi_enabled        = true
  namespace_csi      = "vault"
  vso_enabled        = true
  namespace_vso      = "vault"
  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["read-k8s"]
      token_ttl      = 3600
    },
  ]
}

Option 2b: Using an Environment Variable

variable "kubeconfig_content_from_env" {
  type      = string
  sensitive = true
}

module "vault-base-setup" {
  source             = "github.com/stuttgart-things/vault-base-setup"
  vault_addr         = "https://vault.demo-infra.example.com"
  cluster_name       = "kind-dev2"
  context            = "kind-dev2"
  skip_tls_verify    = true
  kubeconfig_content = var.kubeconfig_content_from_env
  csi_enabled        = true
  namespace_csi      = "vault"
  vso_enabled        = true
  namespace_vso      = "vault"
  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["read-k8s"]
      token_ttl      = 3600
    },
  ]
}
export TF_VAR_kubeconfig_content_from_env=$(cat /home/sthings/.kube/kind-dev2)

Option 2c. Using a Heredoc (Inline String)

module "vault-base-setup" {
  source             = "github.com/stuttgart-things/vault-base-setup"
  vault_addr         = "https://vault.demo-infra.example.com"
  cluster_name       = "kind-dev2"
  context            = "kind-dev2"
  skip_tls_verify    = true
  kubeconfig_content = <<-EOT
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: LS0tLS1CRUdJTi...
        server: https://127.0.0.1:6443
      name: kind-dev2
    contexts:
    - context:
        cluster: kind-dev2
        user: kind-dev2
      name: kind-dev2
    current-context: kind-dev2
    kind: Config
    preferences: {}
    users:
    - name: kind-dev2
      user:
        client-certificate-data: LS0tLS1CRUdJTi...
        client-key-data: LS0tLS1CRUdJTi...
  EOT
  csi_enabled        = true
  namespace_csi      = "vault"
  vso_enabled        = true
  namespace_vso      = "vault"
  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["read-k8s"]
      token_ttl      = 3600
    },
  ]
}

Important Notes:

  • Exactly one of kubeconfig_path or kubeconfig_content must be provided
  • kubeconfig_content is marked as sensitive to prevent accidental exposure in logs
  • Using file() function reads the file at plan time, while kubeconfig_path reads it during data source execution
EXECUTE VAULT CONFIG (VAULT SERVER)

MODULE CALL

cat > vault-base.tf <<'EOF'
module "vault-secrets-setup" {
  source                   = "github.com/stuttgart-things/vault-base-setup"
  kubeconfig_path          = "/home/sthings/.kube/demo-infra"
  vault_addr               = "https://vault.demo-infra.sthings-vsphere.labul.sva.de"
  createDefaultAdminPolicy = true
  csi_enabled              = false
  vso_enabled              = false
  enableApproleAuth        = true
  skip_tls_verify          = true

  approle_roles = [
    {
      name           = "read-k8s"
      token_policies = ["read-k8s"]
    }
  ]

  secret_engines = [
    {
      path        = "apps"
      name        = "s3"
      description = "minio app secrets"
      data_json   = <<EOT
      {
        "accessKey": "this",
        "secretKey": "andThat"
      }
      EOT
    },
    {
      path        = "kubeconfigs"
      name        = "kind-dev2"
      description = "kubeconfig for kind-dev2 cluster"
      data_json   = jsonencode({
        kubeconfig = file("/home/sthings/.kube/kind-dev2")
      })
    }
  ]

  kv_policies = [
    {
      name         = "read-k8s"
      capabilities = <<CAPS
path "apps/s3" {
  capabilities = ["create", "read", "update", "patch", "list"]
}
path "kubeconfigs/data/*" {
  capabilities = ["read", "list"]
}
CAPS
    }
  ]
}

output "role_ids" {
  description = "Role IDs from the vault approle module"
  value       = module.vault-secrets-setup.role_id
}

output "secret_ids" {
  description = "Secret IDs from the vault approle module"
  value       = module.vault-secrets-setup.secret_id
  sensitive   = true
}
EOF

EXECUTION

export VAULT_TOKEN=hvs.#..
terraform init
terraform apply --auto-approve
terraform output -json

TEST APPROLE w/ ANSIBLE (OPTIONAL)

cat <<EOF > test-approle.yaml
---
- hosts: localhost
  become: true

  vars:
    vault_approle_id: "INSERT-HERE"
    vault_approle_secret: "INSERT-HERE" # pragma: allowlist secret
    vault_url: "https://vault.demo-infra.example.com"

    username: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=apps/data/s3:accessKey validate_certs=false auth_method=approle role_id={{ vault_approle_id }} secret_id={{ vault_approle_secret }} url={{ vault_url }}') }}"
    kubeconfig: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=kubeconfigs/data/kind-dev2:kubeconfig validate_certs=false auth_method=approle role_id={{ vault_approle_id }} secret_id={{ vault_approle_secret }} url={{ vault_url }}') }}" # pragma: allowlist secret

  tasks:
    - name: Debug
      debug:
        var: username
    - name: Debug
      debug:
        var: kubeconfig

EOF

ansible-playbook test-approle.yaml -vv
DEPLOY K8S AUTH ON CLUSTER
cat > vault-k8s.tf <<'EOF'
module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.demo-infra.example.com"
  cluster_name    = "kind-dev2"
  context         = "kind-dev2"
  skip_tls_verify = true
  kubeconfig_path = "/home/sthings/.kube/kind-dev2"
  csi_enabled     = true
  namespace_csi   = "vault"
  vso_enabled     = true
  namespace_vso   = "vault"
  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["read-k8s"]
      token_ttl      = 3600
    },
  ]
}
EOF
# ONLY APPLY IF VSO IS ENABLED
kubectl apply -f https://raw.githubusercontent.com/hashicorp/vault-secrets-operator/main/chart/crds/secrets.hashicorp.com_vaultconnections.yaml
kubectl apply -f https://raw.githubusercontent.com/hashicorp/vault-secrets-operator/main/chart/crds/secrets.hashicorp.com_vaultauths.yaml

export VAULT_TOKEN=<TOKEN>
terraform init --upgrade
terraform apply
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: demo-infra
  namespace: default
spec:
  vaultAuthRef: dev            # Reference to a VaultAuth CRD, e.g., token or approle
  mount: kubeconfigs                  # The KV mount path in Vault
  type: kv-v2                  # KV engine version
  path: demo-infra             # Path under the mount
  refreshAfter: 10s            # How often to sync from Vault
  destination:
    create: true
    name: demo-infra-kube # Name of the Kubernetes Secret to create
VAULT PKI CA WITH CERT-MANAGER

Module Call

module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.demo-infra.example.com"
  cluster_name    = "kind-dev2"
  context         = "kind-dev2"
  skip_tls_verify = true
  kubeconfig_path = "/home/sthings/.kube/kind-dev2"
  csi_enabled     = false
  vso_enabled     = false

  # PKI CA configuration
  pki_enabled     = true
  pki_path        = "pki"
  pki_common_name = "example.com"
  pki_organization = "My Org"
  pki_root_ttl    = "87600h"
  pki_roles = [
    {
      name             = "example-dot-com"
      allowed_domains  = ["example.com"]
      allow_subdomains = true
      max_ttl          = "720h"
    }
  ]

  k8s_auths = [
    {
      name           = "dev"
      namespace      = "default"
      token_policies = ["pki-issue"]
      token_ttl      = 3600
    },
  ]
}

cert-manager ClusterIssuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: vault-issuer
spec:
  vault:
    path: pki/sign/example-dot-com
    server: https://vault.demo-infra.example.com
    auth:
      kubernetes:
        role: dev
        mountPath: /v1/auth/kind-dev2-dev
        serviceAccountRef:
          name: dev

cert-manager Certificate

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-tls
  namespace: default
spec:
  secretName: my-app-tls
  issuerRef:
    name: vault-issuer
    kind: ClusterIssuer
  commonName: myapp.example.com
  dnsNames:
    - myapp.example.com
DEPLOY VAULT SERVER (BITNAMI)

Deploys a Vault server using the Bitnami Helm chart. When vault_autounseal_enabled is set to true, the vault-autounseal chart is deployed alongside Vault to automatically initialize and unseal the server. Unseal keys and root token are stored as Kubernetes secrets (vault-keys and vault-root-token) in the Vault namespace.

module "vault-base-setup" {
  source                   = "github.com/stuttgart-things/vault-base-setup"
  vault_addr               = "https://vault.example.com"
  cluster_name             = "my-cluster"
  context                  = "default"
  skip_tls_verify          = true
  kubeconfig_path          = "/home/sthings/.kube/my-cluster"
  vault_enabled            = true
  vault_injector_enabled   = false
  namespace_vault          = "vault"
  vault_storage_class      = "openebs-hostpath"
  vault_autounseal_enabled = true
  csi_enabled              = false
  vso_enabled              = false
}

Retrieve the root token after deployment:

kubectl get secret vault-root-token -n vault -o jsonpath='{.data.root_token}' | base64 -d
DEPLOY VAULT SERVER WITH INGRESS/TLS
module "vault-base-setup" {
  source                    = "github.com/stuttgart-things/vault-base-setup"
  vault_addr                = "https://vault.demo-infra.example.com"
  cluster_name              = "my-cluster"
  context                   = "default"
  skip_tls_verify           = true
  kubeconfig_path           = "/home/sthings/.kube/my-cluster"
  vault_enabled             = true
  vault_injector_enabled    = false
  namespace_vault           = "vault"
  vault_storage_class       = "openebs-zfs"
  vault_autounseal_enabled  = true
  vault_ingress_enabled     = true
  vault_ingress_class       = "nginx"
  vault_ingress_hostname    = "vault.demo-infra.example.com"
  vault_ingress_issuer_name = "cluster-issuer-approle"
  vault_ingress_issuer_kind = "ClusterIssuer"
  csi_enabled               = false
  vso_enabled               = false
}
DEPLOY VAULT SERVER WITH GATEWAY API
module "vault-base-setup" {
  source                   = "github.com/stuttgart-things/vault-base-setup"
  vault_addr               = "https://vault.whatever.sthings-vsphere.labul.sva.de"
  cluster_name             = "my-cluster"
  context                  = "default"
  skip_tls_verify          = true
  kubeconfig_path          = "/home/sthings/.kube/my-cluster"
  vault_enabled            = true
  vault_injector_enabled   = false
  namespace_vault          = "vault"
  vault_storage_class      = "openebs-hostpath"
  vault_autounseal_enabled = true
  vault_gateway_enabled    = true
  vault_gateway_hostname   = "vault.whatever.sthings-vsphere.labul.sva.de"
  vault_gateway_name       = "whatever-gateway"
  vault_gateway_namespace  = "default"
  vault_gateway_section    = "https"
  csi_enabled              = false
  vso_enabled              = false
}
PKI CA BOOTSTRAP WORKFLOW

When Vault is the PKI CA but needs TLS on its own ingress, use the bootstrap workflow: deploy cert-manager with a self-signed CA first, then configure Vault PKI and create a Vault-backed ClusterIssuer.

Two-phase apply required: Phase 1 deploys cert-manager + bootstrap CA + Vault. Phase 2 configures PKI + Vault ClusterIssuer.

module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.demo-infra.example.com"
  cluster_name    = "my-cluster"
  context         = "default"
  skip_tls_verify = true
  kubeconfig_path = "/home/sthings/.kube/my-cluster"

  # Vault with auto-unseal + ingress
  vault_enabled            = true
  vault_injector_enabled   = false
  namespace_vault          = "vault"
  vault_storage_class      = "openebs-zfs"
  vault_autounseal_enabled = true
  vault_ingress_enabled    = true
  vault_ingress_class      = "nginx"
  vault_ingress_hostname   = "vault.demo-infra.example.com"

  # cert-manager + bootstrap CA (auto-wires ingress issuer)
  certmanager_enabled           = true
  certmanager_bootstrap_enabled = true

  # PKI + Vault ClusterIssuer (second apply)
  pki_enabled      = true
  pki_common_name  = "example.com"
  pki_organization = "My Org"
  pki_roles = [
    {
      name             = "example-dot-com"
      allowed_domains  = ["example.com"]
      allow_subdomains = true
      max_ttl          = "720h"
    }
  ]
  certmanager_vault_issuer_enabled  = true
  certmanager_vault_issuer_pki_role = "example-dot-com"

  csi_enabled = false
  vso_enabled = false
}
# Phase 1
terraform apply \
  -target=helm_release.cert_manager \
  -target=kubectl_manifest.bootstrap_ca_clusterissuer \
  -target=helm_release.vault \
  -target=helm_release.vault_autounseal

# Phase 2 (after Vault is unsealed)
export VAULT_TOKEN=$(kubectl get secret vault-root-token -n vault -o jsonpath='{.data.root_token}' | base64 -d)
terraform apply
VAULT CLUSTERISSUER (FLUX-MANAGED CERT-MANAGER / CROSS-CLUSTER)

Use the Vault ClusterIssuer without deploying cert-manager or the PKI engine through this module. This is useful when:

  • cert-manager is managed by Flux (or another GitOps tool)
  • The PKI engine already exists on a remote Vault instance
  • You want to issue certificates on an edge cluster from a central Vault

Same-cluster with Flux-managed cert-manager

When Vault runs in the same cluster and cert-manager is deployed externally (e.g. via Flux), use the internal cluster URL to avoid TLS/caBundle requirements:

module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.example.com"
  skip_tls_verify = true
  kubeconfig_path = "/path/to/kubeconfig"
  cluster_name    = "my-cluster"

  csi_enabled = false
  vso_enabled = false

  pki_enabled      = true
  pki_path         = "pki"
  pki_common_name  = "example.com"
  pki_organization = "My Org"
  pki_roles = [
    {
      name             = "example-dot-com"
      allowed_domains  = ["example.com"]
      allow_subdomains = true
      max_ttl          = "720h"
    }
  ]

  certmanager_vault_issuer_enabled  = true
  certmanager_vault_issuer_pki_role = "example-dot-com"
  certmanager_vault_issuer_server   = "http://vault-server.vault.svc.cluster.local:8200"
}

Cross-cluster (edge cluster → remote Vault)

When the PKI engine already exists on a remote Vault, set pki_enabled = false and provide the policy name and CA bundle explicitly:

module "vault-base-setup" {
  source          = "github.com/stuttgart-things/vault-base-setup"
  vault_addr      = "https://vault.k3s-infra.example.com"
  skip_tls_verify = true
  kubeconfig_path = "/path/to/edge-kubeconfig"
  cluster_name    = "edge-cluster"

  csi_enabled = false
  vso_enabled = false
  pki_enabled = false

  certmanager_vault_issuer_enabled     = true
  certmanager_vault_issuer_pki_role    = "k3s-infra"
  certmanager_vault_issuer_server      = "https://vault.k3s-infra.example.com"
  certmanager_vault_issuer_ca_bundle   = var.vault_ca_bundle
  certmanager_vault_issuer_policy_name = "pki-issue"
}

The vault_ca_bundle variable should contain the base64-encoded PEM of the CA that signed the Vault ingress TLS certificate.

Variables Reference

Variable Type Default Description
certmanager_vault_issuer_enabled bool false Create Vault-backed ClusterIssuer
certmanager_vault_issuer_name string "vault-pki" ClusterIssuer name
certmanager_vault_issuer_pki_role string "" PKI role for certificate issuance
certmanager_vault_issuer_server string "" Vault URL (defaults to vault_addr)
certmanager_vault_issuer_ca_bundle string "" Base64-encoded CA bundle (falls back to bootstrap CA, then omitted)
certmanager_vault_issuer_policy_name string "" Vault policy name (defaults to pki_policy_name)
certmanager_vault_issuer_namespace string "cert-manager" Namespace for the token secret
certmanager_vault_token_ttl string "720h" Vault token TTL
certmanager_vault_token_secret_name string "vault-pki-token" K8s secret name for the token
CSI PROVIDER EXAMPLE APPLICATION

SecretProviderClass

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-lab-creds
  namespace: default
spec:
  provider: vault
  parameters:
    objects: |
      - objectName: "lab"
        secretPath: "env/data/labul"
        secretKey: "lab"
    roleName: dev
    vaultAddress: https://vault.dev11.4sthings.tiab.ssc.sva.de
    vaultKubernetesMountPath: utah-dev
    vaultSkipTLSVerify: "true"

Example Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app3
  labels:
    app: demo
spec:
  selector:
    matchLabels:
      app: demo
  replicas: 1
  template:
    metadata:
      labels:
        app: demo
    spec:
      serviceAccountName: dev
      containers:
        - name: app
          image: nginx
          volumeMounts:
            - name: 'vault-user-creds'
              mountPath: '/mnt/secrets-store'
              readOnly: true
      volumes:
        - name: vault-user-creds
          csi:
            driver: 'secrets-store.csi.k8s.io'
            readOnly: true
            volumeAttributes:
              secretProviderClass: 'vault-lab-creds'
CALL MODULE W/ VALUES
module "vault-base-setup" {
  source = "github.com/stuttgart-things/vault-base-setup"
  createDefaultAdminPolicy = true
  secret_engines = [
    {
      path         = "cloud"
      name         = "vsphere"
      description  = "vsphere secrets",
      data_json    = <<EOT
      {
        "ip": "10.31.101.51"
      }
      EOT
    },
    {
      path         = "apps"
      name         = "s3"
      description  = "minio s3 secrets"
      data_json    = <<EOT
      {
        "accessKey": "this",
        "secretKey": "andThat" # pragma: allowlist secret
      }
      EOT
    }
  ]
  kv_policies = [
    {
      name         = "read-all-s3-kvv2"
      capabilities = <<EOF
path "s3-*/*" {
    capabilities = ["list", "read"]
}
EOF
    },
    {
      name         = "read-write-all-s3-kvv2"
      capabilities = <<EOF
path "s3-*/*" {
    capabilities = ["create", "read", "update", "patch", "list"]
}
EOF
    }
  ]
  enableApproleAuth = true
  approle_roles = [
    {
      name         = "s3"
      token_policies = ["read-all-s3-kvv2", "read-write-all-s3-kvv2"]
    },
    {
      name         = "s4"
      token_policies = ["read-all-s3-kvv2"]
    }
  ]
  enableUserPass = true
  user_list = [
    {
      path         = "auth/userpass/users/user1"
      data_json    = <<EOT
      {
        "password": "helloGitHub", # pragma: allowlist secret
        "policies": ""read-all-s3-kvv2", "read-write-all-s3-kvv2", "admin"
      }
      EOT
  }
  ]
  kubeconfig_path = "/home/sthings/.kube/labda-app"
  k8s_auths = [
    {
      name = "dev"
      namespace = "default"
      token_policies = ["read-all-s3-kvv2", "read-write-all-s3-kvv2"]
      token_ttl = 3600
    },
    {
      name = "cicd"
      namespace = "tektoncd"
      token_policies = ["read-all-tektoncd-kvv2"]
      token_ttl = 3600
    }
  ]
}

output "role_id" {
    value = module.vault-base-setup.role_id
}

output "secret_id" {
    value = module.vault-base-setup.secret_id
}
EXECUTE TERRAFORM
export VAULT_ADDR=${VAULT_ADDR}
export VAULT_TOKEN=${VAULT_TOKEN}

terraform init
terraform validate
terraform plan
terraform apply

Author Information

Xiaomin Lai, stuttgart-things 10/2023
Patrick Hermann, stuttgart-things 12/2023

License

Licensed under the Apache License, Version 2.0 (the "License").

You may obtain a copy of the License at apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" basis, without WARRANTIES or conditions of any kind, either express or implied.

See the License for the specific language governing permissions and limitations under the License.

About

terraform module for base-setup configuration of hashicorp vault.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors