Files
vault-tui/README.md
T
f.weber ae30ba1240 feat(vault-tui): implement KV client and service for managing secrets
- 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.
2026-08-14 11:09:03 +02:00

10 KiB
Raw Blame History

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 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/<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.