From 5879fbd2d848544af4948c103ed67ad2318e4441 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 30 Aug 2026 09:29:26 +1000 Subject: [PATCH] Add ephemeral Gitea creds for repospawner (#142) ## 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: https://git.unkin.net/unkin/terraform-vault/pulls/142 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- .../k8s/au/syd1/repospawner.yaml | 7 +++++++ .../gitea/repospawner.yaml | 19 +++++++++++++++++++ policies/gitea/creds/repospawner.yaml | 16 ++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 config/auth_kubernetes_role/k8s/au/syd1/repospawner.yaml create mode 100644 config/gitea_secret_backend_role/gitea/repospawner.yaml create mode 100644 policies/gitea/creds/repospawner.yaml diff --git a/config/auth_kubernetes_role/k8s/au/syd1/repospawner.yaml b/config/auth_kubernetes_role/k8s/au/syd1/repospawner.yaml new file mode 100644 index 0000000..baf8bd7 --- /dev/null +++ b/config/auth_kubernetes_role/k8s/au/syd1/repospawner.yaml @@ -0,0 +1,7 @@ +bound_service_account_names: + - repospawner +bound_service_account_namespaces: + - repospawner +token_ttl: 600 +token_max_ttl: 600 +audience: vault diff --git a/config/gitea_secret_backend_role/gitea/repospawner.yaml b/config/gitea_secret_backend_role/gitea/repospawner.yaml new file mode 100644 index 0000000..ffcbfc1 --- /dev/null +++ b/config/gitea_secret_backend_role/gitea/repospawner.yaml @@ -0,0 +1,19 @@ +# Role minting ephemeral tokens for the repospawner service user. repospawner +# creates and seeds Gitea repositories on demand, so it gets write on +# repositories (create + push) and write on issues (issue/PR comments). Read is +# implied by write. No admin/org/user-write scopes. +# read:user is required because most API clients validate the login via +# GET /api/v1/user, which 403s without it. +# Reading gitea/creds/repospawner mints a lease-bound token deleted from Gitea +# on revoke/expiry. Consumed by the repospawner pods via Kubernetes auth (see +# policies/gitea/creds/repospawner.yaml) -- the agents AppRole is CIDR-bound to +# Ben's workstation and cannot be used from in-cluster. +--- +username: repospawner +scopes: + - write:repository + - write:issue + - read:user +token_name_prefix: vault-repospawner +ttl: 3600 # 1h +max_ttl: 14400 # 4h diff --git a/policies/gitea/creds/repospawner.yaml b/policies/gitea/creds/repospawner.yaml new file mode 100644 index 0000000..49352c6 --- /dev/null +++ b/policies/gitea/creds/repospawner.yaml @@ -0,0 +1,16 @@ +# Lets the repospawner pods mint ephemeral Gitea tokens for the repospawner +# service user, so the service authenticates to git.unkin.net as its own +# least-privilege identity. Reading gitea/creds/repospawner returns a +# lease-bound token scoped by the role (write:repository, write:issue, +# read:user -- never merge/admin). +# Kubernetes auth only: repospawner runs in-cluster and the agents AppRole is +# CIDR-bound to Ben's workstation. +--- +rules: + - path: "gitea/creds/repospawner" + capabilities: + - read + +auth: + k8s/au/syd1: + - repospawner