# vault-tui A terminal UI and CLI for HashiCorp Vault, built on [Bubble Tea v2](https://github.com/charmbracelet/bubbletea) and [urfave/cli v3](https://github.com/urfave/cli). 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 ```sh 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: ```sh go build -o vault-tui ./cmd/vault-tui go build -tags cloud -o vault-tui ./cmd/vault-tui ``` ## Quick start ```sh ./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= [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_` 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 list secrets under a path vault-tui read read a secret vault-tui write k=v ... create/update a secret (needs --write) vault-tui delete 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 ```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 external `token_helper` your `~/.vault` file 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/.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/` 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.