- Added internal/vault/client.go for creating a Vault client with configuration settings. - Introduced internal/vault/errors.go to classify Vault API errors for better UI handling. - Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations. - Implemented internal/vault/mounts.go to list and describe secret engine mounts. - Developed internal/vault/service.go to provide a unified entry point for Vault operations. - Added internal/vault/kv_test.go for comprehensive testing of KV operations. - Introduced internal/ui/toast.go for transient notifications in the UI. - Added renovate.json for dependency management and updates.
vault-tui
A terminal UI and CLI for HashiCorp Vault, built on Bubble Tea v2
and urfave/cli v3. Browse mounts and KV secrets, view version
history, and log in with (almost) any Vault auth method — with VAULT_TOKEN/an existing
vault login session and browser-based OIDC as the two flows it's built around.
Install / build
make build # dist/vault-tui — default build, no cloud auth
make build-cloud # dist/vault-tui-cloud — with AWS/Azure/GCP auth
make install # go install into $PREFIX/bin ($HOME/.local/bin by default)
make help # every target, with a one-line description of each
Cloud auth methods (AWS, Azure, GCP) are excluded from the default build — their SDKs pull in
a lot of dependencies most deployments never need. make build-cloud/make install-cloud
add them (equivalent to -tags cloud); make release/make release-cloud cross-compile both
for linux/darwin/windows × amd64/arm64 into dist/, with checksums. make check runs
formatting, go vet, lint (via golangci-lint if installed, else go vet), and tests — the
same gate you'd wire into CI. Without make, the plain Go commands still work:
go build -o vault-tui ./cmd/vault-tui
go build -tags cloud -o vault-tui ./cmd/vault-tui
Quick start
./vault-tui # launches the TUI (same as `./vault-tui ui`)
On first run, if no config file exists and you're at an interactive terminal, vault-tui walks
you through creating one (address, namespace, TLS, auth method) and writes it to
~/.config/vault-tui/config.yaml. Nothing about your environment is hard-coded into the
tool — you can also skip the wizard entirely and drive everything from flags/env vars, or run
vault-tui config init at any time to (re-)run it explicitly.
If you already have a valid Vault CLI session (~/.vault-token, or whatever token_helper
your ~/.vault config points at), vault-tui picks it up automatically — no login needed.
Auth methods
| Method | Notes |
|---|---|
token |
Validates an existing token (VAULT_TOKEN, --token, or your saved ~/.vault-token) |
oidc |
Opens a browser, runs a local callback listener (default http://localhost:8250/oidc/callback, same as the Vault CLI) |
userpass, ldap, okta, radius |
Username/password-shaped logins; Okta supports TOTP and best-effort Okta Verify push polling |
approle |
role_id / secret_id |
github |
Personal access token |
jwt |
Role + JWT |
kubernetes |
Reads the projected service-account token from disk |
cert |
TLS client-certificate auth (configure tls.client_cert/tls.client_key on the profile) |
aws, azure, gcp |
Behind -tags cloud (see above) |
Every method works identically from the TUI, from vault-tui login -method=<name> [key=value ...],
and from a profile's auth: config block — the same Method/Field declarations drive the
TUI form, the CLI prompts, and env-var lookup (VAULT_TUI_AUTH_<FIELD> plus each field's own
fallback, e.g. VAULT_PASSWORD).
CLI commands
vault-tui launch the TUI (default)
vault-tui login [-method=...] authenticate and save the resulting token
vault-tui logout [--revoke] forget (optionally revoke) the saved token
vault-tui status show the resolved profile, address, and token info
vault-tui list <path> list secrets under a path
vault-tui read <path> read a secret
vault-tui write <path> k=v ... create/update a secret (needs --write)
vault-tui delete <path> delete/destroy a secret (needs --write)
vault-tui config init|show|path manage the config file
Everything defaults to read-only. Pass --write (or set read_only: false in config) to
allow write/delete, and the same flag applies inside the TUI (write-capable keys are
disabled and hidden from help when read-only).
Configuration
Discovery order: --config flag → VAULT_TUI_CONFIG env → ~/.config/vault-tui/config.yaml
(XDG-aware). Every connection setting resolves through the same precedence:
CLI flag > env var (VAULT_ADDR, VAULT_NAMESPACE, VAULT_CACERT, ...) > profile > defaults block > builtin
vault-tui status / config show display the effective value; multi-profile setups that
want to ignore a stray VAULT_ADDR in the shell can set ignore_env: true on a profile, or
pass --no-env for one run.
Example config.yaml
version: 1
current_profile: prod
ui:
appearance: auto # auto | dark | light
confirm_destructive: true
mask_values: true
require_cas: true
defaults:
client:
timeout: 60s
max_retries: 2
token:
storage: vault-cli # vault-cli | profile | none
auto_renew: true
revoke_on_logout: false
profiles:
prod:
address: https://vault.example.com:8200
namespace: admin/platform
auth:
method: oidc
mount: oidc
params:
role: engineering
oidc:
port: 8250
callback_host: localhost
staging:
address: https://vault-staging.example.com:8200
ignore_env: true # a stray VAULT_ADDR must not hijack this profile
auth:
method: oidc
oidc:
port: 8251 # so prod and staging logins can run concurrently
token:
storage: profile # its own file, doesn't touch ~/.vault-token
ci:
address: https://vault.example.com:8200
auth:
method: approle
params:
role_id: 8c3a9b0e-4f21-4a0d-9d0c-1b2c3d4e5f60
# secret_id intentionally absent — supply via VAULT_TUI_AUTH_SECRET_ID
token:
storage: none # never persist a CI token to disk
keys:
quit: ["q", "ctrl+q"] # override any default binding
down: ["j", "down", "ctrl+n"]
theme:
border_style: rounded # rounded | normal | thick | double | ascii | hidden
colors:
accent: {light: "#5A32B0", dark: "#B39DFF"}
error: "#FF6B6B" # single value = same in light and dark
Token storage
vault-cli(default): shares~/.vault-token— or whatever externaltoken_helperyour~/.vaultfile configures — with the real Vault CLI. Logging in via either tool updates the token the other one sees.profile: its own file under$XDG_STATE_HOME/vault-tui/tokens/<profile>.token, for running multiple profiles/namespaces side by side without clobbering each other.none: never persisted (CI, scripted one-shots).
Keybindings & theme
keys: rebinds any action by name (unknown names or colliding bindings fail at startup, not
at the moment you press the key); theme: recolors everything via named colors that accept
either a single hex value or a {light, dark} pair. theme.mask_char picks the character
secret values are masked with (default •). --no-color/NO_COLOR fall back to a monochrome
palette that carries hierarchy through weight instead of color. See the example above and
internal/ui/keys/keymap.go / internal/ui/theme/theme.go for the full action/color lists.
Default TUI keybindings
j/k or arrows to move, enter/l to open, esc/h to go back, / to filter a list,
? for the full keybinding overlay, q/ctrl+c to quit. p switches profiles (when more
than one is configured) and ctrl+l logs out — both available from anywhere.
On a secret: s shows/hides the selected value, S all of them, y copies the selected
value (auto-cleared from the clipboard after ui.clipboard_clear_after, if set), e edits,
d/D soft-delete/destroy, V opens version history. On version history: enter views
that version, u undeletes it, R rolls back to it (creates a new version with its data).
Destructive actions render as a centered confirmation overlay, and irreversible ones
(destroy, non-KV-v2 delete) require typing the path back to confirm.
The TUI adapts its layout to the terminal size: a side panel with mount/secret details appears once the terminal is wide enough, and chrome (badge, pills, help text) simplifies progressively as it narrows; below roughly 44×12 cells it shows a plain "too small" notice instead of a garbled layout.
Architecture
internal/config YAML schema, precedence engine (flag > env > profile > defaults > builtin)
internal/vault api.Client wrapper, KV v1/v2 path mapping, mount discovery
internal/token token resolution/storage (vault-cli compatible), lookup, auto-renew
internal/auth Method/Field abstraction + every login method (incl. OIDC in auth/oidc)
internal/cli urfave/cli/v3 commands — the headless surface
internal/ui Bubble Tea v2 TUI, built entirely on internal/vault + internal/auth
internal/vault and internal/auth never import a TUI toolkit; internal/ui never imports
hashicorp/vault/api directly. The CLI and TUI are two front ends over the identical
Service/Method abstractions, which is what makes vault-tui login -method=x and picking
the same method in the TUI behave identically.
Mount discovery
sys/mounts requires broad permissions and commonly 403s for ordinary tokens. vault-tui
tries it first (richer detail when it works) and falls back to sys/internal/ui/mounts —
the same endpoint the Vault web UI uses, scoped to what your token can actually see.
Known limitations
- Okta Verify push-number display is best-effort: the
verify/<nonce>polling endpoint isn't part of Vault's documented public API, so a poll failure is silently ignored and the login falls back to waiting on the original request.
A note on how this was built
This tool was written almost entirely by Claude (Anthropic) as a side project scratch-my-own-itch — avoiding the Vault web UI from inside WSL. I've reviewed and tested it for my own use, but I have no interest in maintaining it as a hand-written codebase and don't plan to write code in this repo myself. I'll happily look at and merge pull requests, but there's no support and no warranty of any kind — use at your own risk.