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#
sequenceDiagram
actor Ops as Operator
participant OB as OpenBao
participant ESO as External Secrets Operator
participant K8s as Kubernetes Secret
participant RL as Reloader
participant Pod as Application Pod
Note over ESO: Authenticates via Token or Kubernetes Auth
ESO->>OB: Authenticate (K8s SA JWT or token)
OB-->>ESO: Auth token / lease
Ops->>OB: Rotate secret (bao kv put)
loop Every refreshInterval
ESO->>OB: Read secret path
OB-->>ESO: Updated secret value
end
ESO->>K8s: Update Secret data
Note over K8s: annotation: reloader.stakater.com/match: "true"
K8s-->>RL: Watch event (Secret changed)
RL->>Pod: Trigger rolling restart
Note over Pod: New pod starts with updated secret
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 |