12 Commits

Author SHA1 Message Date
unkinben f7c738fbc2 Manage the litellm plugin via config/plugins (import existing registration)
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
Bring the litellm plugin under terraform management like the gpg one. It was
registered manually before terraform owned the catalog, so its state must be
imported before applying, otherwise apply fails creating an entry that already
exists.

- Add config/plugins/vault-plugin-secrets-litellm.yaml (sha256 = released v0.1.1
  openbao binary; verify against the live catalog on import).

Manual pre-step before apply:
  cd environments/au/syd1
  terragrunt import \
    'module.plugin["vault-plugin-secrets-litellm"].vault_plugin.this' \
    secret/vault-plugin-secrets-litellm
2026-07-17 23:16:32 +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
56 changed files with 678 additions and 0 deletions
@@ -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
@@ -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
@@ -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-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
+20
View File
@@ -185,5 +185,25 @@ 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/")
}
} }
} }
@@ -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-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-sonarr
ttl: 120
max_ttl: 300
datacenters: []
@@ -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
@@ -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"
@@ -0,0 +1,13 @@
# config/plugins/vault-plugin-secrets-litellm.yaml
# Imports (registers) the litellm secrets plugin in the catalog. This plugin was
# registered manually before terraform managed the catalog, so its state must be
# imported before the first apply (see the PR description) — otherwise apply
# tries to create an entry that already exists.
#
# sha256 is the released v0.1.1 openbao binary
# (openbao-plugin-secrets-litellm RPM -> /opt/openbao-plugins/vault-plugin-secrets-litellm),
# which Puppet installs floating. Verify against the live catalog during import
# (`bao read sys/plugins/catalog/secret/vault-plugin-secrets-litellm`).
type: secret
command: vault-plugin-secrets-litellm
sha256: "2263ebcb3498877a87ddcf31a9cbc6efca6b81702a5faecf7fe7e40200ca7a1f"
+3
View File
@@ -68,6 +68,9 @@ 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
# 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
+10
View File
@@ -11,6 +11,12 @@ provider "vault" {
address = local.vault_addr address = local.vault_addr
} }
# The LiteLLM secrets engine is managed through its own provider, which talks to
# the same Vault server. Token falls back to the VAULT_TOKEN environment variable.
provider "litellm" {
address = local.vault_addr
}
terraform { terraform {
backend "consul" { backend "consul" {
address = "https://consul.service.consul" address = "https://consul.service.consul"
@@ -29,6 +35,10 @@ terraform {
source = "hashicorp/consul" source = "hashicorp/consul"
version = "2.23.0" version = "2.23.0"
} }
litellm = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/litellmvaultsecret"
version = "0.1.0"
}
} }
} }
EOF EOF
+43
View File
@@ -303,6 +303,49 @@ 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 "vault_policy" { module "vault_policy" {
source = "./modules/vault_policy" source = "./modules/vault_policy"
@@ -0,0 +1,14 @@
# Expected keys in KV secret: master_key
data "vault_kv_secret_v2" "secret_backend_config" {
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${var.path}"
}
resource "litellm_secret_backend" "this" {
path = var.path
plugin = var.plugin
description = var.description
base_url = var.base_url
master_key = data.vault_kv_secret_v2.secret_backend_config.data["master_key"]
request_timeout_seconds = var.request_timeout_seconds
}
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
litellm = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/litellmvaultsecret"
version = "0.1.0"
}
}
}
@@ -0,0 +1,37 @@
variable "country" {
description = "Country identifier (used to locate the KV secret holding the master key)"
type = string
}
variable "region" {
description = "Region identifier (used to locate the KV secret holding the master key)"
type = string
}
variable "path" {
description = "Mount path of the LiteLLM secrets engine"
type = string
}
variable "plugin" {
description = "Registered plugin name/type to mount"
type = string
default = "vault-plugin-secrets-litellm"
}
variable "description" {
description = "Human-friendly description of the mount"
type = string
default = null
}
variable "base_url" {
description = "Base URL of the LiteLLM proxy (e.g. http://litellm.litellm.svc:4000)"
type = string
}
variable "request_timeout_seconds" {
description = "HTTP timeout in seconds for calls from the plugin to the LiteLLM proxy"
type = number
default = 30
}
@@ -0,0 +1,10 @@
resource "litellm_secret_backend_role" "this" {
backend = var.backend
name = var.name
models = var.models
max_budget = var.max_budget
key_alias_prefix = var.key_alias_prefix
ttl = var.ttl
max_ttl = var.max_ttl
metadata = var.metadata
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
litellm = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/litellmvaultsecret"
version = "0.1.0"
}
}
}
@@ -0,0 +1,45 @@
variable "name" {
description = "Name of the role"
type = string
}
variable "backend" {
description = "Mount path of the LiteLLM secrets engine this role belongs to"
type = string
}
variable "models" {
description = "Models a generated key may access. Empty means unrestricted"
type = list(string)
default = null
}
variable "max_budget" {
description = "Spending limit applied to each generated key. 0 means unlimited"
type = number
default = null
}
variable "key_alias_prefix" {
description = "Prefix for the auto-generated key alias"
type = string
default = null
}
variable "ttl" {
description = "Default lease TTL in seconds for keys generated from this role"
type = number
default = null
}
variable "max_ttl" {
description = "Maximum lease TTL in seconds for keys generated from this role"
type = number
default = null
}
variable "metadata" {
description = "Metadata attached to each generated key"
type = map(string)
default = null
}
@@ -0,0 +1,12 @@
# Registers ("imports") a plugin binary in the Vault/OpenBao plugin catalog so
# it can be mounted. The binary must already exist in the server
# plugin_directory (installed out of band, e.g. by Puppet); `command` is its
# filename there. The sha256 must match the on-disk binary or the server refuses
# to launch the plugin.
resource "vault_plugin" "this" {
type = var.type
name = var.name
command = coalesce(var.command, var.name)
sha256 = var.sha256
version = var.plugin_version
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -0,0 +1,27 @@
variable "name" {
description = "Name to register the plugin under in the catalog (also the mount type)"
type = string
}
variable "type" {
description = "Plugin type: secret, auth or database"
type = string
default = "secret"
}
variable "command" {
description = "Plugin binary filename relative to the server plugin_directory. Defaults to the plugin name."
type = string
default = null
}
variable "sha256" {
description = "SHA-256 of the installed plugin binary; must match the on-disk binary"
type = string
}
variable "plugin_version" {
description = "Optional plugin version to register the catalog entry under"
type = string
default = null
}
+38
View File
@@ -289,6 +289,44 @@ variable "kubernetes_secret_backend_role" {
default = {} default = {}
} }
variable "litellm_secret_backend" {
description = "Map of LiteLLM secret engines to create (mount + config). The master key is read from KV"
type = map(object({
plugin = optional(string, "vault-plugin-secrets-litellm")
description = optional(string)
base_url = string
request_timeout_seconds = optional(number, 30)
}))
default = {}
}
variable "litellm_secret_backend_role" {
description = "Map of LiteLLM roles to create"
type = map(object({
name = string
backend = string
models = optional(list(string))
max_budget = optional(number)
key_alias_prefix = optional(string)
ttl = optional(number)
max_ttl = optional(number)
metadata = optional(map(string))
}))
default = {}
}
variable "plugins" {
description = "Map of plugins to import (register) in the catalog, keyed by catalog name"
type = map(object({
name = string
type = optional(string, "secret")
command = optional(string)
sha256 = string
version = optional(string)
}))
default = {}
}
variable "policy_auth_map" { variable "policy_auth_map" {
description = "Map of auth mounts -> auth roles -> policy names" description = "Map of auth mounts -> auth roles -> policy names"
type = map(map(list(string))) type = map(map(list(string)))
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-artifactapi"
capabilities:
- read
auth:
approle:
- terraform_artifactapi
k8s/au/syd1:
- woodpecker_terraform_artifactapi
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-authentik"
capabilities:
- read
auth:
approle:
- terraform_authentik
k8s/au/syd1:
- woodpecker_terraform_authentik
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-prowlarr"
capabilities:
- read
auth:
approle:
- terraform_prowlarr
k8s/au/syd1:
- woodpecker_terraform_prowlarr
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-radarr"
capabilities:
- read
auth:
approle:
- terraform_radarr
k8s/au/syd1:
- woodpecker_terraform_radarr
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-rancher"
capabilities:
- read
auth:
approle:
- terraform_rancher
k8s/au/syd1:
- woodpecker_terraform_rancher
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-sonarr"
capabilities:
- read
auth:
approle:
- terraform_sonarr
k8s/au/syd1:
- woodpecker_terraform_sonarr
+35
View File
@@ -0,0 +1,35 @@
# Allow the vault deployer to import the gpg plugin and manage its OpenPGP keys.
#
# terraform-vault registers the plugin itself (vault_plugin -> sys/plugins/catalog,
# a sudo-protected path) and manages keys via the gpgvaultsecret provider, so the
# deployer needs catalog access on top of the mount access it already has
# (sys/mounts/*). Without this, apply 403s on the plugin registration and on
# gpg/keys writes.
---
rules:
# Import / register (and deregister) the gpg plugin in the catalog.
- path: "sys/plugins/catalog/secret/vault-plugin-secrets-gpg"
capabilities:
- create
- read
- update
- delete
- sudo
# Manage keys (create/rotate/config/delete) in the gpg mount.
- path: "gpg/keys/*"
capabilities:
- create
- read
- update
- delete
- list
- path: "gpg/keys"
capabilities:
- read
- list
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
@@ -6,5 +6,8 @@ rules:
- read - read
auth: auth:
approle:
- terraform_prowlarr
k8s/au/syd1: k8s/au/syd1:
- media-apps - media-apps
- woodpecker_terraform_prowlarr
@@ -6,5 +6,8 @@ rules:
- read - read
auth: auth:
approle:
- terraform_radarr
k8s/au/syd1: k8s/au/syd1:
- media-apps - media-apps
- woodpecker_terraform_radarr
@@ -6,5 +6,8 @@ rules:
- read - read
auth: auth:
approle:
- terraform_sonarr
k8s/au/syd1: k8s/au/syd1:
- media-apps - media-apps
- woodpecker_terraform_sonarr
@@ -0,0 +1,18 @@
# Allow the Terraform Authentik runner to read:
# - its own Authentik API token (provider auth), and
# - OAuth2/OIDC client secrets that back authentik providers (per target
# service's kv namespace path, via the module's data.vault_kv_secret_v2).
---
rules:
- path: "kv/data/service/terraform/authentik"
capabilities:
- read
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
capabilities:
- read
auth:
approle:
- terraform_authentik
k8s/au/syd1:
- woodpecker_terraform_authentik
@@ -0,0 +1,18 @@
# Allow the Terraform Rancher runner to read:
# - its own Rancher admin API token (rancher2 provider auth), and
# - the OAuth2/OIDC client secret backing the Rancher keycloakoidc AuthConfig
# (same secret Authentik sets on the provider).
---
rules:
- path: "kv/data/service/terraform/rancher"
capabilities:
- read
- path: "kv/data/kubernetes/namespace/cattle-system/default/oauth-credentials"
capabilities:
- read
auth:
approle:
- terraform_rancher
k8s/au/syd1:
- woodpecker_terraform_rancher
+26
View File
@@ -0,0 +1,26 @@
# Allow management of the LiteLLM secrets engine (config and roles)
---
rules:
- path: "litellm/config"
capabilities:
- create
- update
- read
- delete
- path: "litellm/roles/*"
capabilities:
- create
- update
- delete
- read
- list
- path: "litellm/roles"
capabilities:
- read
- list
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/artifactapi/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/authentik/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/prowlarr/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/radarr/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/rancher/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/sonarr/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}