terraform module for base-setup configuration of hashicorp vault.
KUBECONFIG USAGE EXAMPLES
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
},
]
}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
},
]
}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)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)
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
}
EOFexport VAULT_TOKEN=hvs.#..
terraform init
terraform apply --auto-approve
terraform output -jsoncat <<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 -vvDEPLOY 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 createVAULT PKI CA WITH CERT-MANAGER
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
},
]
}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: devapiVersion: 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.comDEPLOY 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 -dDEPLOY 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 applyVAULT 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
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"
}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.
| 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
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"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 applyXiaomin Lai, stuttgart-things 10/2023
Patrick Hermann, stuttgart-things 12/2023Licensed 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.