From b371ae6eee9977b267b12fa9bbbe1b3a183abb60 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 27 Jun 2026 08:16:52 +0000 Subject: [PATCH] homelab vault: install bw system-wide + onboarding runbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two remaining gaps to let non-admins (emo) use `homelab vault`: - setup-devvm.sh installed `@bitwarden/cli` only when `command -v bw` failed, which an admin's own ~/.local/bin/bw satisfied — so the system-wide copy was never installed and non-admins had no `bw` backend. Install to the npm /usr prefix and guard on the system path (/usr/bin/bw) instead. - Add docs/runbooks/homelab-vault-onboarding.md (per-user setup, the shared Organization/Collection flow for sharing passwords, admin deploy + verification, security model) and repoint the two code comments that cited a design-spec path which never existed. Co-Authored-By: Claude Opus 4.8 --- cli/cmd_vault.go | 4 +- docs/runbooks/homelab-vault-onboarding.md | 121 ++++++++++++++++++++++ scripts/workstation/setup-devvm.sh | 13 ++- 3 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 docs/runbooks/homelab-vault-onboarding.md diff --git a/cli/cmd_vault.go b/cli/cmd_vault.go index d880cab1..6d35ba76 100644 --- a/cli/cmd_vault.go +++ b/cli/cmd_vault.go @@ -15,7 +15,7 @@ import ( // Identity is the kernel UID; per-user creds live in that user's isolated Vault // path (secret/workstation/claude-users/) read via their scoped token, and // decryption is done by the official `bw` CLI. See -// docs/superpowers/specs/2026-06-24-homelab-vault-design.md. +// docs/runbooks/homelab-vault-onboarding.md. func vaultCommands() []Command { return []Command{ {Path: []string{"vault", "setup"}, Tier: TierWrite, @@ -51,7 +51,7 @@ func vaultHelp() string { homelab vault lock lock / log out the local bw session Creds live only in your own Vault path; the admin never sees them. Identity is -your unix UID. Security model: docs/superpowers/specs/2026-06-24-homelab-vault-design.md +your unix UID. Security model: docs/runbooks/homelab-vault-onboarding.md (note: anything running as your user can decrypt your vault — the accepted no-HITL trade). ` } diff --git a/docs/runbooks/homelab-vault-onboarding.md b/docs/runbooks/homelab-vault-onboarding.md new file mode 100644 index 00000000..61d323ab --- /dev/null +++ b/docs/runbooks/homelab-vault-onboarding.md @@ -0,0 +1,121 @@ +# `homelab vault` onboarding (per-user Vaultwarden access) + +## 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/` and are decrypted as that OS user — +the admin never sees them. + +```text +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) +homelab vault get [--field password|username|uri|notes|totp] [--json] +homelab vault code current TOTP code +homelab vault lock lock / log out the local bw session +``` + +## How auth works (why a non-admin can use it) + +`homelab vault` runs `vault` as the calling user. It resolves a Vault token in +this order (`ensureVaultToken`, `cli/cmd_vault.go`): + +1. an explicit `$VAULT_TOKEN`, then +2. a native `~/.vault-token` (what admins carry), then +3. the per-user **scoped token** that `claude-auth-sync` maintains at + `~/.config/claude-auth-sync/vault-token` (policy `workstation-claude-`). + +That scoped policy grants exactly `create`/`read`/`update` on the user's own +`secret/workstation/claude-users/` path — no `patch` capability — so the +tool writes with `vault kv patch -method=rw` (read-modify-write), falling back to +`kv put` only when the path does not exist yet. This preserves the +`claude_ai_oauth_json` key that [claude-auth-sync](claude-auth-renew-workstation.md) +co-locates there. (Both bugs that previously made this admin-only were fixed +2026-06-27.) + +## Prerequisites (per user) + +- The user is in `scripts/workstation/roster.yaml` and the **vault** stack has + been applied → their `workstation-claude-` policy exists. +- The user's workstation was provisioned (`setup-devvm.sh`) → their scoped Vault + token exists at `~/.config/claude-auth-sync/vault-token`. +- `bw` is installed **system-wide** at `/usr/bin/bw` (see below). +- The user has a Vaultwarden account at `https://vaultwarden.viktorbarzin.me` + (self-service signup is open; admin panel is disabled). + +## One-time admin steps (devvm) + +`bw` must be system-wide so every user resolves it (it is a Node script, and +`node` is already system-wide at `/usr/bin/node`). `setup-devvm.sh` installs it +to the npm `/usr` prefix; the guard checks the **system** path, not +`command -v bw` (an admin's own `~/.local/bin/bw` used to mask the system +install, leaving non-admins with no backend). To install on a running box: + +```bash +sudo npm install -g --prefix /usr "@bitwarden/cli@^2024" +bw --version # confirm /usr/bin/bw resolves +``` + +After landing a `cli/` change, rebuild the binary so users pick it up: + +```bash +sudo bash -c 'cd /home/wizard/code/infra/cli && \ + go build -ldflags "-X main.version=$(git -C /home/wizard/code/infra describe --tags --always 2>/dev/null || echo dev)" \ + -o /usr/local/bin/homelab .' +``` + +(or just re-run `scripts/workstation/setup-devvm.sh` as root, which rebuilds it.) + +## User onboarding + +The user runs these as themselves. The master password / API key are entered +interactively (never on the command line) and stored only in the user's Vault +path. + +1. In the Vaultwarden web vault → **Settings → Security → Keys → View API key**, + copy the `client_id` (`user.xxxx`) and `client_secret`. +2. Configure: + + ```bash + homelab vault setup # prompts: VW email, API client_id/secret, master password + homelab vault status # → "vault: configured, unlocked, reachable ✓" + homelab vault list # item names (own vault + any shared Collections) + ``` + +## Shared-Collection access (sharing passwords with a user) + +`homelab vault` surfaces Organization Collection items automatically once the +user's Vaultwarden account is a confirmed member. These steps are done by the +vault owner in the **Vaultwarden web UI** (they need the owner's master +password — not an infra/Terraform operation): + +1. Create or reuse an **Organization** and a **Collection** of shared logins. +2. **Invite** the user's Vaultwarden account to the Organization, granting + **"Can view"** on that Collection (least privilege). +3. The user accepts the email invite and confirms membership. +4. The user runs `homelab vault list` — the shared items now appear alongside + their own (a `homelab vault status` sync picks them up). + +## Security model (the no-HITL trade) + +Identity is the kernel UID. Anything running as the user can decrypt the user's +vault — this is the accepted trade for no-human-in-the-loop fetches. Secrets +never appear in `argv` (passed via env or stdin), core dumps are disabled, TOTP +fetches are logged to syslog/Loki, and on a TTY values go to the clipboard +(auto-clearing) rather than scrollback. The admin's Vault token is never used by +a non-admin: each user authenticates with their own scoped token. + +## Verification + +```bash +# the scoped token carries the right policy +VAULT_TOKEN="$(sudo cat /home//.config/claude-auth-sync/vault-token)" \ + vault token lookup -format=json | jq '.data.display_name, .data.policies' +# → "token-devvm-claude-auth-", [..., "workstation-claude-"] + +sudo -u -i bw --version # /usr/bin/bw resolves for the user +sudo -u -i homelab vault status +``` diff --git a/scripts/workstation/setup-devvm.sh b/scripts/workstation/setup-devvm.sh index 2969b803..02bd9257 100755 --- a/scripts/workstation/setup-devvm.sh +++ b/scripts/workstation/setup-devvm.sh @@ -72,11 +72,14 @@ if [[ -n "$want_t3" && "$(t3 --version 2>/dev/null | awk '{print $NF}' | sed 's/ fi # 2c) Bitwarden CLI — backs `homelab vault` (per-user no-HITL Vaultwarden access). -# npm-global so every user's PATH resolves it. Pinned major; best-effort (a -# failure only disables `homelab vault`, nothing else on the box). -if ! command -v bw >/dev/null; then - log "npm: installing @bitwarden/cli (homelab vault backend)" - npm install -g "@bitwarden/cli@^2024" >/dev/null 2>&1 || log "WARN: @bitwarden/cli install failed; homelab vault unavailable" +# Install SYSTEM-WIDE (npm prefix /usr → /usr/bin/bw) so EVERY user's PATH +# resolves it. The guard tests the SYSTEM path, NOT `command -v bw`: the +# latter is satisfied by an admin's own ~/.local/bin/bw and would skip the +# system install, leaving non-admins (emo, anca, …) with no backend. Pinned +# major; best-effort (a failure only disables `homelab vault`). +if [ ! -x /usr/bin/bw ] && [ ! -x /usr/local/bin/bw ]; then + log "npm: installing @bitwarden/cli system-wide (homelab vault backend)" + npm install -g --prefix /usr "@bitwarden/cli@^2024" >/dev/null 2>&1 || log "WARN: @bitwarden/cli install failed; homelab vault unavailable" fi # 3) kubelogin (kubectl oidc-login) system-wide — NOT the apt 'kubelogin' (= Azure tool).