How to Use Reloader with OpenBao External Secrets Operator Pattern#
This guide explains how to set up OpenBao with the External Secrets Operator (ESO), combined with Stakater Reloader for automatic pod restarts when secrets change.
Note: ESO uses the same Vault provider configuration for OpenBao because OpenBao is API-compatible with HashiCorp Vault.
Authentication Options#
ESO supports two authentication methods with OpenBao:
| Method | Description | Use Case |
|---|---|---|
| Token | Uses an OpenBao token stored in a K8s Secret | Simpler setup, good for development |
| Kubernetes Auth | Uses Kubernetes ServiceAccount tokens for authentication | More secure, no static credentials, recommended for production |
Choose the authentication method that best fits your security requirements and proceed to the corresponding section.
Overview#
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ External Secrets Operator │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────────────────────────────┐ │ │
│ │ │ SecretStore │ │ ExternalSecret │ │ │
│ │ │ │ │ │ │ │
│ │ │ - OpenBao URL │ │ - refreshInterval: 30s │ │ │
│ │ │ - Auth Method │────────►│ - Maps OpenBao KV to K8s Secret │ │ │
│ │ │ - KV v2 path │ │ - Adds Reloader annotation │ │ │
│ │ │ │ │ │ │ │
│ │ └─────────────────┘ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ K8s Secret │ │ Stakater │ │ Application │ │
│ │ (app-secrets) │───►│ Reloader │───►│ Pod │ │
│ │ │ │ │ │ │ │
│ │ annotation: │ │ Watches secret │ │ Reads secrets │ │
│ │ reloader.../ │ │ changes and │ │ from env vars │ │
│ │ match: "true" │ │ restarts pods │ │ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────────────┐ │
│ │ OpenBao │ │
│ │ Server │ │
│ │ │ │
│ │ KV v2 engine │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
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 - Read policy (
myapp-read) created - Stakater Reloader installed
Additional requirements:
- External Secrets Operator installed
Note: All
baocommands below assumeBAO_ADDRandBAO_TOKENare set from Overview Step 3.
Install External Secrets Operator#
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--wait
Verify installation:
kubectl get pods -n external-secrets
Option 1: Token Authentication#
This section covers setting up ESO with OpenBao token authentication. This method stores an OpenBao token in a Kubernetes Secret.
Token: Step 1 - Create OpenBao Policy#
bao policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
path "secret/metadata/myapp" {
capabilities = ["read"]
}
EOF
Token: Step 2 - Write Secrets#
bao kv put secret/myapp username='admin-user' password='super-secret-password'
Token: Step 3 - Create OpenBao Token#
Create a token with the read policy:
bao token create -policy=myapp-read -period=24h -display-name=eso-token
Important: Save the token value from the output. You'll need it in the next step.
Example output:
Key Value
--- -----
token s.CAESI...
token_accessor abc123...
token_duration 24h
token_renewable true
token_policies ["default", "myapp-read"]
Token: Step 4 - Create Application Namespace and Token Secret#
# Create namespace
kubectl create namespace openbao-eso-test
# Create token secret for ESO
# Replace <OPENBAO_TOKEN> with the token from Step 3
kubectl create secret generic openbao-token -n openbao-eso-test \
--from-literal=token="<OPENBAO_TOKEN>"
Token: Step 5 - Create SecretStore#
Create secret-store.yaml:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao-secret-store
namespace: openbao-eso-test
spec:
provider:
vault:
server: "http://openbao.openbao.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
tokenSecretRef:
name: openbao-token
key: token
Apply and verify:
kubectl apply -f secret-store.yaml
kubectl get secretstore -n openbao-eso-test
Should show STATUS: Valid and READY: True.
Now proceed to Create ExternalSecret section.
Option 2: Kubernetes Authentication#
This section covers setting up ESO with OpenBao Kubernetes authentication. This method uses Kubernetes ServiceAccount tokens to authenticate with OpenBao - no static credentials required.
K8s Auth: Step 1 - Create OpenBao Policy#
bao policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
path "secret/metadata/myapp" {
capabilities = ["read"]
}
EOF
K8s Auth: Step 2 - Enable and Configure Kubernetes Auth#
# Enable Kubernetes auth method (skip if already enabled)
bao auth enable kubernetes
# Configure Kubernetes auth
bao write auth/kubernetes/config \
kubernetes_host='https://kubernetes.default.svc.cluster.local'
K8s Auth: Step 3 - Create OpenBao Role#
bao write auth/kubernetes/role/eso-role \
bound_service_account_names=openbao-eso-sa \
bound_service_account_namespaces=openbao-eso-test \
policies=myapp-read \
ttl=1h
K8s Auth: Step 4 - Write Secrets#
bao kv put secret/myapp username='admin-user' password='super-secret-password'
K8s Auth: Step 5 - Create Application Namespace and ServiceAccount#
# Create namespace
kubectl create namespace openbao-eso-test
# Create service account for ESO to use
kubectl create serviceaccount openbao-eso-sa -n openbao-eso-test
K8s Auth: Step 6 - Create SecretStore#
Create secret-store.yaml:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao-secret-store
namespace: openbao-eso-test
spec:
provider:
vault:
server: "http://openbao.openbao.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
serviceAccountRef:
name: openbao-eso-sa
Apply and verify:
kubectl apply -f secret-store.yaml
kubectl get secretstore -n openbao-eso-test
Should show STATUS: Valid and READY: True.
Now proceed to Create ExternalSecret section.
Common Configuration#
The following sections apply to both authentication methods.
Create ExternalSecret#
Create external-secret.yaml:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: openbao-eso-test
spec:
refreshInterval: 30s
secretStoreRef:
name: openbao-secret-store
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
template:
metadata:
annotations:
# Reloader annotation - triggers restart on workloads with search: "true"
reloader.stakater.com/match: "true"
data:
- secretKey: username
remoteRef:
key: secret/data/myapp
property: username
- secretKey: password
remoteRef:
key: secret/data/myapp
property: password
Apply and verify:
kubectl apply -f external-secret.yaml
kubectl get externalsecret -n openbao-eso-test
Should show STATUS: SecretSynced and READY: True.
Deploy Application#
Create deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: openbao-eso-test-app
namespace: openbao-eso-test
annotations:
reloader.stakater.com/search: "true"
spec:
replicas: 1
selector:
matchLabels:
app: openbao-eso-test-app
template:
metadata:
labels:
app: openbao-eso-test-app
spec:
containers:
- name: app
image: busybox:latest
command:
- "sh"
- "-c"
- |
while true; do
echo "Username: $APP_USERNAME"
echo "Password: $APP_PASSWORD"
sleep 30
done
env:
- name: APP_USERNAME
valueFrom:
secretKeyRef:
name: app-secrets
key: username
- name: APP_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: password
Apply:
kubectl apply -f deployment.yaml
Verify the Setup#
# SecretStore
kubectl get secretstore -n openbao-eso-test
# ExternalSecret
kubectl get externalsecret -n openbao-eso-test
# Secret (created by ESO)
kubectl get secret app-secrets -n openbao-eso-test
# Pod
kubectl get pods -n openbao-eso-test
# Verify secret contents
kubectl get secret app-secrets -n openbao-eso-test -o jsonpath='{.data.password}' | base64 -d
# Check app logs
kubectl logs -n openbao-eso-test -l app=openbao-eso-test-app
Test Secret Rotation#
Update Secret in OpenBao#
bao kv put secret/myapp username='admin-user' password='new-rotated-password'
Wait and Verify#
Wait 30–60 seconds for ESO refresh and Reloader restart:
# Check secret was updated
kubectl get secret app-secrets -n openbao-eso-test -o jsonpath='{.data.password}' | base64 -d
# Check pod was restarted (new pod name)
kubectl get pods -n openbao-eso-test -l app=openbao-eso-test-app
# Check app logs show new password
kubectl logs -n openbao-eso-test -l app=openbao-eso-test-app --tail=5
Configuration Reference#
SecretStore Configuration#
Token Authentication#
| Field | Description |
|---|---|
server |
OpenBao server URL |
path |
KV secrets engine mount path (e.g., secret) |
version |
KV engine version (v2) |
auth.tokenSecretRef.name |
Name of K8s Secret containing OpenBao token |
auth.tokenSecretRef.key |
Key in the Secret containing the token value |
Kubernetes Authentication#
| Field | Description |
|---|---|
server |
OpenBao server URL |
path |
KV secrets engine mount path (e.g., secret) |
version |
KV engine version (v2) |
auth.kubernetes.mountPath |
OpenBao auth mount path (e.g., kubernetes) |
auth.kubernetes.role |
OpenBao role name bound to the ServiceAccount |
auth.kubernetes.serviceAccountRef.name |
Kubernetes ServiceAccount name |
ExternalSecret Configuration#
| Field | Description |
|---|---|
refreshInterval |
How often ESO syncs secrets from OpenBao (e.g., 30s) |
secretStoreRef |
Reference to the SecretStore |
target.name |
Name of the K8s Secret to create |
target.template.metadata.annotations |
Annotations to add to the created Secret |
data[].secretKey |
Key name in the K8s Secret |
data[].remoteRef.key |
Path in OpenBao (KV v2: secret/data/<path>) |
data[].remoteRef.property |
Specific field within the OpenBao secret |
Reloader Annotations#
| Resource | Annotation |
|---|---|
| Deployment | reloader.stakater.com/search: "true" |
| Secret (via ExternalSecret template) | reloader.stakater.com/match: "true" |
Comparison: Token vs Kubernetes Authentication#
| Aspect | Token | Kubernetes Auth |
|---|---|---|
| Credentials | Static token stored in K8s Secret | Dynamic tokens from ServiceAccount |
| Security | Token must be manually rotated | No static secrets, tokens auto-rotate |
| Setup Complexity | Simpler - just create a token | Requires OpenBao K8s auth configuration |
| OpenBao Config | Policy + token | Policy + auth method + role |
| Best For | Development, simple setups | Production, security-conscious environments |