OpenBao CSI Driver (File-Based) + Stakater Reloader Integration#
This guide demonstrates integrating OpenBao with Stakater Reloader using the Secrets Store CSI Driver in file-based mode, where secrets are delivered directly as mounted files without syncing to Kubernetes Secrets.
Overview#
In the file-based CSI pattern, secrets from OpenBao are mounted directly into pods as files. Reloader monitors the SecretProviderClassPodStatus resource for version changes and triggers pod restarts when secrets are rotated.
sequenceDiagram
actor Ops as Operator
participant OB as OpenBao
participant CSI as CSI Driver +<br/>OpenBao Provider
participant SPCPS as SecretProviderClass<br/>PodStatus
participant RL as Reloader
participant Pod as Application Pod
Note over CSI: Authenticates via Kubernetes Auth
CSI->>OB: Authenticate (K8s SA JWT)
OB-->>CSI: Auth token
Note over Pod: Pod has CSI volume mounted (file-based, no K8s Secret)
Ops->>OB: Rotate secret (bao kv put)
loop CSI rotation interval
CSI->>OB: Fetch secrets
OB-->>CSI: Updated values
CSI->>Pod: Refresh mounted files
CSI->>SPCPS: Update version hash
end
SPCPS-->>RL: Watch event (version hash changed)
RL->>Pod: Trigger rolling restart
Note over Pod: New pod reads updated files
When to Use This Pattern#
Use the file-based CSI pattern when:
- Applications read secrets directly from files
- You want to avoid creating Kubernetes Secret objects
- Security policies require secrets to exist only in memory/tmpfs
- You need the simplest possible secret delivery mechanism
Prerequisites#
Complete the common setup steps from the Overview:
- OpenBao installed, initialized, and unsealed
- OpenBao CLI configured (
BAO_ADDRandBAO_TOKENexported) - KV v2 secrets engine enabled
- Test secrets written to
secret/myapp - Stakater Reloader installed
Additional requirements:
- Secrets Store CSI Driver installed with rotation enabled
- OpenBao CSI Provider installed
Note: All
baocommands below assumeBAO_ADDRandBAO_TOKENare set from Overview Step 3.
Step 1: Install Secrets Store CSI Driver#
Install with rotation enabled:
helm repo add secrets-store-csi-driver \
https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm repo update
helm install csi-secrets-store \
secrets-store-csi-driver/secrets-store-csi-driver \
-n kube-system \
--set enableSecretRotation=true \
--set rotationPollInterval=30s
Note: syncSecret.enabled is not required for file-based delivery.
Step 2: Enable OpenBao CSI Provider#
helm upgrade openbao openbao/openbao \
-n openbao \
--set csi.enabled=true \
--reuse-values
Step 3: Configure OpenBao#
Create Read Policy#
bao policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
EOF
Enable Kubernetes Auth#
bao auth enable kubernetes
bao write auth/kubernetes/config \
kubernetes_host='https://kubernetes.default.svc.cluster.local'
Create Auth Role#
bao write auth/kubernetes/role/openbao-csi-role \
bound_service_account_names=openbao-csi-sa \
bound_service_account_namespaces=openbao-csi-test \
policies=myapp-read \
ttl=1h
Write Test Secret#
bao kv put secret/myapp username=myuser password=mypassword
Step 4: Create Application Namespace and ServiceAccount#
kubectl create namespace openbao-csi-test
# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: openbao-csi-sa
namespace: openbao-csi-test
Step 5: Create SecretProviderClass#
File-based configuration without secretObjects:
# secret-provider-class.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: openbao-secrets-file
namespace: openbao-csi-test
spec:
provider: openbao
parameters:
vaultAddress: "http://openbao.openbao.svc.cluster.local:8200"
roleName: openbao-csi-role
objects: |
- objectName: "username"
secretPath: "secret/data/myapp"
secretKey: "username"
- objectName: "password"
secretPath: "secret/data/myapp"
secretKey: "password"
Key Differences from K8s Secret Sync:
- No
secretObjectssection - Secrets only exist as mounted files
- No Kubernetes Secret is created
Step 6: Deploy Application#
The deployment uses the CSI-specific Reloader annotation:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: openbao-csi-file-app
namespace: openbao-csi-test
annotations:
# Reloader annotation for CSI-mounted secrets (file-based)
secretproviderclass.reloader.stakater.com/reload: "openbao-secrets-file"
spec:
replicas: 1
selector:
matchLabels:
app: openbao-csi-file-app
template:
metadata:
labels:
app: openbao-csi-file-app
spec:
serviceAccountName: openbao-csi-sa
containers:
- name: app
image: busybox:latest
command:
- "sh"
- "-c"
- |
while true; do
echo "=== Secrets from file ==="
echo "Username: $(cat /mnt/secrets/username)"
echo "Password: $(cat /mnt/secrets/password)"
sleep 30
done
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: openbao-secrets-file
Key Configuration:
secretproviderclass.reloader.stakater.com/reload: "openbao-secrets-file"- Tells Reloader to watch the named SecretProviderClass for version changes- Secrets read directly from
/mnt/secrets/files - No environment variables referencing Kubernetes Secrets
Step 7: Verify Setup#
Apply manifests:
kubectl apply -f serviceaccount.yaml
kubectl apply -f secret-provider-class.yaml
kubectl apply -f deployment.yaml
Check pod is running:
kubectl get pods -n openbao-csi-test -l app=openbao-csi-file-app
Verify secrets are mounted:
kubectl exec -n openbao-csi-test deploy/openbao-csi-file-app -- cat /mnt/secrets/password
Check SecretProviderClassPodStatus:
kubectl get secretproviderclasspodstatus -n openbao-csi-test
View version information:
kubectl get secretproviderclasspodstatus -n openbao-csi-test -o yaml | grep -A5 "objects:"
Example output:
status:
objects:
- id: username
version: "u35OH4bBShH7wgv0YNyr1dzoZzJyKwDFq845rA6Ca_k="
- id: password
version: "hYds2SjGgvcyw2BIk9aYSJtxUV4hbIefYN3oPjO3WhA="
Step 8: Test Secret Rotation#
Record current versions:
kubectl get secretproviderclasspodstatus -n openbao-csi-test -o yaml | grep -A5 "objects:"
Update secret in OpenBao:
bao kv put secret/myapp username=myuser password=rotated-password
Wait for CSI rotation and check version change:
# After ~30-45 seconds (based on rotationPollInterval)
kubectl get secretproviderclasspodstatus -n openbao-csi-test -o yaml | grep -A5 "objects:"
The password version hash should change:
status:
objects:
- id: username
version: "u35OH4bBShH7wgv0YNyr1dzoZzJyKwDFq845rA6Ca_k=" # unchanged
- id: password
version: "yNvjDhGGwPLMPhAplHYNJ4eEWrq6zFzA7XGV7Oz6erQ=" # CHANGED
Reloader detects this change and triggers a pod restart.
How It Works#
- CSI Driver mounts secrets - Secrets are mounted as files in the pod's volume
- SecretProviderClassPodStatus created - CSI driver creates a status resource tracking mounted secrets and their versions
- Rotation polling - CSI driver periodically checks OpenBao for changes
- Version update - When secrets change, CSI driver updates the version hash in SecretProviderClassPodStatus
- Reloader detects change - Reloader watches SecretProviderClassPodStatus for the named SecretProviderClass and triggers pod restart on version changes
Reloader Annotation#
For file-based CSI secrets, use the dedicated annotation on the Deployment:
metadata:
annotations:
secretproviderclass.reloader.stakater.com/reload: "<secretproviderclass-name>"
This is different from the standard Secret/ConfigMap annotations. Reloader watches the SecretProviderClassPodStatus resources and triggers restarts when the version hashes change.
Comparison: File-Based vs K8s Secret Sync#
| Aspect | File-Based | K8s Secret Sync |
|---|---|---|
| Secret storage | Mounted files only | Files + K8s Secret |
| Reloader annotation | secretproviderclass...reload |
reloader...match on Secret |
| Security | Secrets never in etcd | Secrets stored in etcd |
| env var support | Read from file | Native secretKeyRef |
| Complexity | Simpler | More configuration |
Important Notes#
Provider Name#
Use openbao as the provider name:
spec:
provider: openbao
Rotation Must Be Enabled#
The CSI driver must have rotation enabled to detect changes:
--set enableSecretRotation=true
--set rotationPollInterval=30s
Application Must Read Files#
Applications must be designed to read secrets from mounted files rather than environment variables.
Troubleshooting#
Version Not Changing#
- Verify CSI driver has rotation enabled
- Check rotationPollInterval setting
- Wait for the full poll interval after rotating secrets
Secrets Not Mounting#
Check OpenBao CSI provider logs:
kubectl logs -n openbao -l app.kubernetes.io/name=openbao-csi-provider
Reloader Not Triggering Restart#
- Verify the annotation references the correct SecretProviderClass name
- Check Reloader logs for errors
- Ensure SecretProviderClassPodStatus exists and shows version changes