OpenBao Secrets Vault
June 12, 2026 · View on GitHub
Overview
OpenBao runs as a raft (Integrated Storage) cluster. Three artifacts make it recoverable:
- Raft snapshots — the
vault-snapshotCronJob saves abao operator raft snapshotto thevault-snapshotsPVC daily (newest 14 kept) and mirrors them to the S3 backup target underopenbao-snapshots/(Cloudflare R2 in prod, MinIO locally). The mirror exists because Velero's file-system backup only captures volumes mounted by running pods — nothing mounts this PVC outside the CronJob's brief run, so Velero alone would never carry the snapshots off-cluster. openbao-unsealSecret — unseal key + root token, captured by Velero's daily resource backup. A snapshot is only usable together with the keys that were current when it was taken.- The
vault-configJob — auto-initializes OpenBao on fresh clusters, auto-unseals on restarts, and auto-restores: when no pod reports an initialized barrier butopenbao-unsealstill holds keys AND a snapshot exists on the PVC, it temp-initializes, runsbao operator raft snapshot restore -forcewith the newest snapshot, and unseals with the stored key — no operator action. Only when no snapshot is available does it abort and demand explicit data-loss acknowledgement (the #1982 guard, unchanged).
Recovery Scenarios
Scenario 1: Single pod restart
Symptom: OpenBao pod was evicted or restarted; vault shows sealed: true.
Resolution: The postStart hook auto-unseals using the openbao-unseal
Secret (created by the vault-config Job on first init, backed up by Velero).
No manual action needed. Verify:
kubectl exec -n openbao openbao-0 -- bao status
Scenario 2: Raft data corruption or loss (Secret + snapshots intact)
Symptom: OpenBao fails to start with storage errors, or all pods report
initialized: false while openbao-unseal still exists (the 2026-06-10
incident shape).
Resolution — automated. Reset the data volumes and let the vault-config
Job restore the newest snapshot:
- Scale down the StatefulSet:
kubectl scale statefulset -n openbao openbao --replicas=0 - Delete the corrupted data PVCs (NOT
vault-snapshots, and do NOT delete theopenbao-unsealSecret — both are the restore inputs):kubectl delete pvc -n openbao data-openbao-0 data-openbao-1 data-openbao-2 - Trigger Flux reconciliation (
ksail workload reconcile) — the StatefulSet scales back up with empty volumes, and thevault-configJob detects uninitialized-pods + surviving-keys + available-snapshot and restores automatically (worst-case RPO: 24 h, the snapshot cadence). - ExternalSecrets resume syncing; PushSecrets top up anything newer than the snapshot on their next refresh.
Only if no snapshot exists (PVC also lost and the R2 mirror is empty) does
the Job abort with the data-loss guard; acknowledge the loss explicitly with
kubectl delete secret openbao-unseal -n openbao and the next run
re-initializes from scratch (Scenario 4 then re-seeds the KV).
Scenario 3: Full cluster rebuild (backups available)
Symptom: Entire cluster is lost (DR scenario); the R2 snapshot mirror and/or Velero backups are available.
Sequencing caveat: on a rebuilt cluster Flux stands OpenBao up before
any restore can run, so the vault-config Job auto-initializes a fresh
vault first (no openbao-unseal exists yet → the guard does not trigger).
Recovering the old data means deliberately resetting that fresh vault into
the Scenario 2 shape:
ksail cluster create+workload push/reconcile— the platform converges with a fresh, empty vault (PushSecrets re-seed SOPS-sourced values, so the cluster is functional but generated secrets are new).- Restore the old
openbao-unsealSecret (from the Velero backup) over the fresh one, and copy the newest snapshot from the R2openbao-snapshots/mirror onto thevault-snapshotsPVC. - Scale OpenBao to 0, delete the fresh
data-openbao-*PVCs, reconcile — thevault-configJob now hits the automated restore path (Scenario 2) and brings back the pre-incident vault. - ExternalSecrets resume syncing from the restored data; consumers pick up the old (matching) credentials.
Key point: a snapshot and the openbao-unseal Secret must come from the
same generation (same backup day) — keys from one era cannot unseal a
snapshot from another.
Scenario 4: Full cluster rebuild (no Velero backup)
Symptom: Cluster and backups are lost. Starting from scratch.
Resolution: SOPS-encrypted Secrets in Git remain the source of truth for bootstrap. On a fresh cluster:
ksail cluster create-- provisions infrastructure- Flux deploys
bootstrap-> SOPS-encrypted Secrets are available - Flux deploys
infrastructure-controllers-> OpenBao + ESO start. Controllers with placeholder Secrets (Dex, OAuth2-proxy) start with dummy values. - Flux deploys
infrastructure->vault-configJob auto-initializes:- Detects vault is uninitialized -> runs
bao operator init - Stores unseal key + root token in
openbao-unsealK8s Secret - Unseals and configures policies/roles
- Detects vault is uninitialized -> runs
- ESO Password generators create random secrets (DB passwords, OIDC client secrets). PushSecrets seed the vault from both generators and SOPS variables.
- ExternalSecrets sync secrets to consumer namespaces, overwriting placeholder Secrets.
- Reloader restarts Dex, OAuth2-proxy with real secrets.
No manual steps required.
Note: The Docker provider's platform CA key pair is not stored in OpenBao. cert-manager auto-generates it via a self-signed CA Certificate resource, so PushSecrets are not involved for that secret.
Scenario 5: Lost unseal key (Secret deleted, no Velero backup)
Symptom: The openbao-unseal Secret was deleted and no backup exists.
The vault is sealed and cannot be unsealed.
Resolution: The vault data is unrecoverable without the unseal key. Re-initialize from scratch:
- Delete the OpenBao PVC:
kubectl delete pvc -n openbao data-openbao-0 - Restart the StatefulSet:
kubectl rollout restart statefulset -n openbao openbao - Trigger Flux reconciliation -- the
vault-configJob re-initializes the vault. - ESO Password generators create new random secrets. PushSecrets re-seed all secrets (both generated and SOPS-sourced). The Docker provider's platform CA key pair is auto-generated by cert-manager and does not depend on OpenBao.