10 Commits

Author SHA1 Message Date
unkinben ea380b9417 Add cert-manager clouddns KV read access for VSO
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
cert-manager needs a Google Cloud DNS service-account key to solve
Let's Encrypt DNS-01 challenges for publicly-trusted certs. VSO syncs it
from Vault KV, so the cert-manager namespace needs its own k8s auth role
and a policy granting read on the KV path.

- Add k8s auth role cert_manager_clouddns bound to SA
  cert-manager-clouddns in the cert-manager namespace.
- Add policy granting read on
  kv/service/kubernetes/au/syd1/cert-manager/clouddns, bound to that role.

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
2026-08-02 17:04:32 +10:00
unkinben c0cc74927c Add logarchive gpg key + logging_logarchiver read access (#106)
ci/woodpecker/push/apply Pipeline was successful
## Why

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

## Changes

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

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

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

Co-authored-by: benvin <neotheo@gmail.com>
Reviewed-on: #106
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-29 20:39:41 +10:00
unkinben 31f32aba0f gitea roles: add read:user scope for API login validation (#105)
ci/woodpecker/push/apply Pipeline was successful
End-to-end verification of the freshly-applied gitea engine (mint → API call → revoke) surfaced that tokens without read:user get 403 from GET /api/v1/user — the endpoint tea and most Gitea API clients use to validate a login. teabot's personalities would fail their auth check with the current scope sets, while in-scope calls (repo/issue) already work and lease revocation correctly kills tokens (verified 401 after revoke).

- add read:user to the teabot-implementer role scopes
- add read:user to the teabot-reviewer role scopes

Reviewed-on: #105
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-28 18:07:29 +10:00
unkinben 96a6a7d728 gitea: add the gitea token secrets engine (mount, config, teabot roles) (#101)
ci/woodpecker/push/apply Pipeline was successful
## Why

teabot's implementer and reviewer bot users should mint **ephemeral, scoped Gitea tokens** on demand rather than holding standing personal access tokens — Gitea tokens never expire on their own, so a leak lasts until someone notices. This registers and mounts the new `vault-plugin-secrets-gitea` engine (released v0.1.0) and declares its roles, mirroring the rancher engine wiring.

## Change

- Register the plugin in the catalog (`config/plugins/vault-plugin-secrets-gitea.yaml`), pinned to the released v0.1.0 binary `sha256 8f67fbc216effada5fd7399888a710b62fad83be0b31761a439e7dec3d56509b` (sha256 of `/opt/openbao-plugins/vault-plugin-secrets-gitea` from the released `openbao-plugin-secrets-gitea-0.1.0` RPM).
- Add `gitea_secret_backend` + `gitea_secret_backend_role` modules and wire them through `config.hcl`, `environments/au/syd1/terragrunt.hcl`, and `modules/vault_cluster` variables/main, using the `giteavaultsecret` provider from the `terraform-unkin` registry (v0.1.0).
- Mount the engine at `gitea/` against `https://git.unkin.net`; seeded site-admin credentials are read from KV (`service/vault/au/syd1/secret_backend/gitea/config`, keys `admin_username`/`admin_password`) — not stored in git.
- **The seed is consumed create-only**: `lifecycle ignore_changes` on `admin_username`/`admin_password` means the engine reads the KV seed only when first creating `gitea/config`. After `rotate-root` diverges the live password from the seed, a later apply never pushes the stale seed back.
- Add roles with conservative, minimal scopes (write: implies read:):
  - `teabot-implementer` — `write:repository`, `write:issue` (clone/push, open PRs, comment).
  - `teabot-reviewer` — `read:repository`, `write:issue` (read diffs, post PR reviews/comments).
- TTLs: `ttl` 1h / `max_ttl` 4h on both roles.

## The site-admin bot + KV seed are now provisioned by Terraform (no manual gap)

Per Ben's review, creating the site-admin bot and seeding its credential is no longer a manual step:

- **terraform-git #46** creates the `gitea-vault-admin` site-admin bot and writes its generated password **once** to `kv/service/vault/au/syd1/secret_backend/gitea/config` (create-only KV write; never updated).
- **terraform-vault #102** grants terraform-git write access to that KV path.

## Ordering (merge + apply)

1. **puppet-prod #498** — installs the plugin binary on the vault nodes (Puppet must run).
2. **terraform-vault #100** (`benvin/gitea-deployer-access`) — deployer access to the gitea mount.
3. **terraform-vault #102** (`benvin/gitea-kv-writer`) — terraform-git KV write grant.
4. **terraform-git #46** (`benvin/gitea-vault-admin`) — creates the bot + seeds KV.
5. **This PR** — mounts the engine (reads the seed) and declares roles.

Files here are disjoint from #100 and #102 (no conflict).

**CI note:** the plan for this PR may hard-fail in CI if the plugin isn't yet registered/installed or the KV seed isn't present in the plan's target. If CI plan fails for that ordering reason, that is expected — do not force; apply only once steps 1–4 are live.

## Remaining manual step (one, ordered)

After this PR's first apply, run `vault write -f gitea/config/rotate-root` so the standing seed password is replaced by one only Vault holds. (On future binary upgrades, bump the RPM version in puppet-prod and the catalog `sha256` here together, then `vault write sys/plugins/reload/backend plugin=vault-plugin-secrets-gitea`.)

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
Reviewed-on: #101
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-27 23:42:13 +10:00
unkinben bf9c785281 policies: allow terraform-git to delete the gitea config seed for taint recovery (#104)
ci/woodpecker/push/apply Pipeline was successful
terraform-git's apply (pipeline 108) still fails: pipeline 107 actually wrote the seed but the post-create metadata read 403'd, so terraform tainted the resource — recovery is replace (delete+create), and delete was deliberately not granted. Withholding delete doesn't provide the write-once property anyway (that's lifecycle ignore_changes in terraform-git); it just breaks taint recovery and destroy.

- add delete on the kv data path for the gitea config seed
- add delete on the matching kv metadata path (full destroy support)

After merge+apply, restart the terraform-git apply once more — it will replace the tainted seed and go green, unblocking #101.

Reviewed-on: #104
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-27 23:36:53 +10:00
unkinben d82580f1af policies: grant terraform-git read on the gitea config KV metadata path (#103)
ci/woodpecker/push/apply Pipeline was successful
terraform-git's main apply still fails after the skip_child_token fix (tfgit #48): the vault_kv_secret_v2 seed resource reads the kv-v2 metadata path during plan/apply, and the grant added in #102 covered kv/data only — Vault returns 403 on GET kv/metadata/.../secret_backend/gitea/config (terraform-git pipeline 107). This is the last blocker before the KV seed lands and terraform-vault #101 can apply.

- add read on kv/metadata/service/vault/au/syd1/secret_backend/gitea/config to the terraform-git seed policy

Reviewed-on: #103
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-27 23:21:43 +10:00
unkinben 2c27395613 policies: let terraform-git seed the gitea engine admin credential to KV (#102)
ci/woodpecker/push/apply Pipeline was successful
## Why

terraform-git now provisions the `gitea-vault-admin` site-admin bot and writes its generated password to `kv/service/vault/au/syd1/secret_backend/gitea/config` (as `admin_username` + `admin_password`) so the gitea secrets engine can consume it at creation time. The `woodpecker_terraform_git` / `terraform_git` identity has no write access to that KV path, so its apply would 403 without this grant.

The deployer that *reads* the seed already has read access via `policies/kv/service/vault/secret_backends_read.yaml` (`kv/data/service/vault/+/+/secret_backend/*`), so only the write side is added here.

## Change

- Add `policies/kv/service/vault/au/syd1/secret_backend/gitea/config_write.yaml` granting `create`/`read`/`update` on the gitea config KV path to the `terraform_git` approle and `woodpecker_terraform_git` k8s role.

## Ordering

Merge + apply this before the terraform-git `benvin/gitea-vault-admin` PR applies (which performs the write). Files are disjoint from the other gitea terraform-vault PRs (#100, #101).

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
Reviewed-on: #102
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-27 20:22:40 +10:00
unkinben d289775e38 policies: grant the vault deployer access to the gitea secrets engine (#100)
ci/woodpecker/push/apply Pipeline was successful
## Why

The forthcoming `gitea_secret_backend` + role configuration (separate PR, `benvin/gitea-secret-engine`) is applied by terraform-vault under the deployment identity (`tf_vault` approle / `woodpecker_terraform_vault` k8s role). That identity has no access to the `gitea/` mount yet, so writing the engine's config and roles would 403. This mirrors `policies/rancher/admin.yaml`.

## Change

- Add `policies/gitea/admin.yaml` granting the deployer:
  - create/read/update/delete on `gitea/config`
  - create/update on `gitea/config/rotate-root` (write-only rotation trigger)
  - full manage + list on `gitea/roles/*` (and list on `gitea/roles`)
- Deliberately excludes `gitea/creds/*` — minting tokens is for consumers, not the deployer.
- No new catalog or mount grant: plugin registration is already covered by the shared, sudo-protected wildcard in `policies/sys/plugins/catalog/admin.yaml`, and mounting uses the deployer's existing `sys/mounts/*` access — same as the rancher engine.

## Order

Merge and apply this **before** the `benvin/gitea-secret-engine` PR, so the deployer can write the engine config/roles on that apply.

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
Reviewed-on: #100
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-27 19:10:21 +10:00
unkinben 31424ea6ff ci: fetch vault from artifactapi instead of dnf install (#99)
ci/woodpecker/push/apply Pipeline was successful
## Why

CI installs vault by shelling out to `dnf install vault -y`. That reads
metadata for every enabled repo (appstream/baseos/crb/epel/ha) and downloads
the 169MB vendored vault RPM from the `unkin` repo on **every** plan/apply run
(~39s per job measured in `almalinux9-opentofu:20260606`).

## Change

- Replace `dnf install vault -y` with a pinned `curl` of the upstream vault zip
  from the artifactapi `hashicorp-releases` remote proxy, extracted with the
  image's `python3` (`python3 -m zipfile`) to `/usr/local/bin/vault`.
- Pin the version via a new `VAULT_VERSION` env var (`1.20.0`); bump the var to
  upgrade.

## Speedup

Measured in `git.unkin.net/unkin/almalinux9-opentofu:20260606`:

| approach | time |
|---|---|
| `dnf install vault -y` (current) | ~39s |
| `dnf --disablerepo='*' --enablerepo=unkin` (still pulls 169MB RPM) | ~9s |
| curl zip from artifactapi + python extract (this PR) | ~6.6s |

~32s saved per plan/apply job. The zip is cached by artifactapi after first
fetch (warm ~3s).

## Caveats

- Assumes the `almalinux9-opentofu` image ships `curl` + `python3` (both
  present in `:20260606`).
- Relies on the existing artifactapi `hashicorp-releases` generic remote whose
  patterns already allow `vault/.*vault_.*_linux_amd64.zip`.

---------

Co-authored-by: benvin <neotheo@gmail.com>
Reviewed-on: #99
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-25 09:47:34 +10:00
unkinben 1fa5900787 Add terraform-enc Vault/Consul plumbing + encapi token grant (#98)
ci/woodpecker/push/apply Pipeline was successful
The new **terragrunt-enc** repo manages all encapi ENC data (statuses, roles, node classifications) via Terraform/Terragrunt and needs its own Vault/Consul plumbing, mirroring terraform-git and terraform-incus. This supersedes the dual-write approach in terraform-incus PR #39; the equivalent terraform-incus grant (PR #97) is being closed, so the encapi-token grant is created fresh here for the new approle.

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

Reviewed-on: #98
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-07-24 23:18:56 +10:00
29 changed files with 464 additions and 2 deletions
+2 -1
View File
@@ -7,8 +7,9 @@ steps:
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
VAULT_VERSION: "1.20.0"
commands:
- dnf install vault -y
- curl -fsSL -o /tmp/vault.zip "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/hashicorp-releases/vault/$${VAULT_VERSION}/vault_$${VAULT_VERSION}_linux_amd64.zip" && python3 -m zipfile -e /tmp/vault.zip /tmp/ && install -m0755 /tmp/vault /usr/local/bin/vault && rm -f /tmp/vault.zip /tmp/vault /tmp/LICENSE.txt
- make plan
- make apply
backend_options:
+2 -1
View File
@@ -6,8 +6,9 @@ steps:
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
VAULT_VERSION: "1.20.0"
commands:
- dnf install vault -y
- curl -fsSL -o /tmp/vault.zip "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/hashicorp-releases/vault/$${VAULT_VERSION}/vault_$${VAULT_VERSION}_linux_amd64.zip" && python3 -m zipfile -e /tmp/vault.zip /tmp/ && install -m0755 /tmp/vault /usr/local/bin/vault && rm -f /tmp/vault.zip /tmp/vault /tmp/LICENSE.txt
- make plan
backend_options:
kubernetes:
@@ -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:
- cert-manager-clouddns
bound_service_account_namespaces:
- cert-manager
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- logarchiver
bound_service_account_namespaces:
- logging
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -0,0 +1,7 @@
bound_service_account_names:
- terraform-enc
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
+13
View File
@@ -239,5 +239,18 @@ locals {
})
if startswith(file_path, "rancher_secret_backend_role/")
}
gitea_secret_backend = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "gitea_secret_backend/")
}
gitea_secret_backend_role = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "gitea_secret_backend_role/", ""), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
backend = dirname(replace(file_path, "gitea_secret_backend_role/", ""))
})
if startswith(file_path, "gitea_secret_backend_role/")
}
}
}
@@ -0,0 +1,5 @@
consul_roles:
- terraform-enc
ttl: 120
max_ttl: 300
datacenters: []
+11
View File
@@ -0,0 +1,11 @@
# Mounts the gitea token secrets engine at "gitea" and writes its config.
# The seeded site-admin credentials are sensitive and read from KV, not stored
# here:
# kv/service/vault/au/syd1/secret_backend/gitea/config
# -> keys: admin_username (required), admin_password (required)
# Populate that KV path with a purpose-built Gitea site-admin bot (2FA disabled)
# BEFORE applying, then run `vault write -f gitea/config/rotate-root` after the
# first apply so only Vault holds the admin password.
description: "Gitea ephemeral scoped access token engine"
gitea_url: "https://git.unkin.net"
request_timeout_seconds: 30
@@ -0,0 +1,17 @@
# Role minting ephemeral tokens for the teabot-implementer bot user.
# The implementer clones/pushes code and opens pull requests, so it gets write
# on repositories (clone + push + PR create) and write on issues (PR/issue
# comments). Read is implied by write. No admin/org/user-write scopes.
# read:user is required because tea (and most API clients) validate the login
# via GET /api/v1/user, which 403s without it (verified against a minted token).
# Reading gitea/creds/teabot-implementer mints a lease-bound token deleted from
# Gitea on revoke/expiry.
---
username: teabot-implementer
scopes:
- write:repository
- write:issue
- read:user
token_name_prefix: vault-teabot-implementer
ttl: 3600 # 1h
max_ttl: 14400 # 4h
@@ -0,0 +1,17 @@
# Role minting ephemeral tokens for the teabot-reviewer bot user.
# The reviewer reads code and posts pull-request reviews/comments, so it gets
# read on repositories (fetch diffs) and write on issues (PR reviews + issue/PR
# comments). No repository-write, admin, org, or user-write scopes.
# read:user is required because tea (and most API clients) validate the login
# via GET /api/v1/user, which 403s without it (verified against a minted token).
# Reading gitea/creds/teabot-reviewer mints a lease-bound token deleted from
# Gitea on revoke/expiry.
---
username: teabot-reviewer
scopes:
- read:repository
- write:issue
- read:user
token_name_prefix: vault-teabot-reviewer
ttl: 3600 # 1h
max_ttl: 14400 # 4h
+8
View File
@@ -0,0 +1,8 @@
# config/gpg_key/gpg/logarchive.yaml
# OpenPGP key in the gpg engine for the logarchiver service. The private key
# stays in Vault; logarchiver reads only the exported public key
# (gpg/keys/logarchive) to encrypt archived logs, and retrieval delegates
# decryption back to gpg/decrypt/logarchive. Key name = "logarchive", backend = "gpg".
algorithm: rsa-4096
identity: "logarchive <logarchive@unkin.net>"
exportable: false
@@ -0,0 +1,11 @@
# config/plugins/vault-plugin-secrets-gitea.yaml
# Imports (registers) the gitea secrets plugin in the catalog. Filename =
# catalog name = mount type. The binary is installed on the OpenBao nodes by
# Puppet (openbao-plugin-secrets-gitea RPM ->
# /opt/openbao-plugins/vault-plugin-secrets-gitea).
#
# 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-gitea
sha256: "8f67fbc216effada5fd7399888a710b62fad83be0b31761a439e7dec3d56509b"
+3
View File
@@ -78,6 +78,9 @@ inputs = {
rancher_secret_backend_service_account = local.config.rancher_secret_backend_service_account
rancher_secret_backend_role = local.config.rancher_secret_backend_role
gitea_secret_backend = local.config.gitea_secret_backend
gitea_secret_backend_role = local.config.gitea_secret_backend_role
# Pass policy maps to vault_cluster module
policy_auth_map = local.policies.policy_auth_map
policy_rules_map = local.policies.policy_rules_map
+34
View File
@@ -421,6 +421,40 @@ module "rancher_secret_backend_role" {
depends_on = [module.rancher_secret_backend_service_account]
}
module "gitea_secret_backend" {
source = "./modules/gitea_secret_backend"
for_each = var.gitea_secret_backend
path = each.key
plugin = each.value.plugin
description = each.value.description
gitea_url = each.value.gitea_url
country = var.country
region = var.region
ca_cert = each.value.ca_cert
tls_skip_verify = each.value.tls_skip_verify
request_timeout_seconds = each.value.request_timeout_seconds
depends_on = [module.plugin]
}
module "gitea_secret_backend_role" {
source = "./modules/gitea_secret_backend_role"
for_each = var.gitea_secret_backend_role
backend = each.value.backend
name = each.value.name
username = each.value.username
scopes = each.value.scopes
token_name_prefix = each.value.token_name_prefix
ttl = each.value.ttl
max_ttl = each.value.max_ttl
depends_on = [module.gitea_secret_backend]
}
module "vault_policy" {
source = "./modules/vault_policy"
@@ -0,0 +1,34 @@
# Mounts the gitea secrets engine and writes its connection config via the
# giteavaultsecret provider. The plugin is registered ("imported") in the
# catalog separately (config/plugins/vault-plugin-secrets-gitea.yaml). The
# seeded site-admin credentials are sensitive and read from KV, not stored in
# git:
# kv/service/vault/<country>/<region>/secret_backend/<path>/config
# Expected keys: admin_username (required), admin_password (required).
data "vault_kv_secret_v2" "config" {
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${var.path}/config"
}
resource "gitea_secret_backend" "this" {
path = var.path
plugin = var.plugin
description = var.description
gitea_url = var.gitea_url
admin_username = data.vault_kv_secret_v2.config.data["admin_username"]
admin_password = data.vault_kv_secret_v2.config.data["admin_password"]
ca_cert = var.ca_cert
tls_skip_verify = var.tls_skip_verify
request_timeout_seconds = var.request_timeout_seconds
lifecycle {
# The KV seed is a bootstrap credential: it is consumed only when the engine
# config is first created. After creation the live admin password is rotated
# in place (vault write -f gitea/config/rotate-root) and diverges from the
# seed, so re-reading the (possibly stale) KV value must never push it back.
# Ignoring the credential attributes makes this module create-only for them.
# (The sibling rancher/litellm seed modules do not yet do this and would
# re-push their seed on a subsequent apply.)
ignore_changes = [admin_username, admin_password]
}
}
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
gitea = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/giteavaultsecret"
version = "0.1.0"
}
}
}
@@ -0,0 +1,49 @@
variable "path" {
description = "Mount path of the gitea secrets engine (e.g. \"gitea\")"
type = string
}
variable "plugin" {
description = "Registered plugin name to mount (the catalog name = mount type)"
type = string
default = "vault-plugin-secrets-gitea"
}
variable "description" {
description = "Human-friendly description of the mount"
type = string
default = null
}
variable "gitea_url" {
description = "Base URL of the Gitea server (e.g. https://git.unkin.net)"
type = string
}
variable "country" {
description = "Country segment of the KV path holding the seeded admin credentials"
type = string
}
variable "region" {
description = "Region segment of the KV path holding the seeded admin credentials"
type = string
}
variable "ca_cert" {
description = "PEM CA certificate that signed the Gitea server's TLS cert (optional; omit to use the system trust store)"
type = string
default = null
}
variable "tls_skip_verify" {
description = "Skip TLS verification of the Gitea server (not recommended)"
type = bool
default = false
}
variable "request_timeout_seconds" {
description = "HTTP timeout in seconds for calls from the plugin to Gitea"
type = number
default = 30
}
@@ -0,0 +1,12 @@
# A role that mints short-lived, scoped gitea tokens for a target Gitea user.
# Reading gitea/creds/<name> produces a lease-bound token that is deleted from
# Gitea when the lease is revoked or reaches max_ttl.
resource "gitea_secret_backend_role" "this" {
backend = var.backend
name = var.name
username = var.username
scopes = var.scopes
token_name_prefix = var.token_name_prefix
ttl = var.ttl
max_ttl = var.max_ttl
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
gitea = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/giteavaultsecret"
version = "0.1.0"
}
}
}
@@ -0,0 +1,37 @@
variable "backend" {
description = "Mount path of the gitea secrets engine this role belongs to"
type = string
}
variable "name" {
description = "Role name (read gitea/creds/<name> to mint a token)"
type = string
}
variable "username" {
description = "Target Gitea username the minted tokens belong to"
type = string
}
variable "scopes" {
description = "Gitea access-token scopes granted to minted tokens (write: implies read:)"
type = list(string)
}
variable "token_name_prefix" {
description = "Prefix for the generated Gitea token name (optional)"
type = string
default = null
}
variable "ttl" {
description = "Default lease TTL in seconds for minted tokens"
type = number
default = null
}
variable "max_ttl" {
description = "Maximum lease TTL in seconds for minted tokens"
type = number
default = null
}
+27
View File
@@ -388,6 +388,33 @@ variable "rancher_secret_backend_role" {
default = {}
}
variable "gitea_secret_backend" {
description = "Map of gitea token secret engines to create (mount + config; seeded admin creds read from KV)"
type = map(object({
plugin = optional(string, "vault-plugin-secrets-gitea")
description = optional(string)
gitea_url = string
ca_cert = optional(string)
tls_skip_verify = optional(bool, false)
request_timeout_seconds = optional(number, 30)
}))
default = {}
}
variable "gitea_secret_backend_role" {
description = "Map of gitea token-minting roles to create"
type = map(object({
name = string
backend = string
username = string
scopes = list(string)
token_name_prefix = optional(string)
ttl = optional(number)
max_ttl = optional(number)
}))
default = {}
}
variable "policy_auth_map" {
description = "Map of auth mounts -> auth roles -> policy names"
type = map(map(list(string)))
@@ -0,0 +1,14 @@
# Allow the terragrunt-enc runner to generate credentials for the
# terraform-enc role in consul (used to lock/write its terragrunt state under
# infra/terraform/enc/ on the consul backend).
---
rules:
- path: "consul_root/au/syd1/creds/terraform-enc"
capabilities:
- read
auth:
approle:
- terraform_enc
k8s/au/syd1:
- woodpecker_terraform_enc
+42
View File
@@ -0,0 +1,42 @@
# Allow the vault deployer to manage the gitea token secrets engine: its
# connection config (seeded admin credentials), in-place root rotation, and
# token-minting roles.
#
# Scoped to gitea/* only, and deliberately excludes gitea/creds/* — minting
# tokens is for consumers, not the deployer. The plugin-catalog grant needed to
# import the plugin is the shared, sudo-protected wildcard in
# policies/sys/plugins/catalog/admin.yaml (already covers this plugin), and
# mounting the engine uses the deployer's existing sys/mounts/* access, so no
# new catalog/mount grant is added here (mirrors the rancher engine).
---
rules:
# Engine connection config (Gitea URL, TLS, seeded admin username/password).
- path: "gitea/config"
capabilities:
- create
- read
- update
- delete
# In-place rotation of the seeded admin password (write-only trigger).
- path: "gitea/config/rotate-root"
capabilities:
- create
- update
# Token-minting roles.
- path: "gitea/roles/*"
capabilities:
- create
- read
- update
- delete
- list
- path: "gitea/roles"
capabilities:
- read
- list
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
+12
View File
@@ -0,0 +1,12 @@
# Allow the logarchiver service (logging namespace, SA logarchiver) to read the
# logarchive public key. A plain read on gpg/keys/logarchive returns the armored
# public_key; no decrypt/export capability is granted (decrypt stays operator-only).
---
rules:
- path: "gpg/keys/logarchive"
capabilities:
- read
auth:
k8s/au/syd1:
- logging_logarchiver
@@ -0,0 +1,10 @@
# Allow reading the cert-manager Google Cloud DNS solver service-account key
---
rules:
- path: "kv/data/service/kubernetes/au/syd1/cert-manager/clouddns"
capabilities:
- read
auth:
k8s/au/syd1:
- cert_manager_clouddns
@@ -0,0 +1,14 @@
# Allow the terragrunt-enc runner to read the encapi environment secret
# (ENCAPI_WRITE_TOKEN), so `make apply` can write ENC data (statuses, roles,
# nodes) to encapi via the encapi Terraform provider.
---
rules:
- path: "kv/data/kubernetes/namespace/encapi/default/environment"
capabilities:
- read
auth:
approle:
- terraform_enc
k8s/au/syd1:
- woodpecker_terraform_enc
@@ -0,0 +1,31 @@
# Allow terraform-git to seed (write once) the gitea secrets engine's admin
# credentials. terraform-git creates the gitea-vault-admin site-admin bot and
# writes its generated password here as admin_username + admin_password; the
# vault gitea engine (managed by the tf_vault deployer) reads it at gitea/config
# creation time. Read is already granted to the deployer via
# policies/kv/service/vault/secret_backends_read.yaml, so this only adds the
# write side for terraform-git's own identity.
---
rules:
# delete is required for taint recovery and destroy (the resource got tainted
# by pipeline 107's failed post-create read and replace = delete+create).
# The seed's write-once semantics are enforced by lifecycle ignore_changes in
# terraform-git, not by withholding delete here.
- path: "kv/data/service/vault/au/syd1/secret_backend/gitea/config"
capabilities:
- create
- read
- update
- delete
# vault_kv_secret_v2 also reads the kv-v2 metadata path on every plan/apply
# (403 here broke the terraform-git main apply, pipeline 107).
- path: "kv/metadata/service/vault/au/syd1/secret_backend/gitea/config"
capabilities:
- read
- delete
auth:
approle:
- terraform_git
k8s/au/syd1:
- woodpecker_terraform_git
@@ -0,0 +1,7 @@
key_prefix "infra/terraform/enc/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}