Add agentvault with a seed-outpost subcommand
Interactive agents are classifier-blocked from plumbing credentials through a shell, so seeding an Authentik outpost token into Vault KV needs to happen inside one binary invocation that never exposes the secret. - Add cmd/agentvault, a fourth CLI sharing the agentpr Vault AppRole login (role_id only, VAULT_ADDR/AGENT_APPROLE_ROLE_ID defaults unchanged). - Add `agentvault seed-outpost`: read the Authentik API token from kv/service/authentik/agent-api-token (field `token`, falling back to `api_token`), exact-match the outpost by name via the instances search, fetch its key from /api/v3/core/tokens/<identifier>/view_key/ and write it to --dest-path under --dest-key. - Print only the outpost name, token identifier, dest path and new KV version; keep secret material out of results, errors and logs. - Distinguish the failure stages (login, KV read denied, outpost missing, view_key, KV write denied) with ErrVaultDenied/ErrVaultNotFound/ ErrOutpostNotFound sentinels and actionable messages. - Add internal/agent vaultkv.go (AppRole-authenticated KV-v2 client) and authentik.go (outpost search + view_key) for reuse by future flows. - Cover the happy path, idempotent re-run, field fallback and every failure mode with httptest servers, including a leak check on error strings. - Wire agentvault into the Makefile, build-rpm.sh, nfpm contents, release cross-builds/assets, README and AGENTS.md.
This commit is contained in:
@@ -29,6 +29,7 @@ parsing, watch-state comparison, git worktree helpers).
|
||||
cmd/agentpr/main.go # agentpr CLI (pr create / pr comment / whoami)
|
||||
cmd/watchpr/main.go # watchpr CLI (poll + meaningful-change exit)
|
||||
cmd/agentws/main.go # agentws CLI (new / list / rm / clean / token / credential)
|
||||
cmd/agentvault/main.go # agentvault CLI (seed-outpost)
|
||||
internal/agent/ # shared plumbing:
|
||||
token.go # env config + in-process Gitea-token cache
|
||||
vault.go # AppRole login + read gitea/creds/unkin-agent
|
||||
@@ -36,6 +37,9 @@ internal/agent/ # shared plumbing:
|
||||
parse.go # owner/repo#N and owner/repo parsing
|
||||
watch.go # PRState snapshot + MeaningfulChange comparison
|
||||
git.go # git worktree/clone/fetch helpers (os/exec, no go-git)
|
||||
vaultkv.go # AppRole-authenticated Vault client + KV-v2 read/write
|
||||
authentik.go # Authentik REST client (outpost search, token view_key)
|
||||
seedoutpost.go # seed-outpost flow (Authentik token -> Vault KV)
|
||||
go.mod # module git.unkin.net/unkin/agent-tools
|
||||
Makefile # build / test / lint / completions / rpm / version-bump
|
||||
packaging/nfpm.yaml # nfpm spec (envsubst-templated) for the RPM (all binaries)
|
||||
@@ -66,6 +70,7 @@ Config via env (all have defaults):
|
||||
| `AGENTWS_SRC_ROOT` | `~/src/prodenv` | agentws source-of-truth checkout root |
|
||||
| `AGENTWS_ROOT` | `~/.cache/agentws` | agentws worktree root |
|
||||
| `AGENTWS_OWNER` | `unkin` | Gitea org that owns agentws-managed repos |
|
||||
| `AUTHENTIK_URL` | `https://identity.k8s.syd1.au.unkin.net` | Authentik base URL (`agentvault`) |
|
||||
|
||||
### agentws git auth (ephemeral credential helper)
|
||||
|
||||
@@ -85,7 +90,7 @@ to keep the default branch current.
|
||||
## Build
|
||||
|
||||
```bash
|
||||
make build # -> dist/agentpr, dist/watchpr, dist/agentws (CGO disabled, static)
|
||||
make build # -> dist/agentpr, dist/watchpr, dist/agentws, dist/agentvault (CGO disabled, static)
|
||||
```
|
||||
|
||||
Requires Go 1.21+. Dependency: `github.com/spf13/cobra` (CLI).
|
||||
@@ -97,8 +102,8 @@ make rpm # build all binaries + package into dist/*.rpm via nfpm
|
||||
```
|
||||
|
||||
`scripts/build-rpm.sh` generates bash/zsh/fish completions from the built
|
||||
binaries and bundles them alongside `/usr/bin/agentpr`, `/usr/bin/watchpr` and
|
||||
`/usr/bin/agentws`.
|
||||
binaries and bundles them alongside `/usr/bin/agentpr`, `/usr/bin/watchpr`,
|
||||
`/usr/bin/agentws` and `/usr/bin/agentvault`.
|
||||
On a `v*` tag the release pipeline builds the RPM and `PUT`s it to the
|
||||
artifactapi `rpm-internal` repo, then cuts a Gitea release.
|
||||
|
||||
@@ -120,10 +125,30 @@ alerting transitions), request-body construction, and the Vault+Gitea client
|
||||
against `httptest` servers (fake AppRole login + gitea creds + PR create /
|
||||
comment / whoami / status). No live Vault/Gitea access is required for tests.
|
||||
|
||||
## agentvault seed-outpost
|
||||
|
||||
`agentvault seed-outpost --outpost <name> --dest-path <kv/path>` does the whole
|
||||
flow in-process:
|
||||
|
||||
1. AppRole login (shared `approleLogin`), then KV-v2 read of
|
||||
`kv/service/authentik/agent-api-token` (field `token`, falling back to
|
||||
`api_token`).
|
||||
2. `GET /api/v3/outposts/instances/?search=<name>` — Authentik's `search` is a
|
||||
substring match, so the exact `name` is re-checked client-side.
|
||||
3. `GET /api/v3/core/tokens/<token_identifier>/view_key/` for the key.
|
||||
4. KV-v2 write to `--dest-path` under `--dest-key` (default `token`).
|
||||
|
||||
Only the outpost name, token identifier, dest path and new KV version are
|
||||
printed. Errors are wrapped per stage (login / read denied / outpost missing /
|
||||
view_key / write denied) via the `ErrVaultDenied`, `ErrVaultNotFound` and
|
||||
`ErrOutpostNotFound` sentinels.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `watchpr` exits 0 with no output changes on `--once` (just prints state).
|
||||
- The token cache is process-wide (`sync.Once`); tests call the unexported
|
||||
`fetchGiteaToken` to avoid it.
|
||||
- `agentvault` never puts a secret in an error string: Vault decode failures and
|
||||
Authentik `view_key` responses are reported without their bodies.
|
||||
- CI "combined status" comes from `/commits/{sha}/status`; an empty head SHA
|
||||
yields an empty state without an API call.
|
||||
|
||||
Reference in New Issue
Block a user