Commit Graph

14 Commits

Author SHA1 Message Date
unkinben 702dc6c62f Infer NetBox user from the engine role config
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
Why:
- The NetBox service identity was split across two files (config/netbox_user
  and config/netbox_secret_backend_role) that repeated the username three
  times: the filename, a netbox_username field, and the permission name.
- Creating the engine role and creating its NetBox user are one act, so one
  file should describe the whole identity.

How:
- Make config/netbox_secret_backend_role/netbox/<name>.yaml the single source
  per identity: filename = engine role name = NetBox username, body = write
  access, TTLs, and an inline permissions block.
- Derive netbox_username from the filename in config.hcl (keep netbox_user_id
  as an optional override), and drop the netbox_username field from the role
  yaml.
- Iterate that same role map in the netbox_user_management module, keyed by
  config path, to synthesize the NetBox user and object permissions; default a
  single permission's name to the role name so nothing repeats the filename.
- Delete the config/netbox_user tree and its netbox_user variable/wiring.
- Scope terraform-infra to view/add/change/delete on the IPAM/DCIM objects it
  manages: prefixes, ip-addresses, ip-ranges, devices, interfaces, mac
  addresses.
2026-08-09 12:37:45 +10:00
unkinben bd1bcc2db9 Manage NetBox service users declaratively for the netbox engine
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
Why:
- The netbox secrets engine can only mint tokens for NetBox users that already
  exist, so today those service users must be hand-created before a role works.
  Consul already solves the equivalent problem declaratively, and NetBox should
  be managed the same way so Ben seeds only the engine admin token.

How:
- Add a netbox_user_management module mirroring consul_acl_management: it reads
  each backend's seeded admin token from KV, configures one e-breuninger/netbox
  provider per backend, and creates netbox_user + netbox_permission resources
  from a config-driven map (random unknown passwords, since these users
  authenticate only via Vault-minted tokens).
- Drive it from config/netbox_user/<username>.yaml, scanned in config.hcl and
  wired through terragrunt inputs and the vault_cluster module, reusing the
  sanitized backend-alias pattern the Consul providers use.
- Define the terraform-infra user 1:1 with the engine role, granting write on
  the IPAM/DCIM objects terraform-infra manages (prefixes, ip-addresses,
  ip-ranges, devices, interfaces, mac addresses, and the supporting
  role/tag/type objects).
- Flip the terraform-infra role's netbox_username from svc-terraform-infra to
  terraform-infra so the engine role and NetBox username match exactly.
2026-08-09 12:18:13 +10:00
unkinben b133fcdf88 Add NetBox secrets engine: mount, roles, policies
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Why:
- terraform-infra and the puppet netbox fact use static NetBox API tokens; the
  released vault-plugin-secrets-netbox mints per-consumer ephemeral tokens with
  lease-bound revocation, removing the long-lived secrets.

How:
- Register the vault-plugin-secrets-netbox v0.1.0 plugin in the catalog
  (config/plugins), pinning the released binary sha256.
- Add netbox_secret_backend + netbox_secret_backend_role modules and their
  config/terragrunt wiring, mirroring the gitea engine.
- Mount the engine at netbox with its admin token seeded from KV and rotated in
  place (lifecycle ignore_changes on the seed).
- Add roles: puppet-facts (read-only, 24h/72h) and terraform-infra
  (write, 1h/4h), each mapped to its NetBox service user.
- Add the netbox admin policy for the deployer and a netbox/creds/terraform-infra
  read policy bound to the terraform-infra AppRole + k8s auth role.
2026-08-09 00:15:49 +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 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 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 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 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 66119e5207 feat: add pre-commit check in ci
ci/woodpecker/pr/pre-commit Pipeline was successful
- add a ci workflow to verify pre-commit passes
- fix pre-commit errors/warnings:
  - missing required_version
  - missing required_providers
  - fixed terraform_deprecated_interpolation
  - removed terraform_unused_declarations
2026-02-28 21:42:47 +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 5536869a38 feat: implement consul ACL management with provider aliases
This commit message captures the major architectural change of implementing Consul ACL management
with proper provider aliasing, along with the supporting configuration files and policy definitions
for various terraform services.

- add consul_acl_management module to manage consul acl policies and roles
- add consul backend roles and policies for terraform services (incus, k8s, nomad, repoflow, vault)
- add consul provider configuration to root.hcl
- add policies to generate credentials for each role
- simplify consul_secret_backend_role module to reference acl-managed roles
- switch to opentofu for provider foreach support
- update terragrunt configuration to support consul backend aliases
- update pre-commit hooks to use opentofu instead of terraform
- configure tflint exceptions for consul acl management module
2026-02-14 18:13:50 +11:00
unkinben f6d06cb319 chore: cleanup unused config data
- remove token_policies from roles config data, this comes from policies.hcl inputs
- remove policies from ldap groups
- remove backend data from roles, this comes from config.hcl inputs
2026-01-26 23:51:50 +11:00
unkinben 8070b6f66b feat: major restructuring in migration to terragrunt
- migrate from individual terraform files to config-driven terragrunt module structure
- add vault_cluster module with config discovery system
- replace individual .tf files with centralized config.hcl
- restructure auth and secret backends as configurable modules
- move auth roles and secret backends to yaml-based configuration
- convert policies from .hcl to .yaml format, add rules/auth definition
- add pre-commit hooks for yaml formatting and file cleanup
- add terragrunt cache to gitignore
- update makefile with terragrunt commands and format target
2026-01-26 23:02:44 +11:00