Configure HashiCorp Vault integration with Kubernetes using the Vault CSI driver and Secrets Operator for automated secret injection and synchronization. This setup enables secure secret management for containerized applications with dynamic secret rotation and policy-based access controls.
Prerequisites
- Running Kubernetes cluster with admin access
- HashiCorp Vault server deployed and accessible
- Helm 3 installed and configured
- kubectl configured for cluster access
- Basic understanding of Vault policies and Kubernetes RBAC
What this solves
Managing secrets in Kubernetes clusters becomes complex when you need centralized secret storage, rotation, and audit trails across multiple environments. This tutorial integrates HashiCorp Vault with Kubernetes to provide secure secret injection into pods using the Vault CSI driver and automated secret synchronization with the Vault Secrets Operator. You'll configure Kubernetes authentication, deploy both integration methods, and establish policy-based access controls for production-ready secret management.
Prerequisites and requirements
You need a running Kubernetes cluster with administrative access and HashiCorp Vault server already deployed. Your cluster should have Helm 3 installed and configured. Basic familiarity with Vault policies and Kubernetes RBAC is recommended.
Step-by-step configuration
Configure Vault Kubernetes authentication
Set up the Kubernetes authentication method in Vault to allow pods to authenticate using service account tokens.
export VAULT_ADDR="https://vault.example.com:8200"
export VAULT_TOKEN="your-vault-token"
Enable Kubernetes authentication method
vault auth enable kubernetes
Extract Kubernetes cluster information
Gather the necessary cluster details that Vault needs to validate service account tokens.
# Get the Kubernetes API server URL
KUBE_API_URL=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')
Extract the service account token reviewer JWT
KUBE_SA_TOKEN=$(kubectl get secret -n kube-system -o jsonpath='{.items[?(@.metadata.annotations.kubernetes\.io/service-account\.name=="default")].data.token}' | base64 --decode)
Get the certificate authority
KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode)
Configure Vault with Kubernetes cluster details
Configure the Kubernetes authentication method with your cluster's connection information.
vault write auth/kubernetes/config \
token_reviewer_jwt="$KUBE_SA_TOKEN" \
kubernetes_host="$KUBE_API_URL" \
kubernetes_ca_cert="$KUBE_CA_CERT" \
disable_iss_validation=true
Create Vault policies for Kubernetes access
Define policies that control what secrets Kubernetes applications can access.
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "database/creds/myapp-role" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Apply the policy to Vault
vault policy write myapp-policy /tmp/app-policy.hcl
Create Kubernetes authentication role
Configure a role that maps Kubernetes service accounts to Vault policies.
vault write auth/kubernetes/role/myapp-role \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=default,production \
policies=myapp-policy \
ttl=1h
Install Vault CSI driver
Deploy the Vault CSI driver using Helm to enable secret injection as mounted volumes.
# Add HashiCorp Helm repository
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
Install Vault CSI driver
helm install vault-csi-provider hashicorp/vault-csi-provider \
--namespace vault-system \
--create-namespace \
--set "vault.address=${VAULT_ADDR}"
Install Vault Secrets Operator
Deploy the Vault Secrets Operator for automatic Kubernetes secret synchronization.
# Install Vault Secrets Operator
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
--namespace vault-secrets-operator-system \
--create-namespace
Configure Vault connection for the operator
Create a VaultConnection resource that defines how the operator connects to Vault.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
name: default
namespace: vault-secrets-operator-system
spec:
address: https://vault.example.com:8200
skipTLSVerify: false
kubectl apply -f vault-connection.yaml
Create service account for applications
Set up a Kubernetes service account that will authenticate with Vault.
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: myapp-sa-token
namespace: default
annotations:
kubernetes.io/service-account.name: myapp-sa
type: kubernetes.io/service-account-token
kubectl apply -f service-account.yaml
Create sample secrets in Vault
Add some test secrets to Vault that your applications will consume.
# Enable KV secrets engine if not already enabled
vault secrets enable -path=secret kv-v2
Create sample application secrets
vault kv put secret/myapp/config \
db_host="db.example.com" \
db_port="5432" \
api_key="super-secret-api-key"
vault kv put secret/myapp/credentials \
username="myapp-user" \
password="secure-password-123"
Configure CSI driver secret injection
Create a SecretProviderClass that defines which secrets to mount and how to format them.
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-myapp
namespace: default
spec:
provider: vault
parameters:
vaultAddress: "https://vault.example.com:8200"
roleName: "myapp-role"
objects: |
- objectName: "db_host"
secretPath: "secret/data/myapp/config"
secretKey: "db_host"
- objectName: "db_port"
secretPath: "secret/data/myapp/config"
secretKey: "db_port"
- objectName: "api_key"
secretPath: "secret/data/myapp/config"
secretKey: "api_key"
- objectName: "username"
secretPath: "secret/data/myapp/credentials"
secretKey: "username"
- objectName: "password"
secretPath: "secret/data/myapp/credentials"
secretKey: "password"
secretObjects:
- secretName: myapp-secret
type: Opaque
data:
- objectName: "db_host"
key: "DB_HOST"
- objectName: "db_port"
key: "DB_PORT"
- objectName: "api_key"
key: "API_KEY"
- objectName: "username"
key: "USERNAME"
- objectName: "password"
key: "PASSWORD"
kubectl apply -f secret-provider-class.yaml
Deploy application with CSI driver
Create a pod that uses the CSI driver to mount secrets from Vault.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-csi
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: myapp-csi
template:
metadata:
labels:
app: myapp-csi
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: nginx:alpine
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: myapp-secret
key: DB_HOST
- name: DB_PORT
valueFrom:
secretKeyRef:
name: myapp-secret
key: DB_PORT
command: ["/bin/sh"]
args: ["-c", "while true; do echo 'App running with secrets'; sleep 30; done"]
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-myapp"
kubectl apply -f app-with-csi.yaml
Configure Vault Secrets Operator authentication
Create a VaultAuth resource for the Secrets Operator to authenticate with Vault.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: default
spec:
vaultConnectionRef: vault-secrets-operator-system/default
method: kubernetes
mount: kubernetes
kubernetes:
role: myapp-role
serviceAccount: myapp-sa
kubectl apply -f vault-auth.yaml
Create VaultStaticSecret for operator
Configure the Secrets Operator to sync static secrets from Vault to Kubernetes secrets.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-config
namespace: default
spec:
vaultAuthRef: default
mount: secret
type: kv-v2
path: myapp/config
destination:
name: myapp-config-secret
create: true
refreshAfter: 30s
rolloutRestartTargets:
- kind: Deployment
name: myapp-operator
kubectl apply -f vault-static-secret.yaml
Deploy application using operator-managed secrets
Create a deployment that consumes secrets managed by the Vault Secrets Operator.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-operator
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: myapp-operator
template:
metadata:
labels:
app: myapp-operator
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: nginx:alpine
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: myapp-config-secret
key: db_host
- name: DB_PORT
valueFrom:
secretKeyRef:
name: myapp-config-secret
key: db_port
- name: API_KEY
valueFrom:
secretKeyRef:
name: myapp-config-secret
key: api_key
command: ["/bin/sh"]
args: ["-c", "while true; do echo 'Operator app running'; env | grep -E '(DB_|API_)'; sleep 30; done"]
ports:
- containerPort: 80
kubectl apply -f app-with-operator.yaml
Configure dynamic database secrets
Set up Vault to generate dynamic database credentials for enhanced security.
# Enable database secrets engine
vault secrets enable database
Configure database connection (example with PostgreSQL)
vault write database/config/myapp-db \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/myapp?sslmode=require" \
allowed_roles="myapp-role" \
username="vault" \
password="vault-db-password"
Create database role for dynamic credentials
vault write database/roles/myapp-role \
db_name=myapp-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
Create VaultDynamicSecret for database credentials
Configure the operator to manage dynamic database credentials with automatic rotation.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: myapp-db-creds
namespace: default
spec:
vaultAuthRef: default
mount: database
path: creds/myapp-role
destination:
name: myapp-db-secret
create: true
renewalPercent: 90
rolloutRestartTargets:
- kind: Deployment
name: myapp-db
kubectl apply -f vault-dynamic-secret.yaml
Verify your setup
Check that both integration methods are working correctly and secrets are accessible.
# Verify CSI driver pods are running
kubectl get pods -n vault-system
Check Secrets Operator status
kubectl get pods -n vault-secrets-operator-system
Verify application pods are running
kubectl get pods -l app=myapp-csi
kubectl get pods -l app=myapp-operator
Check that secrets were created
kubectl get secrets | grep myapp
Verify CSI-mounted secrets
kubectl exec deployment/myapp-csi -- ls -la /mnt/secrets
kubectl exec deployment/myapp-csi -- cat /mnt/secrets/db_host
Check operator-managed secrets
kubectl get secret myapp-config-secret -o jsonpath='{.data}' | jq
View VaultStaticSecret status
kubectl describe vaultstaticsecret myapp-config
Check dynamic secret status
kubectl describe vaultdynamicsecret myapp-db-creds
Advanced configuration options
Configure secret rotation and renewal
Set up automatic secret renewal to ensure credentials remain valid without manual intervention.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-rotated-config
namespace: default
spec:
vaultAuthRef: default
mount: secret
type: kv-v2
path: myapp/config
destination:
name: myapp-rotated-secret
create: true
refreshAfter: 300s
rolloutRestartTargets:
- kind: Deployment
name: myapp-operator
hmacSecretData: true
Implement namespace isolation
Configure separate Vault policies and roles for different Kubernetes namespaces to implement proper secret isolation. This integration works seamlessly with Vault dynamic database secrets for complete secret lifecycle management.
# Create namespace-specific policy
vault policy write production-policy - <Create namespace-specific role
vault write auth/kubernetes/role/production-role \
bound_service_account_names=production-sa \
bound_service_account_namespaces=production \
policies=production-policy \
ttl=30m
Security best practices
Enable audit logging
Configure Vault audit logging to track secret access patterns and maintain security compliance.
# Enable audit logging in Vault
vault audit enable file file_path=/vault/logs/audit.log
Verify audit logging is enabled
vault audit list
Implement least privilege access
Create granular policies that limit secret access to only what each application requires.
# Minimal policy for specific application
path "secret/data/myapp/database" {
capabilities = ["read"]
}
path "secret/data/myapp/api" {
capabilities = ["read"]
allowed_parameters = {
"version" = []
}
}
Deny access to sensitive admin secrets
path "secret/data/admin/*" {
capabilities = ["deny"]
}
Performance and scaling considerations
For high-throughput environments, configure connection pooling and caching to optimize secret retrieval performance. The CSI driver supports local caching to reduce Vault API calls.
csi:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 50m
memory: 128Mi
volumeMounts:
- name: cache
mountPath: /vault/cache
volumes:
- name: cache
emptyDir:
sizeLimit: 1Gi
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Pod fails to mount secrets | Service account not bound to Vault role | Verify role configuration with vault read auth/kubernetes/role/myapp-role |
| Authentication denied errors | Incorrect Kubernetes API configuration | Reconfigure with vault write auth/kubernetes/config |
| Secrets not syncing | VaultConnection misconfigured | Check connection status with kubectl describe vaultconnection |
| CSI driver pods crashlooping | Insufficient RBAC permissions | Verify CSI driver service account has required cluster roles |
| Dynamic secrets not rotating | Database role misconfigured | Test role with vault read database/creds/myapp-role |
| High latency secret retrieval | No connection pooling configured | Enable CSI driver caching and adjust refresh intervals |
Next steps
- Configure Vault PKI for automatic TLS certificate management
- Implement Pod Security Standards with admission controllers
- Set up network policies for microsegmentation
- Configure Vault Agent sidecar injection for legacy applications
- Set up monitoring and alerting for secret rotation
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# HashiCorp Vault + Kubernetes Integration Install Script
# Usage: ./install-vault-k8s.sh VAULT_ADDR VAULT_TOKEN [KUBE_NAMESPACE]
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
DEFAULT_NAMESPACE="default"
KUBE_NAMESPACE="${3:-$DEFAULT_NAMESPACE}"
TEMP_DIR="/tmp/vault-k8s-install"
# Usage function
usage() {
echo "Usage: $0 VAULT_ADDR VAULT_TOKEN [KUBE_NAMESPACE]"
echo "Example: $0 https://vault.example.com:8200 hvs.xyz123 production"
exit 1
}
# Cleanup function
cleanup() {
echo -e "${YELLOW}[CLEANUP] Removing temporary files...${NC}"
rm -rf "$TEMP_DIR"
if [ $? -ne 0 ]; then
echo -e "${RED}[ERROR] Installation failed. Check logs above.${NC}"
exit 1
fi
}
trap cleanup ERR EXIT
# Log functions
log_info() { echo -e "${GREEN}$1${NC}"; }
log_warn() { echo -e "${YELLOW}$1${NC}"; }
log_error() { echo -e "${RED}$1${NC}"; }
# Check arguments
if [ $# -lt 2 ]; then
usage
fi
VAULT_ADDR="$1"
VAULT_TOKEN="$2"
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution - /etc/os-release not found"
exit 1
fi
log_info "[1/12] Checking prerequisites..."
# Check if running as root or with sudo
if [ "$EUID" -ne 0 ]; then
log_error "Please run as root or with sudo"
exit 1
fi
# Create temp directory
mkdir -p "$TEMP_DIR"
chmod 755 "$TEMP_DIR"
log_info "[2/12] Updating package repositories..."
$PKG_UPDATE
log_info "[3/12] Installing required packages..."
case "$PKG_MGR" in
apt)
$PKG_INSTALL curl wget gnupg2 software-properties-common apt-transport-https ca-certificates
;;
dnf|yum)
$PKG_INSTALL curl wget gnupg2 ca-certificates
;;
esac
log_info "[4/12] Installing kubectl..."
if ! command -v kubectl &> /dev/null; then
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod 755 kubectl
mv kubectl /usr/local/bin/
chown root:root /usr/local/bin/kubectl
fi
log_info "[5/12] Installing Helm..."
if ! command -v helm &> /dev/null; then
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
fi
log_info "[6/12] Installing Vault CLI..."
if ! command -v vault &> /dev/null; then
case "$PKG_MGR" in
apt)
curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor > /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/hashicorp.list
apt update
$PKG_INSTALL vault
;;
dnf)
dnf config-manager --add-repo https://rpm.releases.hashicorp.com/fedora/hashicorp.repo
$PKG_INSTALL vault
;;
yum)
yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
$PKG_INSTALL vault
;;
esac
fi
log_info "[7/12] Configuring Vault environment..."
export VAULT_ADDR="$VAULT_ADDR"
export VAULT_TOKEN="$VAULT_TOKEN"
# Test Vault connection
if ! vault status &> /dev/null; then
log_error "Cannot connect to Vault at $VAULT_ADDR"
exit 1
fi
log_info "[8/12] Enabling Kubernetes authentication in Vault..."
vault auth enable kubernetes 2>/dev/null || log_warn "Kubernetes auth already enabled"
log_info "[9/12] Extracting Kubernetes cluster information..."
KUBE_API_URL=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')
KUBE_SA_TOKEN=$(kubectl get secret -n kube-system -o jsonpath='{.items[?(@.metadata.annotations.kubernetes\.io/service-account\.name=="default")].data.token}' | base64 --decode)
KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode)
log_info "[10/12] Configuring Vault Kubernetes authentication..."
vault write auth/kubernetes/config \
token_reviewer_jwt="$KUBE_SA_TOKEN" \
kubernetes_host="$KUBE_API_URL" \
kubernetes_ca_cert="$KUBE_CA_CERT" \
disable_iss_validation=true
log_info "[11/12] Creating Vault policy and role..."
cat > "$TEMP_DIR/app-policy.hcl" << EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "database/creds/myapp-role" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
EOF
chmod 644 "$TEMP_DIR/app-policy.hcl"
vault policy write myapp-policy "$TEMP_DIR/app-policy.hcl"
vault write auth/kubernetes/role/myapp-role \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces="$KUBE_NAMESPACE" \
policies=myapp-policy \
ttl=1h
log_info "[12/12] Installing Vault integrations via Helm..."
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Install Vault CSI driver
helm install vault-csi-provider hashicorp/vault-csi-provider \
--namespace vault-system \
--create-namespace \
--set "vault.address=${VAULT_ADDR}" \
--wait
# Install Vault Secrets Operator
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
--namespace vault-secrets-operator-system \
--create-namespace \
--wait
log_info "Verifying installation..."
kubectl get pods -n vault-system
kubectl get pods -n vault-secrets-operator-system
log_info "✅ HashiCorp Vault + Kubernetes integration completed successfully!"
log_info "Next steps:"
log_info "1. Create a service account: kubectl create serviceaccount myapp-sa -n $KUBE_NAMESPACE"
log_info "2. Configure VaultConnection and VaultStaticSecret resources"
log_info "3. Test secret injection with a sample application"
Review the script before running. Execute with: bash install.sh