Manage Vault and OpenBao clusters running in Kubernetes: inspect their state, unseal pods, step down the active node and roll out upgrades without downtime.
vault-mgmt talks to the Kubernetes API using your current kubeconfig context and reaches the Vault API through a port-forward to each pod, so Vault does not need to be exposed outside the cluster.
- Show the state of all pods: phase, image, and whether each one is initialized, sealed, active and ready.
- Unseal all sealed pods, with keys read from a Vault KV secret or printed by a local command.
- Step down the active pod.
- Reload the configuration of all pods, e.g. to pick up renewed TLS certificates.
- Upgrade the whole cluster without downtime: standby pods are upgraded first, then the active pod steps down and is upgraded last.
- Exec a command in the active, a standby or a sealed pod.
- Wait until the StatefulSet is ready.
- Vault or OpenBao deployed as a StatefulSet, e.g. with the official Helm chart. The pods are found by the selector of the StatefulSet, so several clusters can share a namespace.
- Kubernetes service registration enabled, so the pods carry the
vault-initialized,vault-sealedandvault-activelabels (openbao-*for OpenBao). - Kubernetes 1.32 or newer.
- Permissions in the namespace to get, list and delete pods, to create
pods/execandpods/portforward, and to get, list and watch StatefulSets.
Download a prebuilt binary for Linux or macOS (x86_64 and arm64) from the releases page, or build it from source:
cargo install --git https://github.com/nimbolus/vault-mgmtRelease binaries can update themselves with vault-mgmt self-update.
# show the state of the cluster in namespace "vault"
vault-mgmt show
# OpenBao in another namespace
vault-mgmt --flavor openbao --namespace openbao --statefulset openbao show
# unseal all sealed pods, reading the keys from a KV secret
export VAULT_TOKEN=...
vault-mgmt unseal --keys-secret-uri https://vault.example.com/v1/secret/data/vault/unseal-keys
# unseal with keys printed by a local command, one key per line
vault-mgmt unseal --key-cmd "pass show vault/unseal-keys"
# step down the active pod
vault-mgmt step-down
# reload all pods, e.g. after their TLS certificate was renewed
vault-mgmt reload
# reload only the standby pods
vault-mgmt reload --in standby
# rolling upgrade after changing the image of the StatefulSet
vault-mgmt upgrade --key-cmd "pass show vault/unseal-keys"
# run a command in a standby pod
vault-mgmt exec --in standby -- vault statusRun vault-mgmt help <command> for all options of a command.
| Option | Default | Description |
|---|---|---|
-n, --namespace |
vault |
Namespace Vault is running in |
-s, --statefulset |
vault |
StatefulSet whose pods are managed |
-f, --flavor |
vault |
vault or openbao |
-d, --domain |
vault |
Domain name used to verify the TLS certificate |
--no-tls |
Talk plain HTTP to Vault | |
-l, --log-level |
info |
error, warn, info, debug or trace |
Commands that need a Vault token (unseal with --keys-secret-uri, step-down, upgrade) take it from --token or the VAULT_TOKEN environment variable.
Unseal keys come from one of two sources:
--keys-secret-uri: a KV secret whose fieldkeyscontains the keys separated by newlines.--key-cmd: a command run locally that prints one key per line.
upgrade deletes each pod whose image tag differs from the one in the StatefulSet and waits for it to be recreated and unsealed. Standby pods go first. Then the active pod steps down and is upgraded once a standby has taken over.
--force-upgraderolls every pod, even if it is already up to date, for example after changing its configuration. Renewed certificates do not need a restart, see Certificate renewal.--do-not-unsealwaits for the pods to be unsealed by something else, e.g. auto-unseal.
Vault reads its TLS certificate only at startup and on SIGHUP. When the certificate is renewed, e.g. by cert-manager, the pods keep serving the old one until it expires. reload sends SIGHUP to the vault server process (bao server for OpenBao) in every pod, so the renewed certificate is used without a restart. --in active, --in standby or --in sealed limits it to the pods in that state. If no pod matches, reload fails, unless --ignore-not-found is given, e.g. for --in sealed in a scheduled job. Sending SIGHUP to pid 1 of the container is not enough with the Helm chart, as pid 1 is a shell that does not forward it.
vault-mgmt completion zsh > "${fpath[1]}/_vault-mgmt"Supported shells are bash, elvish, fish, powershell and zsh.
Unit tests run without a cluster:
cargo testThe end-to-end tests install, upgrade and uninstall several Vault and OpenBao clusters with Helm in the current kubeconfig context. They need:
- the
helmCLI, - a namespace,
vault-mgmt-e2eby default (override withVAULT_MGMT_E2E_NAMESPACE).e2e-preparation.yamlcreates it together with a NetworkPolicy.
The pods use emptyDir storage and do not need persistent volumes. The tests cover clustering and active/standby transitions, not storage. Releases are uninstalled afterwards, except when a test fails, to leave them for debugging.
kubectl apply -f e2e-preparation.yaml
cargo test -- --ignoredPushing a v* tag runs the release workflow: it runs the test suite, checks that CHANGELOG.md is up to date, creates a draft release with the changelog section of the version as notes, builds the binaries and publishes the release.
The changelog check regenerates CHANGELOG.md from the tag and compares it with the committed one. The version heading takes its date and compare link from the annotated tag. --tag-unreleased renders the unreleased commits as if HEAD was already tagged, so the changelog is generated before the tag and the tag goes on the release commit:
v=0.2.0
convco changelog --ignore-prereleases --unreleased "$v" --tag-unreleased --output CHANGELOG.md
git add CHANGELOG.md && git commit -m "chore: release v$v"
git tag -a "v$v" -m "v$v"
git push origin main "v$v"--tag-unreleasedis not in a convco release yet, it needs a build of thechangelog-unreleased-tagbranch of tobikris/convco. CI does not need it, it generates the changelog from the pushed tag.--unreleasedtakes the version without thevprefix.- The tag has to be annotated and created the same day, a lightweight tag gets the commit date.
chore:commits are not listed in the changelog, so the release commit does not change it.
Release candidates (tags with a -, like v0.2.0-rc.1) are published as prereleases. They are not added to CHANGELOG.md and need no release commit, the tag is pushed directly. The changelog check is skipped for them, their release notes cover the commits since the previous tag.