56 Commits

Author SHA1 Message Date
unkin-agent a1e7029615 Add Authentik OIDC SSO as the default human login for OpenBao (#147)
ci/woodpecker/push/apply Pipeline failed
## Why

Human login to OpenBao is LDAP-only, so operators keep a second credential set outside Authentik and group membership is maintained twice.

## How

- Add `auth_oidc_backend` module: `oidc`-type JWT auth mount, Authentik discovery URL, `listing_visibility: unauth`, credentials from `kv/service/authentik/oidc-vault`.
- Add `auth_oidc_role` module: oidc role with `user_claim` email, `groups_claim` `ak_groups`, scopes `openid profile email ak_groups`, the registered redirect URIs, `bound_audiences` `[vault]`.
- Add `auth_oidc_group` module: external identity group plus group alias on the OIDC mount accessor, policies from `policy_auth_map`.
- Add config under `config/auth_oidc_{backend,role,group}/`, discovery locals in `config/config.hcl`, `vault_cluster` variables and wiring, and terragrunt inputs.
- Bind `akP-vault-admin` on the `oidc` mount to `global-root`, alongside the existing LDAP `vault_admin` binding.
- Pin the mount path to the literal `oidc`: the registered redirect URIs embed `/ui/vault/auth/oidc/oidc/callback`.

Requires #146, terraform-authentik #33 and #148 applied first.

---------

Co-authored-by: BenVincent <benvin@main.unkin.net>
Reviewed-on: #147
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-30 22:38:05 +10:00
unkin-agent 6c489c6003 Bump arrstack plugin catalog to v0.2.0 (#144)
ci/woodpecker/push/apply Pipeline was successful
## Why

The v0.2.0 arrstack plugin binary is live on all five OpenBao nodes (RPM applied via post-merge puppet runs), and the catalog still pins the v0.1.0 sha — OpenBao refuses to launch a plugin whose binary hash does not match the catalog entry.

## Changes

- `config/plugins/vault-plugin-secrets-arrstack.yaml`: `version` -> `0.2.0` and `sha256` -> `9ea7f160…12fa1`, computed from the binary extracted from `openbao-plugin-secrets-arrstack-0.2.0-1.x86_64.rpm` (the same RPM puppet pins).

## Post-apply

Run `vault plugin reload -plugin=vault-plugin-secrets-arrstack` after the apply so the running mount swaps to the v0.2.0 binary.

Reviewed-on: #144
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-30 17:07:28 +10:00
unkin-agent 9fa51f401a Add arrstack creds role and k8s auth for mediamark (#141)
ci/woodpecker/push/apply Pipeline was canceled
## Why

mediamark (kids-content marking UI, namespace `mediamark`) needs Sonarr/Radarr access to list series/movies and read metadata + artwork. It should get ephemeral virtual arrproxy keys from the arrstack secrets engine via arrproxy, not a copy of the static app API keys.

## How

- `config/arrstack_secret_backend_role/arrstack/mediamark.yaml` — role minting keys scoped to `sonarr` + `radarr` (no prowlarr), `ttl: 60` / `max_ttl: 86400`, mirroring the existing per-app roles.
- `config/auth_kubernetes_role/k8s/au/syd1/mediamark.yaml` — k8s auth role `mediamark`, bound to serviceaccount `default` in namespace `mediamark`, `token_ttl`/`token_max_ttl` 600, audience `vault`.
- `policies/arrstack/creds/mediamark.yaml` — `read` on `arrstack/creds/mediamark`, granted to `k8s/au/syd1: [mediamark]` only (deliberately not the shared `default` k8s role, which would expose the creds to every namespace).

Engine mount/config and the existing roles are untouched.

## Note

The companion argocd-apps change consumes `arrstack/creds/mediamark` via a `VaultDynamicSecret` (response fields: `token`, `id`, `apps`, `subject`, `expires_at`).

**GET/HEAD-only is not expressible today.** `arrstack_secret_backend_role` carries `apps`/`ttl`/`max_ttl` only, and arrproxy scopes machine tokens by app — the GET/HEAD restriction on the cheeztv/kids tier is a grant on an OIDC *group*, not on a minted token. The role is scoped as tightly as the engine allows and the limitation is documented in the yaml; per-token method scoping needs a plugin + arrproxy feature.

Reviewed-on: #141
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-30 09:40:26 +10:00
unkin-agent 5879fbd2d8 Add ephemeral Gitea creds for repospawner (#142)
ci/woodpecker/push/apply Pipeline was successful
## 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>
2026-08-30 09:29:26 +10:00
unkin-agent 36fbfb86bc Sort arrstack role apps alphabetically (#133)
ci/woodpecker/push/apply Pipeline was successful
The master apply (pipeline 202) fails with "Provider produced inconsistent result after apply": the arrstack Vault engine returns apps alphabetically sorted while the provider models apps as an ordered List, so the declared order [sonarr, radarr, prowlarr] never matches the read-back. This is an interim unblock while the provider moves apps to a Set.

- Reorders apps in config/arrstack_secret_backend_role/arrstack/all.yaml to alphabetical order to match the engine read-back

Reviewed-on: #133
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-22 23:25:44 +10:00
unkin-agent 0e82cda02d vault: mount arrstack engine + config + roles (3/3) (#127)
ci/woodpecker/push/apply Pipeline failed
## Why

Creates the arrstack secrets engine itself: the mount + config and the per-scope roles that mint arrproxy API keys. **PR 3 of 3 (resources)**, stacked on #126 (policy). Final step of the register -> policy -> resources split (was #124).

## Change

- Adds `config/arrstack_secret_backend/arrstack.yaml`: mounts the engine at `arrstack` and writes its config (`base_url`, timeout). The arrproxy admin token stays out of git and is read from KV by the module.
- Adds `config/arrstack_secret_backend_role/arrstack/{all,sonarr,radarr,prowlarr}.yaml`: roles scoped to each arr app (plus one covering all three). Default `ttl` is **60s** (short-lived, renewed on demand); `max_ttl` 86400 mirrors the litellm sibling convention. The engine additionally caps renewal at the arrproxy admin token's fixed mint expiry.
- Adds `modules/vault_cluster/modules/arrstack_secret_backend{,_role}` and wires them in: `config/config.hcl` maps, `modules/vault_cluster/main.tf`, `variables.tf`, the environment inputs, and the root provider block.
- Provider source is `artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack` (repo `terraform-provider-vault-secrets-arrstack`), local name `arrstack`.

## Apply order

Apply **after PR #126 (policy) AND after `terraform-provider-vault-secrets-arrstack` v0.1.0 is published** to the artifactapi terraform registry. Until the provider is published, `tofu init` cannot resolve it, so **CI/plan on this PR is red by design** — that is expected, not a regression.

Note **plan-green != apply-green**: the KV-sourced `admin_token` is only fetched at apply time, so a green plan does not prove the seeded token is readable.

## Stack

1. register -> #125
2. policy -> #126
3. **resources (this PR)** -> `benvin/arrstack-resources` off `benvin/arrstack-policy`

Supersedes #124.

---------

Co-authored-by: unkin-agent <unkin-agent@git.unkin.net>
Co-authored-by: BenVincent <benvin@main.unkin.net>
Reviewed-on: #127
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-21 00:09:47 +10:00
unkin-agent 08ec281b1d Remove ghp agent role (missing installation_id; unblock apply) (#131)
ci/woodpecker/push/apply Pipeline was successful
## Why

The `ghp` agent role fails at apply with `Code: 400 — installation_id is required for agent tokens`. The role config `config/ghp_secret_backend_role/ghp/agent.yaml` carries only a placeholder `installation_id`, so the role can never be created. This failure blocks the terraform-vault master apply, which in turn blocks the arrstack #127 apply.

Remove the ghp role for now so the master apply goes green. The `ghp` secret backend itself is retained (it now mounts and configures cleanly). The role can be re-added once a real `installation_id` is provided.

Because the role never successfully created (apply failed on it), removing it is non-destructive — it is not in state, so no destroy is introduced.

## Changes

- Delete `config/ghp_secret_backend_role/ghp/agent.yaml`, which empties the `ghp_secret_backend_role` for_each map so no role instance (and no downstream ghp_secret_role) is planned.

Backend `config/ghp_secret_backend/ghp.yaml` and all other config are unchanged. Net diff vs master is exactly this one file deletion.

---------

Co-authored-by: unkin-agent <unkin-agent@users.noreply.git.unkin.net>
Reviewed-on: #131
Co-authored-by: Unkin Agent <unkin-agent@unkin.net>
Co-committed-by: Unkin Agent <unkin-agent@unkin.net>
2026-08-20 00:05:13 +10:00
unkin-agent 392c5d2ac7 Restore ghp secret backend + roles (config now seeded) (#130)
ci/woodpecker/push/apply Pipeline failed
## Why

Reverts the temporary removal in #129. That PR deleted the ghp backend + role
config YAMLs to unblock the `master` apply, which was failing with:

```
Error: no secret found at "kv/data/service/vault/au/syd1/secret_backend/ghp/config"
  from module.ghp_secret_backend["ghp"].data.vault_kv_secret_v2.config
```

The ghp config KV is now seeded: `kv/data/service/vault/au/syd1/secret_backend/ghp/config`
holds key `admin_token`, and the ghp service secret
`kv/kubernetes/namespace/ghp/default/app` carries the matching `service_token`.
With the KV populated, `data.vault_kv_secret_v2.config` resolves, so the ghp
secret backend + role can be created. The ghp module wiring, plugin
registration, and policies were never removed (they stayed on `master`), so
restoring these two YAMLs re-populates the `for_each` maps and instantiates the
backend + role against the seeded config.

## Changes

- Restore `config/ghp_secret_backend/ghp.yaml`.
- Restore `config/ghp_secret_backend_role/ghp/agent.yaml`.

Net diff vs `master` is exactly the re-addition of those two files
(byte-identical to their pre-#129 content, the mirror-inverse of #129).

## Sequence

Final step (4/4) of the remove -> grant write policy -> seed KV -> add-back
sequence: #129 (remove) -> #128 (grant) -> KV seed -> this PR (add back).

## Verification

- `tofu fmt` clean, `yamllint` passes (pre-commit hooks green), `terragrunt validate` succeeds (only unrelated `vault_kv_secret_v2` deprecation warnings).
- `tofu init` installs the `vault-secrets-ghp` provider with no plugin/catalog error.
- ghp config KV path confirmed seeded with `admin_token`, so the previously-failing data source now resolves.
- A full privileged `plan` is not runnable under the agent AppRole (it lacks the policy to mint the consul backend token), so the created/destroyed resource counts are not machine-confirmed here; the git diff is exactly the two file additions, so no config-driven destroys are introduced.
- Note: the ghp backend mount at apply requires the `vault-plugin-secrets-ghp` binary present on the OpenBao nodes (pre-existing Puppet-managed plugin).

Reviewed-on: #130
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-19 23:47:32 +10:00
unkin-agent 2525bae1d7 Temporarily remove ghp secret backend + roles (unblock apply) (#129)
ci/woodpecker/push/apply Pipeline was successful
## Why

The `terraform-vault` master apply aborts with:

```
Error: no secret found at "kv/data/service/vault/au/syd1/secret_backend/ghp/config"
  from module.ghp_secret_backend["ghp"].data.vault_kv_secret_v2.config
```

The ghp secret backend reads its admin token from a KV path that has not been
seeded yet, so the apply fails and blocks every other change — including the
arrstack plugin registration (#125).

This PR **removes only the ghp backend + role config YAMLs (empties the
`for_each` map)**. With no config YAMLs, `var.ghp_secret_backend` /
`var.ghp_secret_backend_role` are empty maps, so zero ghp backend/role
instances are created, the unseeded `ghp/config` KV is never read, and the
apply passes. The ghp module wiring, plugin registration, and policies all stay
in place. This is part 1 of a remove -> grant write policy -> seed KV -> re-add
sequence, and the YAMLs will be restored once the ghp config KV is seeded.

## Changes

- Delete `config/ghp_secret_backend/ghp.yaml`.
- Delete `config/ghp_secret_backend_role/ghp/agent.yaml`.

Net diff vs `master` is exactly those two file deletions. All ghp wiring is
unchanged (identical to master): the `module.ghp_secret_backend` /
`module.ghp_secret_backend_role` instantiations, their variables, the
`config.hcl` parsing blocks, the `terragrunt.hcl` inputs, the
`vault-plugin-secrets-ghp` plugin registration, and the `ghp/admin` +
`ghp/creds/agent` policies all remain.

Reviewed-on: #129
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-19 22:51:55 +10:00
unkin-agent df0510e33b vault: register vault-plugin-secrets-arrstack in the catalog (1/3) (#125)
ci/woodpecker/push/apply Pipeline failed
## Why

Splits the arrstack Vault engine work (was #124) into three independently-appliable PRs so registration, policy, and engine resources are never bundled. This is **PR 1 of 3 (register)**.

## Change

- Registers the `vault-plugin-secrets-arrstack` plugin (v0.1.0) in the OpenBao plugin catalog via `config/plugins/vault-plugin-secrets-arrstack.yaml`.
- `sha256` pins the released v0.1.0 binary.
- No wiring changes needed: the `plugins` glob and `module.plugin` already exist on `master`.

## Apply order

Apply this **after** the Puppet plugin-install PR (unkin/puppet-prod #521, merged) has placed the binary at `/opt/openbao-plugins/vault-plugin-secrets-arrstack` on the OpenBao nodes. Registration fails until the binary is present on-node.

## Stack

1. **register (this PR)** -> `benvin/arrstack-register` off `master`
2. policy -> `benvin/arrstack-policy`
3. resources -> `benvin/arrstack-resources`

Supersedes #124.

Reviewed-on: #125
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-19 21:50:55 +10:00
unkin-agent 41935d5603 vault: wire up ghp secrets engine (backend + role + policies) (#121)
ci/woodpecker/push/apply Pipeline failed
Why: final step of the ordered ghp plugin add. Mounts + configures the ghp OpenBao secrets engine and its token-minting role (catalog registration is #123; policies #122).

How: config/ghp_secret_backend/ghp.yaml (mount at ghp; base_url https://ghp.unkin.net; admin_token read from KV kv/service/vault/au/syd1/secret_backend/ghp/config), config/ghp_secret_backend_role/ghp/agent.yaml (agent role), the two vault_cluster submodules, and the config.hcl ghp_secret_backend/role discovery + terragrunt inputs pulling the ghp provider (vault-secrets-ghp v0.1.0).

ORDER — apply in sequence:
1. puppet-prod#520 — install openbao-plugin-secrets-ghp binary (merged).
2. terraform-vault#122 — config-write policy (merged).
3. terraform-vault#123 — register plugin in catalog.
4. THIS PR — mount + config + role.

Before apply is clean: set the role real ghp App installation_id (currently sentinel 0), and KV dual-seed the ghpsvc_ token (this engine admin_token + the ghp deployment GHP_AUTH_SERVICE_TOKENS).

Reviewed-on: #121
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-16 15:55:01 +10:00
unkin-agent 5084a53015 vault: register ghp plugin in the catalog (config/plugins only) (#123)
ci/woodpecker/push/apply Pipeline was successful
Why: step 3 of the ordered ghp plugin add — register vault-plugin-secrets-ghp in the OpenBao plugin catalog as its own atomic change, before the engine is mounted/configured.

How: add config/plugins/vault-plugin-secrets-ghp.yaml (type: secret; sha256 pins the v0.1.0 binary installed by puppet-prod#520). config/plugins/* is generically discovered by config.hcl, so this is the only file. Catalog import is covered by the shared sudo-protected sys/plugins/catalog grant.

Order: puppet-prod#520 (install) -> terraform-vault#122 (config-write policy, merged) -> **this** (catalog) -> terraform-vault#121 (mount + config + role).
Reviewed-on: #123
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-16 15:43:18 +10:00
unkinben 9e7687fccb Mint the netbox user-management credential dynamically from the single admin token (#119)
ci/woodpecker/push/apply Pipeline was successful
## Why

netbox_user_management authenticates to NetBox to reconcile service users + permissions on every apply. It must not depend on a second static admin token, and it must not break when the engine rotates its admin seed (`netbox/config/rotate` mints a fresh admin token and deletes the old one). The durable shape: keep exactly ONE static admin token, and have the netbox engine mint an ephemeral, user-admin-capable token that the e-breuninger provider uses to manage users.

## How

- `module.netbox_user_mgmt_role` creates `netbox/roles/vault-user-mgmt`, a write-enabled role for a pre-existing NetBox superuser named by `user_mgmt_username`. Minted tokens authenticate AS that superuser (NetBox tokens carry no scope beyond `write_enabled`; the user's permissions apply), so they can create users.
- `netbox_user_management` reads `netbox/creds/vault-user-mgmt` and configures the netbox provider with the minted token. When `user_mgmt_username` is unset it falls back to the single static `admin_token` (a `check` block warns that rotation would then break it) - a bootstrap/degraded path, never a second static token.
- Grant the deployer `read` on `netbox/creds/vault-user-mgmt` (the one deliberate exception to the admin policy's `netbox/creds/*` exclusion).
- Keep the bare-token + `token_version`-match postconditions on the single static admin token.

## Feasibility constraints (worked through, documented in-module)

1. **The engine CAN mint a user-admin token** - roles map to a pre-existing user with only a `write_enabled` gate (`vault-plugin-secrets-netbox` `path_roles.go`, `client.go` `MintToken`); point it at a superuser and minted tokens can manage users.
2. **Token transits state.** The hashicorp/vault provider (5.6.0) exposes ephemeral resources for KV only, not dynamic engine creds, so the mint is read via the `vault_generic_secret` DATA source: the short-lived token is written to state (sensitive, lease-revoked) and re-minted each plan. Migrate to an ephemeral resource once the vault provider ships a dynamic-secret one.
3. **A clean single fresh apply is not possible.** A provider cannot be configured from a role created in the same run (data sources don't defer; OpenTofu 1.11 defers only ephemeral resources, which the vault provider doesn't offer here). So enabling the dynamic path on a backend needs a one-time targeted bootstrap of the mount + role, then normal applies. Documented in `config/netbox_secret_backend/netbox.yaml`.

## Operator follow-up

- Repair the live mount first (unchanged): `vault write netbox/config token=<BARE>` (the mount uses `ignore_changes=[token]`), keep `token_version=2`.
- To enable dynamic minting: set `user_mgmt_username` to the pre-existing superuser, apply the deployer creds policy, then bootstrap once: `tofu apply -target=...netbox_secret_backend -target=...netbox_user_mgmt_role`, then apply normally. Until then user management stays on the static token (non-breaking, with a warning).

Reviewed-on: #119
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-12 00:03:13 +10:00
unkinben 8ccc5f1393 Add the netbox backend and terraform-infra role (#117)
ci/woodpecker/push/apply Pipeline was successful
## Why

- The netbox engine modules stand ready but mount nothing and create no identity until backend and role data exist, so terraform-infra still reads a static NetBox token instead of minting ephemeral scoped tokens.

## How

- Add `config/netbox_secret_backend/netbox.yaml` to mount the engine at `netbox` and point it at the syd1 NetBox URL; the admin token is read from KV, not stored here.
- Add `config/netbox_secret_backend_role/netbox/terraform-infra.yaml` as the single declarative source for the terraform-infra identity: filename-derived role name and NetBox username, write access, short TTLs, and an inline permissions block. Nothing in the file repeats the filename.
- Scope terraform-infra to view/add/change/delete on the IPAM/DCIM objects it manages: prefixes, ip-addresses, ip-ranges, devices, interfaces, mac addresses.
- Add `policies/netbox/creds/terraform-infra.yaml` letting the terraform-infra AppRole and its Woodpecker k8s role read `netbox/creds/terraform-infra`; it attaches to nothing until the separate terraform-infra Vault onboarding lands.

## Dependency order

- Stacked on the modules PR (#115), which stacks on the plugin registration PR. Merge order: plugin -> #115 -> this.

## CI note

- The plan step is red only on the external admin_token KV seed at `kv/data/service/vault/au/syd1/secret_backend/netbox/config` (a NetBox token with add_token + grant_token / superuser). Seeding that path is an environmental prerequisite, not a code defect; everything else validates.

---------

Co-authored-by: BenVincent <benvin@main.unkin.net>
Reviewed-on: #117
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-11 20:43:04 +10:00
unkinben 521ef4f0f3 Add the netbox secrets engine modules and wiring (#115)
ci/woodpecker/push/apply Pipeline was successful
## Why

- Managing NetBox from Vault needs three capabilities the repo does not yet have: mounting the netbox engine, minting scoped tokens through roles, and creating the NetBox service users those roles mint tokens for. Landing the modules and config scaffolding before any backend or role data lets each concrete identity be added as pure data later.

## How

- Add three modules under `modules/vault_cluster/modules`: `netbox_secret_backend` (mount + engine config, admin token read from KV), `netbox_secret_backend_role` (mint ephemeral scoped tokens for a filename-derived NetBox username), and `netbox_user_management` (mirror consul_acl_management: read the seeded admin token, drive one e-breuninger/netbox provider per backend, and synthesize the NetBox user + object permissions from the role map's inline permissions).
- Derive the `netbox_secret_backend` and `netbox_secret_backend_role` maps in `config.hcl`, deriving each role's name and netbox_username from its filename so the engine role and NetBox username match by construction.
- Wire the three module blocks and their variables through `vault_cluster` and the syd1 terragrunt inputs, reusing the sanitized backend-alias pattern the Consul providers use.
- Leave the backend and role maps empty: the modules stand ready and create nothing until backend and role config data are added.

## Dependency order

- Stacked on the plugin registration PR (branch `benvin/netbox-plugin`); merge that first, then this, then the backend + role PR (#117). Plans clean with empty netbox maps.

---------

Co-authored-by: BenVincent <benvin@main.unkin.net>
Reviewed-on: #115
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-09 16:36:18 +10:00
unkinben d080279728 Register the netbox secrets plugin in the catalog (#118)
ci/woodpecker/push/apply Pipeline was successful
## Why

- The netbox secrets engine cannot be mounted until its plugin binary is registered in the OpenBao catalog, so the catalog entry must land before any engine mount or role config references it.

## How

- Add `config/plugins/vault-plugin-secrets-netbox.yaml` registering the plugin as a secret plugin, pinned to the released v0.1.0 binary sha256 that Puppet installs on the OpenBao nodes. Bump the sha in lockstep with any RPM upgrade.

## Dependency order

- First of three stacked PRs: this plugin registration, then the netbox modules + wiring (#115), then the netbox backend + terraform-infra role (#117). Merges to master independently.

Reviewed-on: #118
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-09 16:30:06 +10:00
unkinben c20e7e4664 Let the agents AppRole mint unkin-agent Gitea tokens (#114)
ci/woodpecker/push/apply Pipeline was successful
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>
2026-08-08 23:41:53 +10:00
unkinben 95927202ba Rename terraform-ipam CI Vault access -> terraform-infra (#111)
ci/woodpecker/push/apply Pipeline was successful
Follows the `terraform-ipam` -> `terraform-infra` repo rename. Renames the k8s auth role (`woodpecker_terraform_infra`), consul secret-backend role + ACL policy (`terraform-infra`, state path `infra/terraform/infra/*`), consul creds read policy, and kv read policy (`kv/service/terraform/infra`).

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Reviewed-on: #111
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-06 22:25:38 +10:00
unkinben d61e985ef2 Add terraform-ipam CI Vault access (#110)
ci/woodpecker/push/apply Pipeline was successful
Grants the `terraform-ipam` Woodpecker pipeline its Vault footprint, mirroring the `terraform-artifactapi` set plus a KV read for provider tokens.

- k8s auth role `woodpecker_terraform_ipam` (SA `terraform-ipam`, ns `woodpecker`)
- consul secret backend role `terraform-ipam` + ACL policy writing `infra/terraform/ipam/*`
- consul creds read policy
- kv read policy for `kv/service/terraform/ipam` (`netbox_token` + `kea_token`)

The KV secret itself still needs seeding before the pipeline can auth to NetBox/Kea.

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Reviewed-on: #110
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-03 00:16:34 +10:00
unkinben 0e6da5cdd3 Add Vault-scoped agent kubernetes roles + agents AppRole (#109)
ci/woodpecker/push/apply Pipeline was successful
## Why
Agentic workloads currently need cluster-admin/root kubeconfig contexts to do routine per-domain work. This adds domain-scoped, Vault-issued kubernetes credentials plus an `agents` AppRole so agents get least-privilege access instead of escalating.

## Changes
- Add `kubernetes_secret_backend_role` configs `agent-dhcp`, `agent-dns`, `agent-certs`, `agent-storage` (au/syd1):
  - **agent-dhcp** (Role, ns `dhcp-system`): full verbs on `kea.unkin.net` CRDs; get/list/watch pods/services/configmaps/events + pods/log.
  - **agent-dns** (`service_account_name` mode): mints tokens for the static `agent-dns` SA (argocd-apps#332) whose per-namespace RoleBindings confine access to bind-system/bind-internal/bind-external/externaldns. `allowed_kubernetes_namespaces: [bind-system]` (the SA's namespace).
  - **agent-certs** (Role, ns `cert-manager`): full verbs on `cert-manager.io` + `acme.cert-manager.io` (closes the orders/challenges debugging gap); get/list/watch/delete secrets; get/list/watch pods + pods/log. Secret delete confined to `cert-manager`.
  - **agent-storage** (Role, ns `cephrgw-system`): full verbs on `ceph.unkin.net` CRDs (buckets/bucketaccesses/objectstoreusers); get/list/watch pods + pods/log.
- Extend the `kubernetes_secret_backend_role` module with an optional `service_account_name`; when set, `generated_role_rules`/`kubernetes_role_type` are omitted (the SA's own bindings supply RBAC).
- Add creds policies for each role, bound to the `kubernetes_au_syd1_cluster_operator` ldap group (human kubectl use) and the `agents` AppRole (programmatic use).
- Add the `agents` AppRole (mirrors the certmanager approle schema): `bind_secret_id: false` (role_id-only login), `token_bound_cidrs: [10.10.12.200/32]` (agent workstation wg0 addr), deterministic role_id, 1h/4h TTLs.
- Add `policies/kv/kubernetes/agents.yaml` granting the AppRole create/read/update/list on `kv/data/kubernetes/*` + read/list on `kv/metadata/kubernetes/*` (no delete).

## Ordering
argocd-apps#332 (the `agent-dns` SA + ClusterRole + per-namespace RoleBindings) must sync **before** the `agent-dns` creds here are usable — Vault mints tokens for a service account that must already exist.

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Reviewed-on: #109
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-02 21:55:05 +10:00
unkinben c0cc74927c Add logarchive gpg key + logging_logarchiver read access (#106)
ci/woodpecker/push/apply Pipeline was successful
## Why

logarchiver encrypts archived logs to an OpenPGP key held in Vault's gpg engine so the private key never leaves Vault (retrieval delegates decryption to `gpg/decrypt/logarchive`, operator-only). This provisions the key and lets the service read only its public key.

## Changes

- Create gpg key `logarchive` (rsa-4096, non-exportable) in the `gpg` mount.
- Add k8s auth role `logging_logarchiver` bound to SA `logarchiver` in the `logging` namespace.
- Add policy granting `read` on `gpg/keys/logarchive` to that role (public key only; no decrypt/export).

Cross-repo: this must apply before the argocd-apps logarchiver Deployment (unkin/argocd-apps) can fetch the key.

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
---------

Co-authored-by: benvin <neotheo@gmail.com>
Reviewed-on: #106
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-29 20:39:41 +10:00
unkinben 31f32aba0f gitea roles: add read:user scope for API login validation (#105)
ci/woodpecker/push/apply Pipeline was successful
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>
2026-07-28 18:07:29 +10:00
unkinben 96a6a7d728 gitea: add the gitea token secrets engine (mount, config, teabot roles) (#101)
ci/woodpecker/push/apply Pipeline was successful
## 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>
2026-07-27 23:42:13 +10:00
unkinben 1fa5900787 Add terraform-enc Vault/Consul plumbing + encapi token grant (#98)
ci/woodpecker/push/apply Pipeline was successful
The new **terragrunt-enc** repo manages all encapi ENC data (statuses, roles, node classifications) via Terraform/Terragrunt and needs its own Vault/Consul plumbing, mirroring terraform-git and terraform-incus. This supersedes the dual-write approach in terraform-incus PR #39; the equivalent terraform-incus grant (PR #97) is being closed, so the encapi-token grant is created fresh here for the new approle.

Changes:
- Add approle role `terraform_enc` and k8s auth role `woodpecker_terraform_enc` (bound to the `terraform-enc` ServiceAccount in the `woodpecker` namespace) for CI auth.
- Add consul secret backend role `terraform-enc` plus its ACL rules granting `write` on `infra/terraform/enc/` (its terragrunt state prefix), and a policy letting both auth roles read `consul_root/au/syd1/creds/terraform-enc`.
- Grant both auth roles read on `kv/data/kubernetes/namespace/encapi/default/environment` (the ENCAPI_WRITE_TOKEN) so `make apply` can write to encapi via the encapi provider.

Reviewed-on: #98
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-24 23:18:56 +10:00
unkinben dcc73131a4 Bump rancher plugin catalog to v0.1.1 (bearerToken fix) (#96)
ci/woodpecker/push/apply Pipeline was successful
## Why

v0.1.0 of the rancher plugin minted non-functional tokens — it returned `status.value` (the secret fragment) instead of `status.bearerToken` (`ext/<name>:<secret>`), so every cred and every root rotation 401'd against Rancher. Fixed in plugin v0.1.1 (PR #2, released).

## Changes

- `config/plugins/vault-plugin-secrets-rancher.yaml` sha256 → v0.1.1 binary `9e597cd9…d5955` (verified against both v0.1.1 RPMs). Re-registers the catalog entry so OpenBao launches the fixed binary.

## Deploy coordination

1. Puppet installs the v0.1.1 RPM on the vault nodes (separate puppet-prod PR).
2. Merge this → apply re-registers the catalog with the new sha.
3. `vault plugin reload -plugin=vault-plugin-secrets-rancher` so the running plugin swaps to v0.1.1 (its sha must match this catalog entry).

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #96
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-18 22:59:35 +10:00
unkinben 87e3ada14f Mount the rancher secrets engine + seed a service account + roles (#93)
ci/woodpecker/push/apply Pipeline was successful
## Why

Deploy the new Rancher token secrets engine into the cluster (the last of the 4 wiring PRs), mirroring the litellm/gpg pattern. Users can then `vault read rancher/creds/<role>` for short-lived, cluster-scoped Rancher tokens, backed by a seeded admin token the engine auto-rotates before Rancher's 90-day cap.

## Changes

- Add `rancher_secret_backend` module — mount + config via the ranchervaultsecret provider (rancher_url `https://rancher.k8s.syd1.au.unkin.net`).
- Add `rancher_secret_backend_service_account` module — seeds an auto-rotated token (90d TTL / 45d rotation); the seed token is read from KV, not git.
- Add `rancher_secret_backend_role` module + a `ci` role (1h/8h, cluster+TTL scoped).
- Wire `config.hcl` discovery, module variables, `main.tf` blocks, terragrunt inputs, and the `rancher` provider in `root.hcl`.
- Config: `config/rancher_secret_backend/rancher.yaml`, `.../service_account/rancher/admin.yaml`, `.../role/rancher/ci.yaml`.

## Prerequisite

Populate `kv/service/vault/au/syd1/secret_backend/rancher/service_account/admin` with a live Rancher admin token (keys: `token`, optional `token_name`) **before** apply, exactly as litellm's `master_key` is seeded in KV.

## Merge order

Part 4 of 4 (last). Requires: puppet install (#483) → deployer policy (#91) → plugin import (#92) → this. The `plan` needs the KV seed present, so seed KV first.

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #93
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-18 16:16:54 +10:00
unkinben 193c17d1bc Manage the litellm plugin via config/plugins (import existing registration) (#90)
ci/woodpecker/push/apply Pipeline was successful
Bring the litellm plugin under terraform management like the gpg one, using the new plugin-import module (#89).

## Why
The litellm plugin was registered **manually** before terraform owned the catalog. If we just add it to `config/plugins/`, the first apply tries to *create* a catalog entry that already exists and fails. So its state must be imported first.

## Changes
- Add `config/plugins/vault-plugin-secrets-litellm.yaml` (`sha256` = released **v0.1.1** openbao binary from the RPM Puppet installs).

## Manual pre-step (before apply)
Import the existing registration into state, and verify the sha matches the live catalog:
```sh
cd environments/au/syd1
# confirm the live sha first:
bao read sys/plugins/catalog/secret/vault-plugin-secrets-litellm
# import into state:
terragrunt import \
  'module.plugin["vault-plugin-secrets-litellm"].vault_plugin.this' \
  secret/vault-plugin-secrets-litellm
```
If the live sha differs from the yaml, update the yaml to match (or expect a benign re-register to the on-disk v0.1.1 sha). Needs the deployer's plugin-catalog access (#88, already merged).

Reviewed-on: #90
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-18 14:55:33 +10:00
unkinben 03e9baf17f Register the rancher plugin in the catalog (import) (#92)
ci/woodpecker/push/apply Pipeline was successful
## Why

Register the released v0.1.0 rancher secrets plugin so the engine can be mounted. Uses the existing config/plugins discovery + plugin module (added for gpg in #89). Mirrors #90 (litellm import).

## Changes

- Add `config/plugins/vault-plugin-secrets-rancher.yaml` (`type: secret`, `command: vault-plugin-secrets-rancher`, `sha256` pinned to the released binary `d2b17f45…ec574a`, verified against both the vault and openbao v0.1.0 RPMs).

## Merge order

Part 2 of 4. Requires the binary on disk (puppet-prod install) and the deployer policy (#91). Merge before the backend PR.

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #92
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-18 14:47:08 +10:00
unkinben 933de177fa Register + mount the GPG secrets engine at gpg/ (#87)
ci/woodpecker/push/apply Pipeline was successful
Complete the deploy of the [vault-plugin-secrets-gpg](https://git.unkin.net/unkin/vault-plugin-secrets-gpg) engine. Puppet ([puppet-prod #480](unkin/puppet-prod#480)) installs the `openbao-plugin-secrets-gpg` RPM onto the OpenBao nodes; this registers that binary in the plugin catalog and enables the secrets engine so `gpg/` is actually usable.

- Add a `gpg_secret_backend` module using the standard `hashicorp/vault` provider (already required at 5.6.0): `vault_plugin` (catalog register with a pinned sha256) + `vault_mount` (enable at the mount path).
- Wire it through `vault_cluster` (new `gpg_secret_backend` variable + module block) and the config discovery (`config.hcl` group + syd1 terragrunt input), mirroring `litellm_secret_backend`.
- Add `config/gpg_secret_backend/gpg.yaml` mounting at `gpg/` and pinning the released v0.1.0 binary sha256 (`0e92d740…a7b20`, extracted from the published RPM). Puppet installs the RPM floating, so this sha must be bumped in lockstep on any plugin upgrade or OpenBao rejects the binary.

Validated locally with `tofu validate` + `tofu fmt`. Granting non-root access to `gpg/*` (auth roles + policies) is a follow-up scoped to whoever consumes the engine (e.g. passv from CI).

Reviewed-on: #87
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-17 23:19:38 +10:00
unkinben 3d59758324 Add a plugin-import module + config/plugins for catalog registration (#89)
ci/woodpecker/push/apply Pipeline was canceled
Split plugin catalog registration out of the per-engine backend modules into its own concern (previously bundled into #87's gpg_secret_backend).

## Changes
- New generic `plugin` module (`vault_plugin`: type/name/command/sha256/plugin_version) that imports a binary into the catalog.
- New `config/plugins/` discovery group (filename = catalog name = mount type), wired through `vault_cluster` (`plugins` variable + module) and the syd1 environment.
- `config/plugins/vault-plugin-secrets-gpg.yaml` pins the released v0.1.0 binary sha256 (`0e92d740…a7b20`, from the published RPM). Puppet installs the RPM floating, so bump this in lockstep on upgrade.

Any engine now registers its plugin by dropping a file in `config/plugins/`; its `*_secret_backend` module just mounts the registered type.

Needs the deployer's plugin-catalog access (#88). Merge order: **#88 → this → #87**.

Reviewed-on: #89
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-17 23:08:04 +10:00
unkinben 8bb071ae46 Add auth and state access for terraform-rancher (#86)
ci/woodpecker/push/apply Pipeline was successful
## Why

The new `terraform-rancher` repo (manages Rancher's Authentik OIDC auth via the rancher2 provider) needs Vault auth + Consul state, mirroring the terraform-authentik runner (#78/#81/#82).

## Change

- `AppRole/terraform_rancher` + k8s auth role `woodpecker_terraform_rancher` (SA terraform-rancher in the woodpecker ns).
- Consul secret-backend role + ACL policy (`resources/secret_backend/consul_root/au/syd1/terraform-rancher.hcl`) granting write to the `infra/terraform/rancher/` state prefix.
- Vault policies: read the Rancher admin API token (`kv/service/terraform/rancher`) and the keycloakoidc client secret (`kv/kubernetes/namespace/cattle-system/default/oauth-credentials`), plus the consul_root state creds.

Scoped the OAuth read to the `cattle-system` path specifically (rather than the `+` wildcard the authentik policy uses) since the Rancher runner only needs its own app's secret.

## Validation

pre-commit (terragrunt-hcl-fmt + yamllint) passed. CI plan will confirm.

Reviewed-on: #86
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-15 21:37:45 +10:00
benvin 0dba5e00a6 chore: update litellm address (#85)
ci/woodpecker/push/apply Pipeline was successful
- update litellm address
- add a test role

---------

Co-authored-by: Ben Vincent <ben@unkin.net>
Reviewed-on: #85
2026-07-09 23:47:32 +10:00
unkinben 95e7a81b2e feat: manage litellm secrets engine via terraform-provider-litellmvaultsecret (#83)
ci/woodpecker/push/apply Pipeline failed
## Why
The `vault-plugin-secrets-litellm` engine (mints LiteLLM virtual keys) is registered in Vault, but nothing in this repo declared its mount, config, or roles. This wires in the companion `litellm` provider (`git.unkin.net/unkin/litellmvaultsecret`) so the mount is managed as code alongside the other secret backends.

## Changes
- Add `litellm_secret_backend` module that mounts the engine and writes its config (`base_url`, `request_timeout_seconds`); reads the sensitive `master_key` from KV at `kv/service/vault/<country>/<region>/secret_backend/<path>`, matching the consul/kubernetes backend convention.
- Add `litellm_secret_backend_role` module that manages roles (`models`, `max_budget`, `key_alias_prefix`, `ttl`/`max_ttl` in seconds, `metadata`).
- Register both modules in `vault_cluster` `main.tf` and add typed variables in `variables.tf`.
- Discover `litellm_secret_backend[_role]` YAML in `config.hcl` and pass the maps through the terragrunt inputs.
- Declare the `litellm` provider (pinned `0.1.0`) and a `provider "litellm"` block in the generated root `backend.tf`.
- Add example config for the `litellm` mount and a sample `team-a` role.

## Notes
- Requires the `master_key` KV secret to exist at `kv/service/vault/au/syd1/secret_backend/litellm` before apply (the module reads it, does not create it).
- Assumes provider `git.unkin.net/unkin/litellmvaultsecret` `0.1.0` is published to the artifactapi `terraform-unkin` registry.

Reviewed-on: #83
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-07 00:18:29 +10:00
unkinben 36d7afbb65 feat: add vault/consul config for media terraform repos (#79)
ci/woodpecker/push/apply Pipeline was successful
Add Kubernetes auth roles, AppRole configs, Consul secret backend roles, Consul ACL policies, and Vault kv read policies for terraform-sonarr, terraform-radarr, and terraform-prowlarr.

Reviewed-on: #79
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-06-28 22:03:25 +10:00
unkinben c33dcdc447 Add auth and state access for terraform-authentik (#78)
ci/woodpecker/push/apply Pipeline was successful
## Summary
- K8s auth role for Woodpecker CI (`terraform-authentik` SA in `woodpecker` namespace)
- AppRole for local terraform runs
- Consul secret backend role (`terraform-authentik`, TTL 120/300)
- Consul ACL policy for `infra/terraform/authentik/` key prefix
- Vault policy granting both auth methods access to Consul creds

Reviewed-on: #78
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-06-28 01:17:51 +10:00
benvin be9bd96cf3 feat: enable consul state store for artifactapi (#77)
ci/woodpecker/push/apply Pipeline was successful
enable the terraform-artifactapi system to manage its state in consul
using dynamic credentials from kubernetes ci jobs in woodpecker

---------

Co-authored-by: Ben Vincent <ben@unkin.net>
Reviewed-on: #77
2026-06-17 21:42:25 +10:00
benvin 346cf9fa43 feat: manage gitadmin token (#74)
ci/woodpecker/push/apply Pipeline was successful
- add approle for terraform-git
- add policy to read gitadmin token
- update access to the terraform-git consul token

---------

Co-authored-by: Ben Vincent <ben@unkin.net>
Reviewed-on: #74
2026-06-08 15:17:58 +10:00
unkinben 1288057b81 feat: add vault and consul roles for terraform-git (#73)
ci/woodpecker/push/apply Pipeline was successful
## Summary
- Add K8s auth role woodpecker_terraform_git for CI pipeline authentication
- Add consul secret backend role terraform-git for consul state storage tokens
- Add consul ACL policy granting write access to infra/terraform/git/ key prefix
- Add vault policy for reading consul creds at consul_root/au/syd1/creds/terraform-git

## Test plan
- [ ] Verify terragrunt plan succeeds
- [ ] Verify consul ACL policy is created correctly
- [ ] Verify K8s auth role can authenticate from woodpecker namespace

Reviewed-on: #73
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-06-07 20:36:35 +10:00
unkinben 9cbac6d3ef feat: add plan workflow
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
- update makefile to enable kubernetes auth or roleid auth
- add plan workflow
- update all policies to allow the terraform-vault kubernetes role
2026-05-21 23:52:30 +10:00
unkinben 48a4fd0dd1 feat: add templated policies for kubernetes
ci/woodpecker/pr/pre-commit Pipeline was successful
- add default kubernetes auth role
- add templated access kv/kubernetes/*
2026-03-08 12:48:08 +11:00
unkinben 546a9efe44 fix: update audience for rpmbuilder
ci/woodpecker/pr/pre-commit Pipeline was successful
when using using the service account jwt directly, the default audience
is the api servers url
2026-03-07 11:31:36 +11:00
unkinben 71789f9f32 feat: add rpmbuilder k8s role
ci/woodpecker/pr/pre-commit Pipeline was successful
- create rpmbuilder role
- enable access to gitea/github ro-tokens
- enable access to rpmbuilder role from woodpeckerci
2026-03-07 11:06:27 +11:00
unkinben d9e07e432e chore: add artifactapi k8s role
ci/woodpecker/pr/pre-commit Pipeline was successful
- enable access to read artifactapi secrets
2026-03-06 18:53:42 +11:00
unkinben 7cafafd483 feat: set max token life for auth_kubernetes_role
found kubernetes vaultauth resources never picking up new policies,
because they would infinitely renew their token.

- set default max token length for roles to 1 day
- changed all existing role token_max_ttl to match their token_ttl
2026-02-22 22:28:21 +11:00
unkinben dd44146d88 feat: add woodpecker secrets
- add secrets required to integrate woodpecker into gitea/pgsql
2026-02-22 22:27:30 +11:00
unkinben dca26029c0 feat: add terraform-ldap service
- add consul role/policy/acls to allow terraform-ldap state management
- add approle to generate tokens for consul
2026-02-15 13:38:31 +11:00
unkinben c093d5830d fix: kubernetes auth fixes
- annotations as alias metadata does not work with openbao (idempotency issue)
- set token_ttl to be 600 for all auth roles for kubernetes (min)
2026-02-15 13:06:08 +11:00
unkinben 90b765d713 feat: add identity secrets
- add kubernetes auth role for identity namespace
- add policy to access openldap bootstrap credentials
2026-02-15 13:01:06 +11:00
unkinben 33a746e545 feat: add kubernetes ldap groups
vault's terraform approle doesnt need to access all of these kubernetes
roles, it was just added as a placeholder and access to the kubernetes
roles was via the `vault_admin` to-much-access account. this is an
effort to roll back that and make access more targeted.

- add kubernetes* ldap groups for specific cluster/role combinations
- remove tf_vault from kubernetes* roles
2026-02-14 19:46:39 +11:00
unkinben a47f841028 feat: add terraform_k8s approle
- add approle for kubernetes terraform
- ensure it can access consul token for state storage
- ensure it can generate root token for managing kubernetes
2026-02-14 19:37:22 +11:00