## Why
`repospawner` creates and seeds Gitea repositories on demand from in-cluster. It needs a Gitea token, and it should not carry a static one -- it gets ephemeral, lease-bound creds like every other service. The `agents` AppRole is CIDR-bound to Ben's workstation, so pods authenticate via Kubernetes auth instead.
## How
- `config/gitea_secret_backend_role/gitea/repospawner.yaml` -- gitea engine role for the `repospawner` user. Scopes `write:repository`, `write:issue`, `read:user` (`read:user` is mandatory: clients validate the login via `GET /api/v1/user`, which 403s without it). ttl 1h / max_ttl 4h. Mirrors `unkin-agent.yaml`.
- `config/auth_kubernetes_role/k8s/au/syd1/repospawner.yaml` -- k8s auth role bound to serviceaccount `repospawner` in namespace `repospawner`, 600s ttl, `audience: vault` (VSO/projected-token flavor, same as `media-apps` / `logging_logarchiver`).
- `policies/gitea/creds/repospawner.yaml` -- `read` on `gitea/creds/repospawner`, bound to `k8s/au/syd1: [repospawner]` only. Deliberately **not** bound to the `agents` AppRole: the service runs in-cluster only.
## Depends on
The terraform-git PR that creates the `repospawner` Gitea user. The engine role cannot mint creds until the user exists -- **merge that one first**.
Reviewed-on: #142
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
Why: AI coding agents authenticate to Gitea as Ben using Ben's token. With the unkin-agent identity now in place (terraform-git PR #59), the agents AppRole should issue that account's tokens directly so agent commits and PRs are attributable and carry only least-privilege scopes.
How:
- add a gitea secrets-engine role minting ephemeral tokens for unkin-agent scoped to write:repository, write:issue, read:user — push branches and open PRs, never merge or administer
- add a policy granting read on gitea/creds/unkin-agent, bound to the agents AppRole, mirroring the agent-* Kubernetes creds bindings
Depends on terraform-git PR #59: the unkin-agent Gitea account must exist before minted tokens work. The vault-plugin-secrets-gitea engine is already live (plugin v0.1.0 registered, gitea mount configured), so no engine/plugin change is needed here.
Reviewed-on: #114
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
End-to-end verification of the freshly-applied gitea engine (mint → API call → revoke) surfaced that tokens without read:user get 403 from GET /api/v1/user — the endpoint tea and most Gitea API clients use to validate a login. teabot's personalities would fail their auth check with the current scope sets, while in-scope calls (repo/issue) already work and lease revocation correctly kills tokens (verified 401 after revoke).
- add read:user to the teabot-implementer role scopes
- add read:user to the teabot-reviewer role scopes
Reviewed-on: #105
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
## Why
teabot's implementer and reviewer bot users should mint **ephemeral, scoped Gitea tokens** on demand rather than holding standing personal access tokens — Gitea tokens never expire on their own, so a leak lasts until someone notices. This registers and mounts the new `vault-plugin-secrets-gitea` engine (released v0.1.0) and declares its roles, mirroring the rancher engine wiring.
## Change
- Register the plugin in the catalog (`config/plugins/vault-plugin-secrets-gitea.yaml`), pinned to the released v0.1.0 binary `sha256 8f67fbc216effada5fd7399888a710b62fad83be0b31761a439e7dec3d56509b` (sha256 of `/opt/openbao-plugins/vault-plugin-secrets-gitea` from the released `openbao-plugin-secrets-gitea-0.1.0` RPM).
- Add `gitea_secret_backend` + `gitea_secret_backend_role` modules and wire them through `config.hcl`, `environments/au/syd1/terragrunt.hcl`, and `modules/vault_cluster` variables/main, using the `giteavaultsecret` provider from the `terraform-unkin` registry (v0.1.0).
- Mount the engine at `gitea/` against `https://git.unkin.net`; seeded site-admin credentials are read from KV (`service/vault/au/syd1/secret_backend/gitea/config`, keys `admin_username`/`admin_password`) — not stored in git.
- **The seed is consumed create-only**: `lifecycle ignore_changes` on `admin_username`/`admin_password` means the engine reads the KV seed only when first creating `gitea/config`. After `rotate-root` diverges the live password from the seed, a later apply never pushes the stale seed back.
- Add roles with conservative, minimal scopes (write: implies read:):
- `teabot-implementer` — `write:repository`, `write:issue` (clone/push, open PRs, comment).
- `teabot-reviewer` — `read:repository`, `write:issue` (read diffs, post PR reviews/comments).
- TTLs: `ttl` 1h / `max_ttl` 4h on both roles.
## The site-admin bot + KV seed are now provisioned by Terraform (no manual gap)
Per Ben's review, creating the site-admin bot and seeding its credential is no longer a manual step:
- **terraform-git #46** creates the `gitea-vault-admin` site-admin bot and writes its generated password **once** to `kv/service/vault/au/syd1/secret_backend/gitea/config` (create-only KV write; never updated).
- **terraform-vault #102** grants terraform-git write access to that KV path.
## Ordering (merge + apply)
1. **puppet-prod #498** — installs the plugin binary on the vault nodes (Puppet must run).
2. **terraform-vault #100** (`benvin/gitea-deployer-access`) — deployer access to the gitea mount.
3. **terraform-vault #102** (`benvin/gitea-kv-writer`) — terraform-git KV write grant.
4. **terraform-git #46** (`benvin/gitea-vault-admin`) — creates the bot + seeds KV.
5. **This PR** — mounts the engine (reads the seed) and declares roles.
Files here are disjoint from #100 and #102 (no conflict).
**CI note:** the plan for this PR may hard-fail in CI if the plugin isn't yet registered/installed or the KV seed isn't present in the plan's target. If CI plan fails for that ordering reason, that is expected — do not force; apply only once steps 1–4 are live.
## Remaining manual step (one, ordered)
After this PR's first apply, run `vault write -f gitea/config/rotate-root` so the standing seed password is replaced by one only Vault holds. (On future binary upgrades, bump the RPM version in puppet-prod and the catalog `sha256` here together, then `vault write sys/plugins/reload/backend plugin=vault-plugin-secrets-gitea`.)
https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
Reviewed-on: #101
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>