78 Commits

Author SHA1 Message Date
Ben Vincent 6f27546207 Grant vault deployer access to import + manage the rancher engine
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
The rancher token secrets engine is registered ('imported') into the catalog by
terraform-vault (sys/plugins/catalog, sudo-protected) and configured via the
ranchervaultsecret provider, so the deployer needs catalog access plus write on
the engine's config/service-accounts/roles paths. Without this, apply 403s on
plugin registration and on rancher/* writes.

- Add policies/rancher/admin.yaml granting the tf_vault approle and the
  woodpecker_terraform_vault k8s role: catalog sudo on the plugin, and manage on
  rancher/{config,service-accounts,roles}.
2026-07-17 23:37:31 +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 ce1185deba Grant vault deployer access to import + manage the gpg engine (#88)
ci/woodpecker/push/apply Pipeline was successful
## Why
Applying the gpg mount (#87) needs two grants the deployer (`tf_vault` approle / `woodpecker_terraform_vault` k8s role) doesn't have. terraform-vault **registers the plugin itself** (`vault_plugin` → `sys/plugins/catalog`, a sudo-protected path) and **manages keys** via the gpgvaultsecret provider (`gpg/keys/*`). The deployer already has `sys/mounts/*` but neither of these, so apply would 403 on the plugin registration and on `gpg/keys` writes — the same failure mode as #84.

## Changes
- Add `policies/gpg/admin.yaml` granting:
  - `create/read/update/delete/sudo` on `sys/plugins/catalog/secret/vault-plugin-secrets-gpg` — to **import** (register/deregister) the plugin.
  - full management of `gpg/keys/*` (+ `gpg/keys` list) — to **manage keys**.
  - assigned to `tf_vault` (approle) + `woodpecker_terraform_vault` (k8s/au/syd1), mirroring `policies/litellm/admin.yaml` (#84).

Should merge/apply **before** #87 so the deployer can register the plugin and create the `pass` key.

Reviewed-on: #88
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-17 23:09:31 +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 a400e5dc7e fix: grant vault deployer access to manage the litellm engine (#84)
ci/woodpecker/push/apply Pipeline was successful
## Why
Applying the newly-merged litellm mount (#83) failed at apply time with:

```
Error: failed to write litellm config
URL: PUT https://vault.service.consul:8200/v1/litellm/config
Code: 403. * permission denied
```

The deployer identity (`tf_vault` approle / `woodpecker_terraform_vault` k8s role) can enable the mount via `sys/mounts/admin`, but no policy grants it access to the engine's own data paths, so writing the config and roles is denied.

## Changes
- Add `policies/litellm/admin.yaml` granting `create`/`read`/`update`/`delete` on `litellm/config` and `litellm/roles/*` (plus `read`/`list` on `litellm/roles`), assigned to the same auth roles as the other secret-engine admin policies (`tf_vault`, `woodpecker_terraform_vault`).

## Note
The policy attaches to the deployer's auth roles, so it takes effect on the next token issuance — a re-run of the apply (fresh Vault login) will have the permission and can write `litellm/config` and the roles.

Reviewed-on: #84
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-07 20:10:43 +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 bbde79d2a6 policies: let terraform-authentik read its provider API token (#82)
ci/woodpecker/push/apply Pipeline was successful
## Why
terraform-authentik's provider needs an Authentik API token (`TF_VAR_authentik_token`), now sourced from Vault at `kv/service/terraform/authentik` (Makefile wiring in terraform-authentik #2). The CI role needs read access to that path.

## Change
Extend `policies/kv/service/terraform/authentik.yaml` to also grant read on `kv/data/service/terraform/authentik` for the `terraform_authentik` approle + `woodpecker_terraform_authentik` k8s role.

Reviewed-on: #82
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-06 23:41:03 +10:00
unkinben f2c54888de policies: let terraform-authentik read oauth client secrets from kv (#81)
ci/woodpecker/push/apply Pipeline was successful
## Why
terraform-authentik now reads OAuth2 client secrets from Vault (`data.vault_kv_secret_v2`) rather than committing them (terraform-authentik #2). But the `terraform_authentik` approle / `woodpecker_terraform_authentik` k8s role only had the consul-creds policy, so `plan` fails with permission denied on the grafana oauth path.

## Change
Add `policies/kv/service/terraform/authentik.yaml` granting read on `kv/data/kubernetes/namespace/+/default/oauth-credentials` for both the approle and the woodpecker k8s role.

Reviewed-on: #81
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-06 23:05:20 +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
unkinben bb5f6922fa feat: add vault policy for terraform-git webhook secrets (#75)
ci/woodpecker/push/apply Pipeline was successful
## Summary
- Add read policy for kv/data/service/gitea/webhook/* path
- Assigned to terraform_git approle and woodpecker_terraform_git k8s auth role
- Webhook URLs are stored in Vault KV and read at plan/apply time

## Test plan
- [ ] Verify terragrunt plan succeeds for terraform-git after merge

Reviewed-on: #75
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-06-08 22:56:30 +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 3876fa818d chore: bump almalinux9 image tags (#72)
ci/woodpecker/push/apply Pipeline was successful
Bump almalinux9 image tags to 20260606

Reviewed-on: #72
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-06-07 00:35:30 +10:00
unkinben a548bf1cb1 fix: apply requires plan (#71)
ci/woodpecker/push/apply Pipeline was successful
- ensure make plan runs before make apply when deploying

Reviewed-on: #71
2026-05-22 00:03:08 +10:00
unkinben 93ba86baf3 feat: add apply workflow (#70)
ci/woodpecker/push/apply Pipeline was successful
Reviewed-on: #70
2026-05-21 23:57:25 +10:00
unkinben 098830c10b Merge pull request 'feat: add plan workflow' (#69) from benvin/make-plan-buildwq into master
Reviewed-on: #69
2026-05-21 23:54:07 +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 73aaaaeb99 Merge pull request 'chore: enable access to gateway.networking.k8s.io' (#68) from benvin/gatewayapi into master
Reviewed-on: #68
2026-05-21 22:42:28 +10:00
unkinben 7c60a5fd53 chore: enable access to gateway.networking.k8s.io
ci/woodpecker/pr/pre-commit Pipeline was successful
2026-05-21 22:39:57 +10:00
unkinben 27f12f183e Merge pull request 'chore: change to specific ci image' (#67) from benvin/ci_image into master
Reviewed-on: #67
2026-03-09 01:16:59 +11:00
unkinben c61434b692 chore: change to specific ci image
ci/woodpecker/pr/pre-commit Pipeline was successful
- almalinux9-opentofu image contains all required tools
2026-03-09 01:14:41 +11:00
unkinben 172ceac2fc Merge pull request 'feat: add templated policies for kubernetes' (#66) from benvin/kubernetes_structured_paths into master
Reviewed-on: #66
2026-03-08 12:57:58 +11: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 4dc09547ef Merge pull request 'fix: update audience for rpmbuilder' (#65) from benvin/default_aud into master
Reviewed-on: #65
2026-03-08 12:29:43 +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 679cec4bc1 Merge pull request 'feat: add rpmbuilder k8s role' (#64) from benvin/rpmbuilder-in-k8s into master
Reviewed-on: #64
2026-03-07 11:11:23 +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 4cbcec58d3 Merge pull request 'feat: enable woodpecker access to ro tokens' (#63) from benvin/woodpecker_task_access into master
Reviewed-on: #63
2026-03-07 10:52:38 +11:00
unkinben 9c93e185f8 feat: enable woodpecker access to ro tokens
ci/woodpecker/pr/pre-commit Pipeline was successful
- enable woodpecker tasks to access gitea/github read-only tokens
2026-03-07 10:49:39 +11:00
unkinben d6c8474bd3 Merge pull request 'chore: move pgsql password to vault' (#62) from benvin/artifactapi_postgrespassword into master
Reviewed-on: #62
2026-03-06 19:51:25 +11:00
unkinben 42351000ee chore: move pgsql password to vault
ci/woodpecker/pr/pre-commit Pipeline was successful
- no more storing secrets in configmaps
2026-03-06 19:39:36 +11:00
unkinben f7d1330c37 Merge pull request 'chore: add artifactapi k8s role' (#61) from benvin/artifactapi into master
Reviewed-on: #61
2026-03-06 18:57:05 +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 14a258de7d Merge pull request 'chore: enable access woodpecker-agent-secret' (#60) from benvin/woodpecker_agent_secret into master
Reviewed-on: #60
2026-03-03 23:34:32 +11:00
unkinben be8bcc3743 chore: enable access woodpecker-agent-secret
ci/woodpecker/pr/pre-commit Pipeline was successful
- add policy to access woodpecker-agent-secret
2026-03-03 23:30:49 +11:00
unkinben dc257b1bcd Merge pull request 'feat: add pre-commit check in ci' (#59) from benvin/woodpecker_integration into master
Reviewed-on: #59
2026-02-28 22:28:21 +11: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 9e6de4dc32 Merge pull request 'feat: set max token life for auth_kubernetes_role' (#58) from benvin/token_max_ttl into master
Reviewed-on: #58
2026-02-22 22:30:18 +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 c94b2af196 Merge pull request 'feat: add woodpecker secrets' (#57) from benvin/woodpecker into master
Reviewed-on: #57
2026-02-22 22:27:50 +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 18a62332f6 Merge pull request 'chore: enable access to openldap admin creds' (#56) from benvin/ldap_admin_pass_terraform_ldap into master
Reviewed-on: #56
2026-02-15 20:17:35 +11:00
unkinben 8fa68e2670 chore: enable access to openldap admin creds
- ensure terraform_ldap can read ldap admin credentials
2026-02-15 20:16:58 +11:00
unkinben 4cad39989f Merge pull request 'chore: add default_user_password credentials policy' (#55) from benvin/openldap_default_pass into master
Reviewed-on: #55
2026-02-15 13:45:45 +11:00
unkinben c825962490 chore: add default_user_password credentials policy
- fix the comment for ldap_admin_password
- add policy to read default_user_password
2026-02-15 13:43:02 +11:00
unkinben 51bc3fffc0 Merge pull request 'feat: add terraform-ldap service' (#54) from benvin/terraform-ldap into master
Reviewed-on: #54
2026-02-15 13:40:32 +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 d398911108 Merge pull request 'fix: kubernetes auth fixes' (#53) from benvin/kubernetes_fixes into master
Reviewed-on: #53
2026-02-15 13:08:43 +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 4b176846f2 Merge pull request 'feat: add identity secrets' (#52) from benvin/identity into master
Reviewed-on: #52
2026-02-15 13:02:01 +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 3fb5a64a17 Merge pull request 'feat: add kubernetes ldap groups' (#51) from benvin/kubernetes_ldap_groups into master
Reviewed-on: #51
2026-02-14 19:48:56 +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 4fe0e0de73 Merge pull request 'feat: add terraform_k8s approle' (#50) from benvin/terraform_k8s_approle into master
Reviewed-on: #50
2026-02-14 19:38:46 +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
unkinben 9192879c03 Merge pull request 'feat: use ephemeral consul token' (#49) from benvin/use_consul_creds into master
Reviewed-on: #49
2026-02-14 18:59:56 +11:00
unkinben 5cdf6b410d feat: use ephemeral consul token
- add vault_env to makefile
- retrieve a consul_http_token on demand from vault
2026-02-14 18:59:05 +11:00
unkinben b51617c009 Merge pull request 'feat: implement consul ACL management with provider aliases' (#48) from benvin/consul_backend into master
Reviewed-on: #48
2026-02-14 18:41:49 +11:00
unkinben 66ee6430fa Merge pull request 'feat: add tf_vault required policies' (#47) from benvin/tf-vault-policy-updates into master
Reviewed-on: #47
2026-02-14 18:41:33 +11:00
unkinben fd03727ec2 feat: add tf_vault required policies
move management of Vault back to tf_vault approle. for this, we need to
create a number of policies that are missing.

- add policies to manage consul secret engines
- add policies to manage pki secret engines
- add policies to manage kv secret engines
- add policies to manage ssh secret engines
2026-02-14 18:39: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 f8f1185b42 Merge pull request 'chore: add puppet k8s role' (#46) from benvin/puppet_secrets into master
Reviewed-on: #46
2026-02-01 14:54:45 +11:00
unkinben 75e9db1aa6 chore: add puppet k8s role
- add role and policies
2026-02-01 14:54:23 +11:00
unkinben f47804ffdf Merge pull request 'chore: rancher pods use rancher service account' (#45) from benvin/rancher_role into master
Reviewed-on: #45
2026-01-30 22:11:53 +11:00
unkinben 24c124d6eb chore: rancher pods use rancher service account
- update bound service account names to be `rancher`
- update namespace to cattle-system (do not run rancher in another namespace)
2026-01-30 22:11:08 +11:00
unkinben 9d54b4cfcc Merge pull request 'chore: add rancher role' (#44) from benvin/rancher_role into master
Reviewed-on: #44
2026-01-30 19:46:19 +11:00
unkinben 33af7010fb chore: add rancher role
- add kubernetes role for rancher
- add policy to enable access to bootstrap-password
2026-01-30 19:43:06 +11:00
unkinben cb1b383035 Merge pull request 'feat: major restructuring in migration to terragrunt' (#43) from benvin/vault_terragrunt into master
Reviewed-on: #43
2026-01-26 23:53:35 +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 1c9e063310 Merge branch 'master' into benvin/vault_terragrunt 2026-01-26 23:07:13 +11:00
unkinben b115b7d28a Merge pull request 'chore: add nzbget secrets' (#42) from benvin/nzbget into master
Reviewed-on: #42
2026-01-26 18:31:48 +11:00
unkinben 25e3d48337 chore: add nzbget secrets
- add policy for nzbget secrets
- enable the media-apps kubernetes role to use policy
2026-01-26 18:30:49 +11:00
unkinben fdc801739f Merge pull request 'feat: add prowlarr access' (#41) from benvin/prowlarr_policy into master
Reviewed-on: #41
2026-01-04 23:37:23 +11:00
unkinben 56d858f900 feat: add prowlarr access
- enable kubernetes access to prowlarr secrets
2026-01-04 23:36:43 +11:00
227 changed files with 2075 additions and 188 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ repos:
- repo: https://github.com/gruntwork-io/pre-commit - repo: https://github.com/gruntwork-io/pre-commit
rev: v0.1.30 rev: v0.1.30
hooks: hooks:
- id: terraform-fmt - id: tofu-fmt
- id: terraform-validate - id: tofu-validate
- id: tflint - id: tflint
- id: terragrunt-hcl-fmt - id: terragrunt-hcl-fmt
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
+23
View File
@@ -0,0 +1,23 @@
when:
- event: push
branch: master
steps:
- name: apply
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
commands:
- dnf install vault -y
- make plan
- make apply
backend_options:
kubernetes:
serviceAccountName: terraform-vault
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+21
View File
@@ -0,0 +1,21 @@
when:
- event: pull_request
steps:
- name: plan
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
commands:
- dnf install vault -y
- make plan
backend_options:
kubernetes:
serviceAccountName: terraform-vault
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+18
View File
@@ -0,0 +1,18 @@
when:
- event: pull_request
steps:
- name: pre-commit
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
commands:
- uvx pre-commit run --all-files
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+22 -16
View File
@@ -1,29 +1,35 @@
.PHONY: init plan apply format .PHONY: init plan apply format
#init: VAULT_AUTH_METHOD ?= approle
# @echo "Sourcing environment and initializing Terraform..." VAULT_K8S_ROLE ?= woodpecker_terraform_vault
# @source ./env && terraform init VAULT_K8S_MOUNT ?= auth/k8s/au/syd1
# VAULT_K8S_JWT_PATH ?= /var/run/secrets/kubernetes.io/serviceaccount/token
#plan:
# @echo "Sourcing environment and planning Terraform changes..."
# @source ./env && terraform plan
#
#apply:
# @echo "Sourcing environment and applying Terraform changes..."
# @source ./env && terraform apply -auto-approve
# Define vault_env function to set up vault environment
define vault_env
@export VAULT_ADDR="https://vault.service.consul:8200" && \
if [ "$(VAULT_AUTH_METHOD)" = "kubernetes" ]; then \
export VAULT_TOKEN=$$(vault write -field=token $(VAULT_K8S_MOUNT)/login role=$(VAULT_K8S_ROLE) jwt=$$(cat $(VAULT_K8S_JWT_PATH))); \
else \
export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \
fi && \
export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-vault)
endef
init: init:
@terragrunt run --all --non-interactive init -- -upgrade @$(call vault_env) && \
terragrunt run --all --non-interactive init -- -upgrade
plan: init plan: init
@terragrunt run --all --parallelism 4 --non-interactive plan @$(call vault_env) && \
terragrunt run --all --parallelism 4 --non-interactive plan
apply: init apply: init
@terragrunt run --all --parallelism 2 --non-interactive apply @$(call vault_env) && \
terragrunt run --all --parallelism 2 --non-interactive apply
format: format:
@echo "Formatting Terraform files..." @echo "Formatting OpenTofu files..."
@terraform fmt -recursive . @tofu fmt -recursive .
@echo "Formatting Terragrunt files..." @echo "Formatting Terragrunt files..."
@terragrunt hcl fmt @terragrunt hcl fmt
@@ -1,5 +1,3 @@
token_policies:
- "pki_int/certmanager"
token_ttl: 30 token_ttl: 30
token_max_ttl: 30 token_max_ttl: 30
bind_secret_id: false bind_secret_id: false
@@ -1,6 +1,3 @@
token_policies:
- "default_access"
- "kv/service/incus/incus-cluster-join-tokens"
token_ttl: 60 token_ttl: 60
token_max_ttl: 120 token_max_ttl: 120
bind_secret_id: false bind_secret_id: false
@@ -1,6 +1,3 @@
token_policies:
- "default_access"
- "kv/service/packer/packer_builder"
token_ttl: 300 token_ttl: 300
token_max_ttl: 600 token_max_ttl: 600
bind_secret_id: false bind_secret_id: false
@@ -1,5 +1,3 @@
token_policies:
- "kv/service/puppetapi/puppetapi_read_tokens"
token_ttl: 30 token_ttl: 30
token_max_ttl: 30 token_max_ttl: 30
bind_secret_id: false bind_secret_id: false
@@ -1,6 +1,3 @@
token_policies:
- "kv/service/github/neoloc/tokens/read-only-token/read"
- "kv/service/gitea/unkinben/tokens/read-only-packages/read"
token_ttl: 30 token_ttl: 30
token_max_ttl: 30 token_max_ttl: 30
bind_secret_id: false bind_secret_id: false
@@ -1,5 +1,3 @@
token_policies:
- "rundeck/rundeck"
token_ttl: 3600 token_ttl: 3600
token_max_ttl: 14400 token_max_ttl: 14400
bind_secret_id: true bind_secret_id: true
@@ -1,6 +1,3 @@
token_policies:
- "ssh-host-signer/sshsigner"
- "sshca_signhost"
token_ttl: 30 token_ttl: 30
token_max_ttl: 30 token_max_ttl: 30
bind_secret_id: false bind_secret_id: false
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -1,7 +1,3 @@
token_policies:
- "default_access"
- "kv/service/terraform/incus"
- "kv/service/puppet/certificates/terraform_puppet_cert"
token_ttl: 60 token_ttl: 60
token_max_ttl: 120 token_max_ttl: 120
bind_secret_id: false bind_secret_id: false
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -0,0 +1,9 @@
token_ttl: 60
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -1,6 +1,3 @@
token_policies:
- "default_access"
- "kv/service/terraform/nomad"
token_ttl: 60 token_ttl: 60
token_max_ttl: 120 token_max_ttl: 120
bind_secret_id: false bind_secret_id: false
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -1,7 +1,3 @@
token_policies:
- "default_access"
- "kv/service/repoflow/unkinadmin/tokens/terraform/read"
- "kv/service/terraform/repoflow"
token_ttl: 60 token_ttl: 60
token_max_ttl: 120 token_max_ttl: 120
bind_secret_id: false bind_secret_id: false
@@ -0,0 +1,9 @@
token_ttl: 120
token_max_ttl: 120
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
- "198.18.25.102/32"
- "198.18.26.91/32"
- "198.18.27.40/32"
use_deterministic_role_id: true
@@ -1,27 +1,3 @@
token_policies:
- "default_access"
- "approle_token_create"
- "auth/approle/approle_role_admin"
- "auth/approle/approle_role_login"
- "auth/kubernetes/k8s_auth_admin"
- "auth/ldap/ldap_admin"
- "auth/token/auth_token_create"
- "auth/token/auth_token_self"
- "auth/token/auth_token_roles_admin"
- "kubernetes/au/config_admin"
- "kubernetes/au/roles_admin"
- "kv/service/glauth/services/svc_vault_read"
- "kv/service/kubernetes/au/syd1/token_reviewer_jwt/read"
- "kv/service/kubernetes/au/syd1/service_account_jwt/read"
- "kv/service/vault/auth_backends_read"
- "pki_int/pki_int_roles_admin"
- "pki_root/pki_root_roles_admin"
- "ssh-host-signer/ssh-host-signer_roles_admin"
- "sshca/sshca_roles_admin"
- "sys/sys_auth_admin"
- "sys/sys_mounts_admin"
- "sys/sys_policy_admin"
- "transit/keys/admin"
token_ttl: 60 token_ttl: 60
token_max_ttl: 120 token_max_ttl: 120
bind_secret_id: false bind_secret_id: false
@@ -1,5 +1,5 @@
kubernetes_host: https://api-k8s.service.consul:6443 kubernetes_host: https://api-k8s.service.consul:6443
disable_iss_validation: true disable_iss_validation: true
use_annotations_as_alias_metadata: true use_annotations_as_alias_metadata: false # doesnt work with openbao yet
default_lease_ttl: 1h default_lease_ttl: 1h
max_lease_ttl: 24h max_lease_ttl: 24h
@@ -0,0 +1,7 @@
bound_service_account_names:
- default
bound_service_account_namespaces:
- artifactapi
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -4,8 +4,6 @@ bound_service_account_names:
bound_service_account_namespaces: bound_service_account_namespaces:
- csi-cephrbd - csi-cephrbd
- csi-cephfs - csi-cephfs
token_ttl: 60 token_ttl: 600
token_policies: token_max_ttl: 600
- kv/service/kubernetes/au/syd1/csi/ceph-rbd-secret/read
- kv/service/kubernetes/au/syd1/csi/ceph-cephfs-secret/read
audience: vault audience: vault
@@ -2,8 +2,6 @@ bound_service_account_names:
- cert-manager-vault-issuer - cert-manager-vault-issuer
bound_service_account_namespaces: bound_service_account_namespaces:
- cert-manager - cert-manager
token_ttl: 60 token_ttl: 600
token_policies: token_max_ttl: 600
- pki_int/sign/servers_default
- pki_int/issue/servers_default
audience: vault audience: vault
@@ -0,0 +1,6 @@
bound_service_account_names:
- default
bound_service_account_namespaces: ['*']
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -2,7 +2,6 @@ bound_service_account_names:
- externaldns - externaldns
bound_service_account_namespaces: bound_service_account_namespaces:
- externaldns - externaldns
token_ttl: 60 token_ttl: 600
token_policies: token_max_ttl: 600
- kv/service/kubernetes/au/syd1/externaldns/tsig/read
audience: vault audience: vault
@@ -2,8 +2,5 @@ bound_service_account_names:
- default - default
bound_service_account_namespaces: bound_service_account_namespaces:
- huntarr - huntarr
token_ttl: 60 token_ttl: 600
token_policies:
- pki_int/sign/servers_default
- pki_int/issue/servers_default
audience: vault audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- default
bound_service_account_namespaces:
- identity
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -2,8 +2,6 @@ bound_service_account_names:
- media-apps-vault-reader - media-apps-vault-reader
bound_service_account_namespaces: bound_service_account_namespaces:
- media-apps - media-apps
token_ttl: 60 token_ttl: 600
token_policies: token_max_ttl: 600
- kv/service/media-apps/radarr/read
- kv/service/media-apps/sonarr/read
audience: vault audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- default
bound_service_account_namespaces:
- puppet
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- rancher
bound_service_account_namespaces:
- cattle-system
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -2,11 +2,6 @@ bound_service_account_names:
- default - default
bound_service_account_namespaces: bound_service_account_namespaces:
- repoflow - repoflow
token_ttl: 60 token_ttl: 600
token_policies: token_max_ttl: 600
- kv/service/repoflow/au/syd1/ceph-s3/read
- kv/service/repoflow/au/syd1/elasticsearch/read
- kv/service/repoflow/au/syd1/hasura/read
- kv/service/repoflow/au/syd1/postgres/read
- kv/service/repoflow/au/syd1/repoflow-server/read
audience: vault audience: vault
@@ -0,0 +1,8 @@
# rpmbuilder is deployed in woodpeckerci
bound_service_account_names:
- default
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- default
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-artifactapi
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-authentik
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-git
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-prowlarr
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-radarr
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-rancher
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-sonarr
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-vault
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -0,0 +1,3 @@
---
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
description: foo
@@ -0,0 +1,3 @@
---
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
description: foo
@@ -0,0 +1,3 @@
---
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
description: foo
@@ -1,2 +0,0 @@
policies:
- "default_access"
+3 -3
View File
@@ -1,3 +1,3 @@
policies: ---
- "default_access" # this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
- "global-admin" description: foo
+35 -2
View File
@@ -169,7 +169,7 @@ locals {
} }
consul_secret_backend = { consul_secret_backend = {
for file_path, content in local.all_configs : for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content trimsuffix(replace(file_path, "consul_secret_backend/", ""), ".yaml") => content
if startswith(file_path, "consul_secret_backend/") if startswith(file_path, "consul_secret_backend/")
} }
consul_secret_backend_role = { consul_secret_backend_role = {
@@ -185,5 +185,38 @@ locals {
trimsuffix(basename(file_path), ".yaml") => content trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "pki_mount_only/") if startswith(file_path, "pki_mount_only/")
} }
litellm_secret_backend = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "litellm_secret_backend/")
}
litellm_secret_backend_role = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "litellm_secret_backend_role/", ""), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
backend = dirname(replace(file_path, "litellm_secret_backend_role/", ""))
})
if startswith(file_path, "litellm_secret_backend_role/")
}
plugins = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
})
if startswith(file_path, "plugins/")
}
gpg_secret_backend = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "gpg_secret_backend/")
}
gpg_key = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "gpg_key/", ""), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
backend = dirname(replace(file_path, "gpg_key/", ""))
})
if startswith(file_path, "gpg_key/")
}
} }
} }
@@ -0,0 +1,7 @@
description: "consul secret engine for au-syd1 cluster"
default_lease_ttl_seconds: 600
max_lease_ttl_seconds: 86400
address: "consul.service.au-syd1.consul"
scheme: https
bootstrap: false
datacenter: au-syd1
@@ -0,0 +1,5 @@
consul_roles:
- terraform-artifactapi
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-authentik
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-git
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-incus
ttl: 300
max_ttl: 600
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-k8s
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-ldap
ttl: 60
max_ttl: 60
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-nomad
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-prowlarr
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-radarr
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-rancher
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-repoflow
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-sonarr
ttl: 120
max_ttl: 300
datacenters: []
@@ -0,0 +1,5 @@
consul_roles:
- terraform-vault
ttl: 120
max_ttl: 300
datacenters: []
+7
View File
@@ -0,0 +1,7 @@
# config/gpg_key/gpg/pass.yaml
# An OpenPGP key in the gpg engine for password-store (passv). The private key
# stays in Vault; clients import the exported public key to encrypt and delegate
# decryption to gpg/decrypt/pass. Key name = "pass", backend = "gpg".
algorithm: rsa-4096
identity: "pass <pass@unkin.net>"
exportable: false
+4
View File
@@ -0,0 +1,4 @@
# config/gpg_secret_backend/gpg.yaml
# Mounts the gpg secrets engine at "gpg". The plugin itself is registered in the
# catalog separately (see config/plugins/vault-plugin-secrets-gpg.yaml).
description: "GPG/OpenPGP secrets engine (sign/verify/encrypt/decrypt)"
@@ -1,4 +1,3 @@
backend: "kubernetes/au/syd1"
allowed_kubernetes_namespaces: allowed_kubernetes_namespaces:
- "*" - "*"
kubernetes_role_type: "ClusterRole" kubernetes_role_type: "ClusterRole"
@@ -1,4 +1,3 @@
backend: "kubernetes/au/syd1"
allowed_kubernetes_namespaces: allowed_kubernetes_namespaces:
- "*" - "*"
kubernetes_role_type: "ClusterRole" kubernetes_role_type: "ClusterRole"
@@ -1,4 +1,3 @@
backend: "kubernetes/au/syd1"
allowed_kubernetes_namespaces: allowed_kubernetes_namespaces:
- "*" - "*"
kubernetes_role_type: "ClusterRole" kubernetes_role_type: "ClusterRole"
@@ -1,4 +1,3 @@
backend: "kubernetes/au/syd1"
allowed_kubernetes_namespaces: allowed_kubernetes_namespaces:
- "media-apps" - "media-apps"
kubernetes_role_type: "Role" kubernetes_role_type: "Role"
@@ -0,0 +1,6 @@
# Mounts the LiteLLM dynamic secrets engine at "litellm" and writes its config.
# The master key is sensitive and read from KV, not stored here:
# kv/service/vault/au/syd1/secret_backend/litellm -> key "master_key"
description: "LiteLLM dynamic virtual keys"
base_url: "https://litellm.k8s.syd1.au.unkin.net"
request_timeout_seconds: 30
@@ -0,0 +1,9 @@
---
models:
- claude-opus-4-7
max_budget: 5
ttl: 3600 # seconds (1h)
max_ttl: 86400 # seconds (24h)
metadata:
team: testuser
env: prod
@@ -0,0 +1,10 @@
---
models:
- claude-haiku-4-5
- claude-sonnet-4-6
max_budget: 50
ttl: 3600 # seconds (1h)
max_ttl: 86400 # seconds (24h)
metadata:
team: mailfiltering
env: prod
@@ -1,4 +1,3 @@
backend: "pki/au/syd1"
allow_ip_sans: true allow_ip_sans: true
allowed_domains: allowed_domains:
- "unkin.net" - "unkin.net"
@@ -1,4 +1,3 @@
backend: "pki_int"
allow_ip_sans: true allow_ip_sans: true
allowed_domains: allowed_domains:
- "unkin.net" - "unkin.net"
@@ -1,4 +1,3 @@
backend: "pki_root"
allow_ip_sans: true allow_ip_sans: true
allowed_domains: allowed_domains:
- "unkin.net" - "unkin.net"
@@ -0,0 +1,10 @@
# config/plugins/vault-plugin-secrets-gpg.yaml
# Imports (registers) the gpg secrets plugin in the catalog. Filename = catalog
# name = mount type. The binary is installed on the OpenBao nodes by Puppet
# (openbao-plugin-secrets-gpg RPM -> /opt/openbao-plugins/vault-plugin-secrets-gpg).
#
# sha256 pins the released v0.1.0 binary; bump it in lockstep with any RPM
# upgrade or OpenBao will refuse to launch the plugin.
type: secret
command: vault-plugin-secrets-gpg
sha256: "0e92d7408795688badb55789bc1604e8f1dd4d71998656c7f831991fce9a7b20"
+23
View File
@@ -13,6 +13,11 @@ include "policies" {
expose = true expose = true
} }
include "resources" {
path = "${get_repo_root()}/resources/resources.hcl"
expose = true
}
locals { locals {
# Extract country and region from path # Extract country and region from path
path_parts = split("/", dirname(get_terragrunt_dir())) path_parts = split("/", dirname(get_terragrunt_dir()))
@@ -24,6 +29,16 @@ locals {
# Include policies from policies.hcl # Include policies from policies.hcl
policies = include.policies.locals policies = include.policies.locals
# Include resources from resources.hcl
resources = include.resources.locals
# Create sanitized backend name mapping for Consul providers
# Provider aliases can't contain slashes, so replace them with underscores
consul_backend_aliases = {
for backend_name, _ in local.config.consul_secret_backend :
backend_name => replace(backend_name, "/", "_")
}
} }
terraform { terraform {
@@ -53,8 +68,16 @@ inputs = {
kubernetes_secret_backend = local.config.kubernetes_secret_backend kubernetes_secret_backend = local.config.kubernetes_secret_backend
kubernetes_secret_backend_role = local.config.kubernetes_secret_backend_role kubernetes_secret_backend_role = local.config.kubernetes_secret_backend_role
pki_mount_only = local.config.pki_mount_only pki_mount_only = local.config.pki_mount_only
litellm_secret_backend = local.config.litellm_secret_backend
litellm_secret_backend_role = local.config.litellm_secret_backend_role
plugins = local.config.plugins
gpg_secret_backend = local.config.gpg_secret_backend
gpg_key = local.config.gpg_key
# Pass policy maps to vault_cluster module # Pass policy maps to vault_cluster module
policy_auth_map = local.policies.policy_auth_map policy_auth_map = local.policies.policy_auth_map
policy_rules_map = local.policies.policy_rules_map policy_rules_map = local.policies.policy_rules_map
# Pass sanitized consul backend aliases for provider configuration
consul_backend_aliases = local.consul_backend_aliases
} }
+24 -13
View File
@@ -3,27 +3,26 @@ generate "backend" {
path = "backend.tf" path = "backend.tf"
if_exists = "overwrite" if_exists = "overwrite"
contents = <<EOF contents = <<EOF
#-------------------------------------------
# locals
#-------------------------------------------
locals { locals {
vault_addr = "https://vault.service.consul:8200" vault_addr = "https://vault.service.consul:8200"
} }
#-----------------------------------------------------------------------------
# Configure this provider through the environment variables:
# - VAULT_ADDR
# - VAULT_TOKEN
#-----------------------------------------------------------------------------
provider "vault" { provider "vault" {
address = local.vault_addr address = local.vault_addr
} }
#------------------------------------------------------------------------------ # The LiteLLM secrets engine is managed through its own provider, which talks to
# Use remote state file and encrypt it since your state files may contains # the same Vault server. Token falls back to the VAULT_TOKEN environment variable.
# sensitive data. provider "litellm" {
# export CONSUL_HTTP_TOKEN=<your-token> address = local.vault_addr
#------------------------------------------------------------------------------ }
# The gpg secrets engine's keys are managed through its own provider (same Vault
# server; token falls back to VAULT_TOKEN).
provider "gpg" {
address = local.vault_addr
}
terraform { terraform {
backend "consul" { backend "consul" {
address = "https://consul.service.consul" address = "https://consul.service.consul"
@@ -38,6 +37,18 @@ terraform {
source = "hashicorp/vault" source = "hashicorp/vault"
version = "5.6.0" version = "5.6.0"
} }
consul = {
source = "hashicorp/consul"
version = "2.23.0"
}
litellm = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/litellmvaultsecret"
version = "0.1.0"
}
gpg = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/gpgvaultsecret"
version = "0.1.0"
}
} }
} }
EOF EOF
+11
View File
@@ -0,0 +1,11 @@
rule "terraform_required_providers" {
enabled = false
}
rule "terraform_required_version" {
enabled = false
}
rule "terraform_unused_declarations" {
enabled = false
}
+103 -12
View File
@@ -3,8 +3,6 @@ module "auth_approle_backend" {
for_each = var.auth_approle_backend for_each = var.auth_approle_backend
country = var.country
region = var.region
path = each.key path = each.key
listing_visibility = each.value.listing_visibility listing_visibility = each.value.listing_visibility
default_lease_ttl = each.value.default_lease_ttl default_lease_ttl = each.value.default_lease_ttl
@@ -61,7 +59,7 @@ module "auth_ldap_group" {
groupname = each.value.groupname groupname = each.value.groupname
backend = each.value.backend backend = each.value.backend
policies = each.value.policies policies = var.policy_auth_map[each.value.backend][each.value.groupname]
depends_on = [module.auth_ldap_backend] depends_on = [module.auth_ldap_backend]
} }
@@ -92,6 +90,7 @@ module "auth_kubernetes_role" {
bound_service_account_names = each.value.bound_service_account_names bound_service_account_names = each.value.bound_service_account_names
bound_service_account_namespaces = each.value.bound_service_account_namespaces bound_service_account_namespaces = each.value.bound_service_account_namespaces
token_ttl = each.value.token_ttl token_ttl = each.value.token_ttl
token_max_ttl = each.value.token_max_ttl
token_policies = var.policy_auth_map[each.value.backend][each.value.role_name] token_policies = var.policy_auth_map[each.value.backend][each.value.role_name]
audience = each.value.audience audience = each.value.audience
@@ -185,7 +184,6 @@ module "pki_secret_backend" {
crl_distribution_points = each.value.crl_distribution_points crl_distribution_points = each.value.crl_distribution_points
ocsp_servers = each.value.ocsp_servers ocsp_servers = each.value.ocsp_servers
enable_templating = each.value.enable_templating enable_templating = each.value.enable_templating
default_issuer_ref = each.value.default_issuer_ref
default_follows_latest_issuer = each.value.default_follows_latest_issuer default_follows_latest_issuer = each.value.default_follows_latest_issuer
crl_expiry = each.value.crl_expiry crl_expiry = each.value.crl_expiry
crl_disable = each.value.crl_disable crl_disable = each.value.crl_disable
@@ -237,19 +235,41 @@ module "consul_secret_backend" {
max_lease_ttl_seconds = each.value.max_lease_ttl_seconds max_lease_ttl_seconds = each.value.max_lease_ttl_seconds
} }
# Create data sources for consul backend tokens
data "vault_kv_secret_v2" "consul_backend_configs" {
for_each = {
for k, v in var.consul_secret_backend : k => v
if !v.bootstrap
}
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${each.key}"
}
# Create Consul ACL management module
module "consul_acl_management" {
source = "./modules/consul_acl_management"
country = var.country
region = var.region
consul_backends = var.consul_secret_backend
consul_roles = var.consul_secret_backend_role
consul_backend_aliases = var.consul_backend_aliases
}
# Create consul secret backend roles (Vault resources only)
module "consul_secret_backend_role" { module "consul_secret_backend_role" {
source = "./modules/consul_secret_backend_role" source = "./modules/consul_secret_backend_role"
for_each = var.consul_secret_backend_role for_each = var.consul_secret_backend_role
name = each.value.name name = each.value.name
backend = each.value.backend backend = each.value.backend
consul_roles = each.value.consul_roles ttl = each.value.ttl
ttl = each.value.ttl max_ttl = each.value.max_ttl
max_ttl = each.value.max_ttl local = each.value.local
local = each.value.local
depends_on = [module.consul_secret_backend] depends_on = [module.consul_secret_backend, module.consul_acl_management]
} }
module "kubernetes_secret_backend" { module "kubernetes_secret_backend" {
@@ -283,6 +303,77 @@ module "kubernetes_secret_backend_role" {
depends_on = [module.kubernetes_secret_backend] depends_on = [module.kubernetes_secret_backend]
} }
module "litellm_secret_backend" {
source = "./modules/litellm_secret_backend"
for_each = var.litellm_secret_backend
country = var.country
region = var.region
path = each.key
plugin = each.value.plugin
description = each.value.description
base_url = each.value.base_url
request_timeout_seconds = each.value.request_timeout_seconds
}
module "litellm_secret_backend_role" {
source = "./modules/litellm_secret_backend_role"
for_each = var.litellm_secret_backend_role
name = each.value.name
backend = each.value.backend
models = each.value.models
max_budget = each.value.max_budget
key_alias_prefix = each.value.key_alias_prefix
ttl = each.value.ttl
max_ttl = each.value.max_ttl
metadata = each.value.metadata
depends_on = [module.litellm_secret_backend]
}
module "plugin" {
source = "./modules/plugin"
for_each = var.plugins
name = each.value.name
type = each.value.type
command = each.value.command
sha256 = each.value.sha256
plugin_version = each.value.version
}
module "gpg_secret_backend" {
source = "./modules/gpg_secret_backend"
for_each = var.gpg_secret_backend
path = each.key
plugin = each.value.plugin
description = each.value.description
depends_on = [module.plugin]
}
module "gpg_key" {
source = "./modules/gpg_key"
for_each = var.gpg_key
backend = each.value.backend
name = each.value.name
algorithm = each.value.algorithm
identity = each.value.identity
exportable = each.value.exportable
deletion_allowed = each.value.deletion_allowed
min_decryption_version = each.value.min_decryption_version
depends_on = [module.gpg_secret_backend]
}
module "vault_policy" { module "vault_policy" {
source = "./modules/vault_policy" source = "./modules/vault_policy"
@@ -300,7 +391,6 @@ module "pki_mount_only" {
path = each.key path = each.key
description = each.value.description description = each.value.description
max_lease_ttl_seconds = each.value.max_lease_ttl_seconds max_lease_ttl_seconds = each.value.max_lease_ttl_seconds
issuer_ref = each.value.issuer_ref
issuing_certificates = each.value.issuing_certificates issuing_certificates = each.value.issuing_certificates
crl_distribution_points = each.value.crl_distribution_points crl_distribution_points = each.value.crl_distribution_points
ocsp_servers = each.value.ocsp_servers ocsp_servers = each.value.ocsp_servers
@@ -314,3 +404,4 @@ module "pki_mount_only" {
enable_delta = each.value.enable_delta enable_delta = each.value.enable_delta
delta_rebuild_interval = each.value.delta_rebuild_interval delta_rebuild_interval = each.value.delta_rebuild_interval
} }
@@ -8,4 +8,4 @@ resource "vault_auth_backend" "approle" {
max_lease_ttl = var.max_lease_ttl max_lease_ttl = var.max_lease_ttl
listing_visibility = var.listing_visibility listing_visibility = var.listing_visibility
} }
} }
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,13 +1,3 @@
variable "country" {
description = "Country identifier"
type = string
}
variable "region" {
description = "Region identifier"
type = string
}
variable "path" { variable "path" {
description = "Mount path of the AppRole auth backend" description = "Mount path of the AppRole auth backend"
type = string type = string
@@ -34,4 +24,4 @@ variable "max_lease_ttl" {
description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string" description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string"
type = string type = string
default = null default = null
} }
@@ -16,7 +16,7 @@ data "vault_kv_secret_v2" "role_config" {
locals { locals {
salt = data.vault_kv_secret_v2.salt_config.data["salt"] salt = data.vault_kv_secret_v2.salt_config.data["salt"]
role_id_input = "${local.salt}-${var.approle_name}-${var.mount_path}" role_id_input = "${local.salt}-${var.approle_name}-${var.mount_path}"
deterministic_role_id = uuidv5("dns", "${local.role_id_input}") deterministic_role_id = uuidv5("dns", local.role_id_input)
# Use deterministic role-id by default, or read from KV if specified # Use deterministic role-id by default, or read from KV if specified
role_id = var.use_deterministic_role_id ? local.deterministic_role_id : data.vault_kv_secret_v2.role_config[0].data["role_id"] role_id = var.use_deterministic_role_id ? local.deterministic_role_id : data.vault_kv_secret_v2.role_config[0].data["role_id"]
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -4,6 +4,7 @@ resource "vault_kubernetes_auth_backend_role" "role" {
bound_service_account_names = var.bound_service_account_names bound_service_account_names = var.bound_service_account_names
bound_service_account_namespaces = var.bound_service_account_namespaces bound_service_account_namespaces = var.bound_service_account_namespaces
token_ttl = var.token_ttl token_ttl = var.token_ttl
token_max_ttl = var.token_max_ttl
token_policies = var.token_policies token_policies = var.token_policies
audience = var.audience audience = var.audience
} }
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -24,6 +24,12 @@ variable "token_ttl" {
default = 3600 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 = 86400
}
variable "token_policies" { variable "token_policies" {
description = "List of policies to assign to the role (passed from policy_auth_map)" description = "List of policies to assign to the role (passed from policy_auth_map)"
type = list(string) type = list(string)
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -0,0 +1,7 @@
rule "terraform_required_providers" {
enabled = false
}
rule "terraform_required_version" {
enabled = false
}
@@ -0,0 +1,58 @@
# Get consul backend tokens from Vault
data "vault_kv_secret_v2" "consul_backend_configs" {
for_each = var.consul_backends
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${each.key}"
}
# Create consul provider instances for each consul backend
provider "consul" {
alias = "by_backend"
for_each = var.consul_backend_aliases
address = var.consul_backends[each.key].address
scheme = var.consul_backends[each.key].scheme
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
token = data.vault_kv_secret_v2.consul_backend_configs[each.key].data["token"]
}
# Create Consul ACL policies
resource "consul_acl_policy" "policies" {
for_each = var.consul_roles
provider = consul.by_backend[each.value.backend]
name = each.value.name
description = each.value.description != null ? each.value.description : "Auto-generated policy for Vault role ${each.value.name}"
rules = file("${path.module}/../../../../../../../../resources/secret_backend/${each.value.backend}/${each.value.name}.hcl")
datacenters = each.value.datacenters
}
# Create Consul ACL roles
resource "consul_acl_role" "roles" {
for_each = var.consul_roles
provider = consul.by_backend[each.value.backend]
name = each.value.name
description = each.value.description != null ? each.value.description : "Auto-generated role for Vault role ${each.value.name}"
policies = [consul_acl_policy.policies[each.key].name]
dynamic "service_identities" {
for_each = each.value.service_identities != null ? each.value.service_identities : []
content {
service_name = service_identities.value.service_name
datacenters = service_identities.value.datacenters
}
}
dynamic "node_identities" {
for_each = each.value.node_identities != null ? each.value.node_identities : []
content {
node_name = node_identities.value.node_name
datacenter = node_identities.value.datacenter
}
}
}
@@ -0,0 +1,9 @@
output "consul_acl_policies" {
description = "Map of created Consul ACL policies"
value = consul_acl_policy.policies
}
output "consul_acl_roles" {
description = "Map of created Consul ACL roles"
value = consul_acl_role.roles
}
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
consul = {
source = "hashicorp/consul"
version = "2.23.0"
}
}
}
@@ -0,0 +1,46 @@
variable "consul_backends" {
description = "Map of consul secret backends"
type = map(object({
address = string
scheme = string
bootstrap = bool
bootstrap_token = optional(string)
ca_cert = optional(string)
client_cert = optional(string)
client_key = optional(string)
}))
}
variable "consul_roles" {
description = "Map of consul secret backend roles"
type = map(object({
name = string
backend = string
description = optional(string)
datacenters = optional(list(string))
service_identities = optional(list(object({
service_name = string
datacenters = optional(list(string))
})))
node_identities = optional(list(object({
node_name = string
datacenter = string
})))
}))
}
variable "consul_backend_aliases" {
description = "Map of consul backend names to sanitized provider aliases"
type = map(string)
default = {}
}
variable "country" {
description = "Country identifier"
type = string
}
variable "region" {
description = "Region identifier"
type = string
}

Some files were not shown because too many files have changed in this diff Show More