homelab vault: install bw system-wide + onboarding runbook

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 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-27 08:16:52 +00:00
parent 51dc5d031c
commit b371ae6eee
3 changed files with 131 additions and 7 deletions

View file

@ -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/<user>) 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).
`
}

View file

@ -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/<os-user>` 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 <name> [--field password|username|uri|notes|totp] [--json]
homelab vault code <name> 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-<user>`).
That scoped policy grants exactly `create`/`read`/`update` on the user's own
`secret/workstation/claude-users/<user>` 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-<user>` 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/<user>/.config/claude-auth-sync/vault-token)" \
vault token lookup -format=json | jq '.data.display_name, .data.policies'
# → "token-devvm-claude-auth-<user>", [..., "workstation-claude-<user>"]
sudo -u <user> -i bw --version # /usr/bin/bw resolves for the user
sudo -u <user> -i homelab vault status
```

View file

@ -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).