## Why
The deployer identities cannot enable the `oidc` auth mount: `POST /v1/sys/auth/oidc` returns 403 because `sys/auth/<path>` (and its `/tune`) is sudo-protected, and `policies/sys/auth/admin.yaml` granted create/update/delete/read/list without `sudo`.
## How
- Add exact-path rules for `sys/auth/oidc` and `sys/auth/oidc/tune` to `policies/sys/auth/admin.yaml` with the wildcard's capability set plus `sudo` (exact match wins over the glob, so the set is repeated in full); auth block unchanged.
Merge order: apply this, then re-run the master apply so `module.auth_oidc_backend["oidc"]` can create the mount.
Reviewed-on: #149
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## 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>
## Why
AppRole capabilities are fixed at login, so the deployer needs `auth/oidc/*` and identity-group grants in an apply that precedes the one creating those resources.
## How
- Add `policies/auth/oidc/admin.yaml`: full `auth/oidc/*` administration (mount config and login roles), mirroring `policies/auth/ldap/admin.yaml`.
- Add `policies/identity/group/admin.yaml`: manage external identity groups, group aliases and `identity/lookup/group`, on both the collection and per-id endpoints.
- Grant both policies to the `tf_vault` approle and the `woodpecker_terraform_vault` k8s role.
Apply before #147.
Reviewed-on: #148
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
**Why:** the terraform-vault deployer must read the OpenBao OIDC client credentials that Authentik's provider module generates before it can configure `auth/oidc`, and AppRole capabilities are fixed at login so the grant has to exist in a prior apply.
**How:**
- Add `policies/kv/service/authentik/oidc-vault/read.yaml`: read on `kv/data/service/authentik/oidc-vault` for the deployer identities (approle `tf_vault`, k8s/au/syd1 `woodpecker_terraform_vault`); `terraform_authentik` still owns the write side of `kv/service/authentik/*`.
**Merge order:** this PR must merge and apply *before* the follow-up PR that adds the `auth/oidc` modules. OIDC becomes the default human auth path; approle/k8s (CI and agents) and break-glass are unchanged.
Reviewed-on: #146
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## Why
Engine plugin v0.2.0 (catalog bumped in #144) added a `methods` field to arrstack roles, pinning a minted arrproxy key to a set of HTTP methods so a read-only integration can hold a key that cannot write. Provider v0.2.0 (just published to the `terraform-unkin` registry) exposes it as an optional set attribute, but the module had no input for it, so no role yaml could use it.
## How
- Bumps the `vault-secrets-arrstack` provider pin from 0.1.1 to 0.2.0 in `environments/root.hcl` and both arrstack modules.
- Adds an optional `methods` input to `modules/vault_cluster/modules/arrstack_secret_backend_role` and passes it through to the resource.
- Threads `methods` through the `vault_cluster` `arrstack_secret_backend_role` object type, so a role yaml may now carry a `methods:` list and it flows via the existing config.hcl merge with no discovery change.
`methods` defaults to `null` rather than `[]`: the provider reads an unrestricted role back as null, so a null default keeps a role yaml that omits the field drift-free. An empty-set default would plan `null -> []` on every existing role.
**Expected plan: no resource changes.** No role yaml changes here, so the plan should be a provider-version-only diff (provider upgrade, zero add/change/destroy).
Follow-up PR scopes the mediamark role to GET/HEAD.
Reviewed-on: #145
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## 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>
## Why
`terraform-rancher` authenticates the rancher2 provider with a static 90-day admin API token stored in `kv/service/terraform/rancher` — the repo carries its own Makefile TODO to retire it. The Rancher secrets engine is already mounted at `rancher/` with a `ci` role (seeded `admin` service account, ttl 1h / max 8h), but no policy grants read on `rancher/creds/*`, so nothing can use it yet.
## What
Adds `policies/rancher/creds/ci.yaml`: `read` on `rancher/creds/ci`, bound to the same runner identities as the existing kv policy — approle `terraform_rancher` and k8s/au/syd1 role `woodpecker_terraform_rancher`.
Minted tokens are lease-bound and deleted from Rancher on revoke. They inherit the seeded admin service account's RBAC, so this is the same privilege as the static token it replaces, just short-lived.
## Follow-up
A terraform-rancher Makefile PR swapping `vault kv get kv/service/terraform/rancher` for `vault read -field=token rancher/creds/ci` must merge **only after** this one applies.
Reviewed-on: #143
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## 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>
## 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
`kv/service/authentik/agent-api-token` is seeded by hand today. terraform-authentik should own it as IaC (`vault_kv_secret_v2`), along with any future Authentik automation tokens, but its runner identities only hold read on `kv/service/terraform/authentik` and the per-namespace oauth-credentials paths.
This must land and **apply** first: an AppRole/k8s token's capabilities are fixed at login, so the companion terraform-authentik PR would fail its very first plan against a policy that is not yet live.
## How
- Adds `policies/kv/service/authentik/write.yaml`, granting:
- `kv/data/service/authentik/*` — create, read, update, delete
- `kv/metadata/service/authentik/*` — read, list, delete
- Assigned to the same identities that already hold the terraform-authentik read policy: approle `terraform_authentik` and `k8s/au/syd1` role `woodpecker_terraform_authentik`.
- `delete` is included (unlike the `kv/kubernetes` agents grant) so `terraform destroy` and resource replacement clean up both the data and the metadata; metadata `read`/`list` is what `vault_kv_secret_v2` hits on every plan.
- Scope note in the file header: the ask was to scope to `*-token` paths, but Vault ACL paths only support a trailing glob, so the whole `kv/service/authentik/` subtree is granted. terraform-authentik is the owner of everything under that prefix.
- Existing `policies/kv/service/authentik/agent-api-token/read.yaml` (agents approle) is untouched.
## Order
Merge + apply this first; the companion terraform-authentik PR merges only afterwards.
Reviewed-on: #140
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## Summary
- Grants the agents approle read on kv/service/authentik/agent-api-token
## Why
Automation seeds oauth client secrets and LDAP outpost tokens; fetching outpost tokens needs a scoped Authentik API token, seeded at this path by the operator.
Reviewed-on: #139
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## Why
The one-off terragrunt import in terraform-authentik that required this grant is complete (jellyfin provider, groups, application, and policy bindings are all reconciled into state; apply pipeline is green). Per the recovery plan the temporary read grant is removed again.
## Changes
- Reverts de9d6e5: removes policies/kv/service/terraform/authentik/read.yaml (agents AppRole read on kv/data/service/terraform/authentik)
Reviewed-on: #138
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
A one-off `terragrunt import` in terraform-authentik is needed to reconcile the Authentik resources orphaned by the jellyfin apply failure. The agents AppRole must be able to read the Authentik provider token (`kv/service/terraform/authentik`) to run the import; this grant is read-only on that single path and can be reverted once the import is done.
- Add `kv/service/terraform/authentik/read` policy (read on `kv/data/service/terraform/authentik`) bound to the `agents` AppRole
Reviewed-on: #137
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
PR plan pipelines were failing with "Error acquiring the state lock ... OperationTypePlan" when a plan collided with an apply (or another plan) holding the lock on the same Consul-backed state. Plans are read-only and don't need the lock.
- `plan`: pass `-lock=false` to `terragrunt ... plan`; `apply` is unchanged and still locks.
Reviewed-on: #136
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## Why
The `operator` kube context is a Vault-minted, read-only credential (Kubernetes
secret engine role `cluster-operator`, bound to a `get/list/watch`-only
ClusterRole). It is currently RBAC-forbidden from listing operator-owned CRDs —
the immediate breakage is `valkeyclusters.valkey.io` — and likewise every other
operator CRD group deployed via `argocd-apps`. This extends the RO ruleset so the
context can read those CRDs. Still strictly read-only: no create/update/delete.
## Change
- Extend the `cluster-operator` generated_role_rules
(`resources/secret_backend/kubernetes/au/syd1/roles/cluster-operator.yaml`)
with `get/list/watch` on the CRD API groups of the operators deployed via
`argocd-apps` (verbs and `resources: "*"` unchanged; same single rule block).
## API groups added
- `valkey.io` (valkey-operator — immediate need)
- `ceph.unkin.net` (cephrgw-operator)
- `bind.unkin.net` (bind-operator)
- `kea.unkin.net` (kea/dhcp operator)
- `k8up.io` (k8up)
- `grafana.integreatly.org` (grafana-operator)
- `operator.victoriametrics.com` (VictoriaMetrics operator)
- `clickhouse.altinity.com`, `clickhouse-keeper.altinity.com` (altinity clickhouse-operator)
- `acme.cert-manager.io` (cert-manager companion CRD group)
- `deviceplugin.intel.com`, `fpga.intel.com` (intel device plugins operator)
- `autoscaling.k8s.io` (VPA)
- `apm.k8s.elastic.co`, `beat.k8s.elastic.co`, `agent.k8s.elastic.co`,
`maps.k8s.elastic.co`, `enterprisesearch.k8s.elastic.co`,
`autoscaling.k8s.elastic.co`, `stackconfigpolicy.k8s.elastic.co` (ECK — the
`elasticsearch`/`kibana`/`logstash` ECK groups were already granted)
- `snapshot.storage.k8s.io`, `groupsnapshot.storage.k8s.io` (CSI external-snapshotter, deployed via csi-cephfs/csi-cephrbd)
Groups already present (`postgresql.cnpg.io`, `cert-manager.io`,
`externaldns.k8s.io`, `secrets.hashicorp.com`, `purelb.io`, `nfd.k8s-sigs.io`,
`elasticsearch/kibana/logstash.k8s.elastic.co`, `gateway.networking.k8s.io`,
etc.) are unchanged. Rancher/RKE/Calico/cluster-api/fleet management-layer CRD
groups are intentionally excluded — they are not `argocd-apps` operators.
---------
Co-authored-by: unkin-agent <agent@unkin.net>
Reviewed-on: #135
Co-authored-by: Unkin Agent <unkin-agent@unkin.net>
Co-committed-by: Unkin Agent <unkin-agent@unkin.net>
v0.1.1 models the role apps attribute as a Set instead of a List, fixing the ordering-based "inconsistent result after apply" error and superseding the interim alphabetical yaml sort (#133).
- Bumps the vault-secrets-arrstack provider pin from 0.1.0 to 0.1.1 in root.hcl and both arrstack modules
Reviewed-on: #134
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
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>
A new VaultStaticSecret `ceph-mediafs-secret` in ns `csi-cephfs` (for the legacy mediafs CephFS static PV) gets 403 on `kv/data/service/kubernetes/au/syd1/csi/ceph-mediafs-secret` — the `ceph-csi` role can already read the sibling `ceph-cephfs-secret` path via the same `ceph-csi-cephfs` VaultAuth, but no policy covers the new path.
- Adds `policies/kv/service/kubernetes/au/syd1/csi/ceph-mediafs-secret/read.yaml` granting read on `kv/data/service/kubernetes/au/syd1/csi/ceph-mediafs-secret`, bound to role `ceph-csi` on mount `k8s/au/syd1` (mirrors the existing ceph-cephfs-secret/ceph-rbd-secret policies)
Reviewed-on: #132
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## 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>
## 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>
## 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>
## Why
The `terraform-vault` master apply aborts because the KV path `kv/data/service/vault/au/syd1/secret_backend/ghp/config` (key `admin_token`, a `ghpsvc_` service token) is unseeded. The ghp secrets engine reads that value at `ghp/config` creation time, so the ghp data-source read fails and the apply stops. Granting the `agents` AppRole scoped write to just this one KV path lets an agent seed the value so the apply can proceed.
## Changes
- Add `policies/kv/service/vault/au/syd1/secret_backend/ghp/config_write.yaml`, a `vault_policy` bound to the `agents` AppRole role only.
- Grant `create`, `update`, `read` on the kv-v2 data path `kv/data/service/vault/au/syd1/secret_backend/ghp/config`.
- Grant `read` on the kv-v2 metadata path `kv/metadata/service/vault/au/syd1/secret_backend/ghp/config` (read on plan/apply).
- Scope to this single ghp config path only; no wildcards, no delete, no list, no other `secret_backend` configs (least privilege).
## Caveat
This grant is itself a `vault_policy` applied by the master apply, which currently aborts on the ghp data-source read. So the policy likely needs to be applied first (a targeted apply of just this `vault_policy`) before the agent can seed the KV path. The agent also still needs the actual `ghpsvc_` service token value provided out-of-band to write into `admin_token`.
---------
Co-authored-by: BenVincent <benvin@main.unkin.net>
Co-authored-by: unkin-agent <agent@unkin.net>
Reviewed-on: #128
Co-authored-by: Unkin Agent <unkin-agent@unkin.net>
Co-committed-by: Unkin Agent <unkin-agent@unkin.net>
## Why
Grants the Vault access the arrstack engine needs, before any engine resources exist. **PR 2 of 3 (policy)**, stacked on #125 (register). Keeping policy separate from resources honours the never-bundle / sequential-apply rule.
## Change
- Adds `policies/arrstack/admin.yaml`: the terraform-vault deployer (`tf_vault` approle + `woodpecker_terraform_vault` k8s role) may create/read/update/delete `arrstack/config` and manage `arrstack/roles/*`.
- Adds `policies/kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token/read.yaml`: the deployer may read the KV-seeded arrproxy admin token (both `kv/data/...` and `kv/metadata/...`) that the engine config sources. The existing `secret_backends_read` policy does not cover this `kubernetes/namespace` KV path.
- Adds `policies/arrstack/creds/{sonarr,radarr,prowlarr}.yaml`: each `terraform-<app>` run may read its own `arrstack/creds/<app>` to mint a scoped key.
- Policy YAMLs are auto-discovered by `policies/policies.hcl`, so no wiring changes are needed.
## Apply order
Apply **after PR #125 (register)**. Safe to apply before the engine exists — these only grant capabilities on paths.
## Stack
1. register -> #125
2. **policy (this PR)** -> `benvin/arrstack-policy` off `benvin/arrstack-register`
3. resources -> `benvin/arrstack-resources`
Supersedes #124.
Reviewed-on: #126
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
## 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>
## 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
47 changed files with 702 additions and 32 deletions
description="The unique path of the OIDC auth backend to configure"
type=string
}
variable"role_name"{
description="The name of the role"
type=string
}
variable"user_claim"{
description="Claim used as the entity alias name (the Vault identity of the human)"
type=string
default="email"
}
variable"groups_claim"{
description="Claim holding the caller's group memberships, matched against identity group aliases"
type=string
default="ak_groups"
}
variable"oidc_scopes"{
description="Scopes requested from the identity provider during the authorization request"
type= list(string)
default=[]
}
variable"bound_audiences"{
description="List of audiences (aud claim) accepted in the ID token"
type= list(string)
default=[]
}
variable"allowed_redirect_uris"{
description="Redirect URIs accepted for this role. Must match the provider's registered URIs exactly"
type= list(string)
}
variable"token_ttl"{
description="The TTL period of tokens issued using this role, in seconds"
type=number
default=3600
}
variable"token_max_ttl"{
description="The maximum lifetime for generated tokens in number of seconds. Its current value will be referenced at renewal time."
type=number
default=28800
}
variable"token_policies"{
description="List of policies to assign to the role (passed from policy_auth_map). Human authorization normally comes from external identity groups instead"
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.