137 lines
6.7 KiB
Markdown
137 lines
6.7 KiB
Markdown
---
|
|
name: vault
|
|
description: "The ONE canonical way to use the ClaudeTools SOPS+age secret vault — read, store, update, and verify credentials. Use this whenever a task involves a password, API key, token, secret, connection string, SSH key, or any credential: retrieving one to use it, storing a newly created/discovered one, or checking what's vaulted. Stops the per-session improvising (raw sops, guessed paths, VAULT_ROOT_ENV hacks, plaintext-field mistakes). Triggers: vault, store/save a secret, add to vault, get the password/api key for X, where is the credential for X, sops, encrypt this secret, decrypt, rotate a credential, 1password fallback, vault a new key."
|
|
---
|
|
|
|
# Vault — one consistent way to handle secrets
|
|
|
|
Vaulting should be identical every time. It is. This skill is the single source of truth so no
|
|
session has to re-derive it. Two tools, one rule:
|
|
|
|
- **Reads / search / list** → the canonical `vault.sh` (auto-resolves the vault from identity.json).
|
|
- **Create / update / verify (non-interactive)** → `vault-helper.sh` in this skill. (The base
|
|
`vault.sh add`/`edit` are interactive `$EDITOR` flows Claude can't drive — do NOT use them; do
|
|
NOT fall back to raw `sops` with hand-built paths.)
|
|
|
|
## THE RULE (read this first)
|
|
|
|
1. **Never paste a secret into chat, a ticket, a commit message, or a coord/Discord channel.**
|
|
Point people at the vault path instead (teammates with vault access decrypt it themselves).
|
|
2. **Never write a secret to a field outside `credentials:`.** The `.sops.yaml` only encrypts keys
|
|
named `credentials | password | secret | api_key | token | pre_shared_key | notes | content`.
|
|
A secret placed anywhere else commits in **plaintext**. Always put secrets under `credentials:`.
|
|
3. **Never hand-roll the vault path or `sops` command.** Use the two scripts below — they resolve
|
|
the vault root the same way on every machine.
|
|
4. **Finish the job:** create/update → verify it's encrypted → publish (sync). Don't stop at
|
|
"it's on disk, you push it."
|
|
|
|
## Read a secret (canonical)
|
|
|
|
```bash
|
|
# whole entry (decrypted)
|
|
bash .claude/scripts/vault.sh get <path>
|
|
# one field, dot-notation (e.g. credentials.api_key, credentials.admin.password)
|
|
bash .claude/scripts/vault.sh get-field <path> credentials.api_key
|
|
# find where something lives
|
|
bash .claude/scripts/vault.sh search <query>
|
|
bash .claude/scripts/vault.sh list [subdir]
|
|
```
|
|
|
|
`<path>` is relative to the vault root, with or without the `.sops.yaml` suffix
|
|
(e.g. `clients/kittle/gururmm-site-main` or `msp-tools/computerguru-user-manager.sops.yaml`).
|
|
|
|
The repo wrapper `.claude/scripts/vault.sh` reads `vault_path` from `.claude/identity.json` and
|
|
delegates to the real `vault.sh` in the vault repo. That is the ONLY entry point you need for
|
|
reads. (If `vault_path` is missing on a machine, fix identity.json — don't paper over it with
|
|
`VAULT_ROOT_ENV`, which is a separate remediation-tool-script quirk, not how you read the vault.)
|
|
|
|
## Store a NEW secret (non-interactive, one shot)
|
|
|
|
```bash
|
|
bash .claude/skills/vault/scripts/vault-helper.sh new <path> \
|
|
--kind <api-key|server|m365|vpn|note|generic> \
|
|
--name "Human-readable name" [--url https://...] [--tag client] [--tag service] \
|
|
--set api_key=THE_SECRET [--set username=foo] [--set password=bar]
|
|
```
|
|
|
|
This writes the plaintext template (metadata at top level, every `--set` under `credentials:`),
|
|
encrypts it in place with `sops`, verifies the round-trip, and tells you to publish. It refuses if
|
|
the file already exists.
|
|
|
|
## Update / add a field on an existing entry
|
|
|
|
```bash
|
|
bash .claude/skills/vault/scripts/vault-helper.sh set <path> --set password=NEW_VALUE
|
|
```
|
|
|
|
Decrypts, merges the field(s) into `credentials:`, re-encrypts, verifies. Use this instead of
|
|
`vault edit` (which needs an interactive editor).
|
|
|
|
## Verify (always, after any write — and before any commit)
|
|
|
|
```bash
|
|
bash .claude/skills/vault/scripts/vault-helper.sh verify <path> # one entry: encrypted + decrypts
|
|
bash .claude/skills/vault/scripts/vault-helper.sh check [subdir] # scan for ANY plaintext *.sops.yaml
|
|
```
|
|
|
|
`check` is the safety net against the plaintext footgun — run `check` over a dir (or the whole
|
|
vault) before committing if you hand-edited anything.
|
|
|
|
## Publish (the last mile — do it yourself)
|
|
|
|
The main sync handles the vault repo too:
|
|
|
|
```bash
|
|
bash .claude/scripts/sync.sh # Phase 6 commits + pushes the vault repo
|
|
```
|
|
|
|
(Equivalent: `cd <vault_path> && git add -A && git commit -m "..." && git push`.) Don't park
|
|
"you push it" as a task — a clean encrypted entry is routine. Only hand off if `git push` itself
|
|
fails (auth/conflict). The Windows LF→CRLF warning on the yaml is benign — SOPS integrity is over
|
|
the `ENC[...]` values, not line endings.
|
|
|
|
## Layout & file format
|
|
|
|
Vault root subdirs: `clients/<slug>/`, `msp-tools/`, `infrastructure/`, `services/`, `projects/`,
|
|
`business/`, `ssh-keys/`, `tailscale/`. Put a client credential under `clients/<slug>/`, an MSP app
|
|
under `msp-tools/`, shared infra under `infrastructure/` or `services/`.
|
|
|
|
A decrypted entry looks like:
|
|
|
|
```yaml
|
|
kind: api-key # api-key | server | m365-tenant | vpn | note | generic
|
|
name: Human-readable name
|
|
url: https://... # optional, plaintext metadata
|
|
status: active
|
|
tags: [client, service] # plaintext, searchable
|
|
credentials: # <-- EVERYTHING secret goes here (this whole block is encrypted)
|
|
api_key: "..."
|
|
username: "..."
|
|
password: "..."
|
|
notes: "" # encrypted too
|
|
```
|
|
|
|
Plaintext metadata (kind/name/url/tags/status and arbitrary non-secret structure like `client:` /
|
|
`site:`) stays readable so `search` works. Only `credentials`/`notes` (and the other regex keys)
|
|
are encrypted.
|
|
|
|
## 1Password fallback
|
|
|
|
The SOPS vault is primary. 1Password is the fallback when a secret isn't in SOPS or for
|
|
human-shared items — use the `1password` skill / `op` CLI for that. If you store something new and
|
|
it belongs in the team flow, prefer the SOPS vault so it syncs with the repo.
|
|
|
|
## Gotchas (already handled — don't re-discover them)
|
|
|
|
- **Interactive `vault edit` / `vault add`** don't work in this harness ($EDITOR). Use
|
|
`vault-helper.sh set` / `new` instead.
|
|
- **yq blocked on Windows (WDAC/Device Guard)** — `vault.sh` auto-falls back to a bundled Python
|
|
YAML parser. Nothing to do.
|
|
- **`VAULT_ROOT_ENV`** is only a workaround for the *remediation-tool* scripts mis-resolving their
|
|
root; it is NOT the vault access pattern. For vault work use the two scripts here.
|
|
- **Encrypted-field regex** is the one real footgun — secrets must be under `credentials:` (or a
|
|
top-level `password`/`api_key`/`token`/`secret`/`pre_shared_key`/`notes`/`content`). `verify`
|
|
catches a miss.
|
|
- The vault repo has its own pre-commit `harness-guard`/hook that warns on plaintext; `check` is
|
|
your proactive version of the same.
|