vault: distinguish Vaultwarden vs HashiCorp Vault, add vault kv

`homelab vault` only spoke to Vaultwarden (the password manager), but the
name reads as HashiCorp Vault (the infra secrets store — actually OpenBao
here). Make the two unmistakable and support both.

Distinction (no breakage — the existing Vaultwarden verbs are unchanged):
- bare `homelab vault` help now LEADS with the two-stores split;
- every verb summary is tagged `[vaultwarden]` or `[hashicorp-vault]`;
- HashiCorp Vault/OpenBao lives under a clearly-named `vault kv` group.

New `vault kv` (HashiCorp Vault / OpenBao, the secret/… KV store):
- `kv get <path> [--field K]` — read; --field → one value (TTY-aware
  clipboard/stdout), no field → full secret JSON (refuses a bare TTY).
- `kv list <path>` — list sub-paths (no values).
- `kv put <path> <key>` — write one key; value via stdin (piped or
  no-echo prompt, never argv); creates the path or merges (never
  clobbers siblings; uses kv patch -method=rw so no `patch` cap needed).

Critical: `kv` uses the caller's OWN Vault token (OIDC ~/.vault-token /
$VAULT_TOKEN), NOT the per-user scoped Vaultwarden token (bound only to
claude-users/<user>, which would 403 elsewhere) — handlers set VAULT_ADDR
but never inject the scoped token. Access is whatever the policy grants.

Logic in cmd_vault_kv.go (pure cores extractKVData/parseKVList/arg
builders/kvGet/List/Put; file header documents the credential split).
CLI v0.11.0. Tests: no value in put argv, create-then-merge, KV-v2
envelope strip, help names both systems. Verified e2e against live Vault
(read key-names-only + a scratch put/merge/cleanup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-28 11:09:33 +00:00
parent a1cf7ccaf6
commit e03e4719ad
6 changed files with 492 additions and 21 deletions

View file

@ -1,15 +1,24 @@
# `homelab vault` onboarding (per-user Vaultwarden access)
# `homelab vault` onboarding (Vaultwarden access + `vault kv` infra secrets)
## Scope
`homelab vault` gives each devvm roster user no-HITL access to **their own**
Vaultwarden vault (and any Organization Collection shared with their account)
from the command line. It shells out to the official `bw` CLI; the user's
Vaultwarden credentials live only in their isolated Vault path
`secret/workstation/claude-users/<os-user>` and are decrypted as that OS user —
the admin never sees them.
`homelab vault` fronts **two unrelated secret stores** — the name collides, so
the command keeps them clearly separated:
- **Vaultwarden** — your personal *password manager* (logins/passwords/TOTP).
The verbs below give each devvm roster user no-HITL access to **their own**
Vaultwarden vault (and any Organization Collection shared with their account).
It shells out to the official `bw` CLI; the user's Vaultwarden credentials live
only in their isolated Vault path `secret/workstation/claude-users/<os-user>`
and are decrypted as that OS user — the admin never sees them.
- **HashiCorp Vault / OpenBao** — the homelab *infra* secrets store (the
`secret/…` KV mount at `vault.viktorbarzin.me`), under `homelab vault kv`.
These use the caller's **own** Vault token (`vault login -method=oidc`
`~/.vault-token`), **not** the scoped Vaultwarden token (which only reads the
`claude-users/<user>` path); access is whatever your Vault policy grants.
```text
# Vaultwarden (password manager)
homelab vault setup one-time: store VW email + master password + API key
homelab vault status configured / unlocked / reachable (no secrets)
homelab vault list [--search Q] item names (no secrets)
@ -17,6 +26,11 @@ homelab vault get <name> [--field password|username|uri|notes|totp] [--json]
homelab vault get <name> --all all fields (incl. custom) as JSON; pipe it (| jq)
homelab vault code <name> current TOTP code
homelab vault lock lock / log out the local bw session
# HashiCorp Vault / OpenBao (infra secrets; uses your own OIDC token)
homelab vault kv get <path> [--field K] read an infra KV secret
homelab vault kv list <path> list sub-paths
homelab vault kv put <path> <key> write one key (value via stdin; merges)
```
## How auth works (why a non-admin can use it)