1 Commits

Author SHA1 Message Date
unkin-agent afbea53d94 Grant the puppet k8s role PKI issue and SSH host signing
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
2026-09-22 22:32:52 +10:00
113 changed files with 139 additions and 1911 deletions
-15
View File
@@ -22,18 +22,3 @@ repos:
"-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}",
"-s",
]
- repo: local
hooks:
- id: vault-yaml-tests
name: vault yaml definitions pass their unit tests
entry: python3 -m unittest discover -s tests -t .
language: python
additional_dependencies: [pyyaml, pydantic]
pass_filenames: false
always_run: true
- id: tofu-module-tests
name: tofu module tests pass
entry: sh -c 'for m in auth_jwt_backend auth_jwt_role; do (cd modules/vault_cluster/modules/$m && tofu init -backend=false -input=false >/dev/null && tofu test) || exit 1; done'
language: system
pass_filenames: false
always_run: true
+1 -1
View File
@@ -4,7 +4,7 @@ when:
steps:
- name: apply
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
VAULT_VERSION: "1.20.0"
+1 -1
View File
@@ -3,7 +3,7 @@ when:
steps:
- name: plan
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
VAULT_VERSION: "1.20.0"
+1 -1
View File
@@ -3,7 +3,7 @@ when:
steps:
- name: pre-commit
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
commands:
- uvx pre-commit run --all-files
backend_options:
+1 -5
View File
@@ -1,4 +1,4 @@
.PHONY: init plan apply format test
.PHONY: init plan apply format
VAULT_AUTH_METHOD ?= approle
VAULT_K8S_ROLE ?= woodpecker_terraform_vault
@@ -28,10 +28,6 @@ apply: init
@$(call vault_env) && \
terragrunt run --all --parallelism 2 --non-interactive apply
test:
@uv run --with pyyaml --with pydantic python -m unittest discover -s tests -t .
@for m in auth_jwt_backend auth_jwt_role; do (cd modules/vault_cluster/modules/$$m && tofu init -backend=false -input=false >/dev/null && tofu test) || exit 1; done
format:
@echo "Formatting OpenTofu files..."
@tofu fmt -recursive .
-32
View File
@@ -35,35 +35,3 @@ You can check the Consul KV store by accessing the Consul UI or using the consul
```bash
consul kv get terraform/state
```
# GitHub Actions JWT roles
`auth/jwt/github/iuclabs` trusts GitHub Actions OIDC tokens. One org-wide role, `default` (`config/auth_jwt_role/jwt/github/iuclabs/default.yaml`, the mount's `default_role`, so workflows needn't name it), accepts any IUCLabs repo for `push`, `release`, `schedule` and `pull_request` runs on `refs/heads/*`, `refs/tags/*` or `refs/pull/*/merge`. Role validation rejects any other event (`pull_request_target`, `workflow_run`, comment events), any other ref glob, and globs in any other claim. Fork pull requests can't mint OIDC tokens unless "Send write tokens to workflows from pull requests" is enabled on the repo.
Workflows log in with audience `https://vault.service.consul:8200`:
```yaml
permissions:
id-token: write
contents: read
steps:
- uses: hashicorp/vault-action@v3
with:
url: https://vault.service.consul:8200
method: jwt
path: jwt/github/iuclabs
jwtGithubAudience: https://vault.service.consul:8200
secrets: |
kv/data/github/IUCLabs/<repo>/ref/refs/heads/main/.secrets/deploy token | DEPLOY_TOKEN
```
The role attaches a templated read-only policy, so each run reads only its own KV-v2 secrets:
- `kv/github/<org>/<repo>/event/<event_name>/*` is not ref-scoped: any run of that event in the repo, on any branch or tag a writer pushes, can read it; keep it for secrets every such run may use
- `kv/github/<org>/<repo>/ref/<full ref>/.secrets/*`, e.g. `ref/refs/heads/main/.secrets/`, `ref/refs/tags/v1.2.3/.secrets/`; git refnames can't contain a `.`-prefixed component, so branch `x` can never read branch `x/y`'s secrets
On OpenBao older than the fork's v2.6, a ref path component of exactly `+` acts as a single-segment wildcard in the templated policy. Public IUCLabs repos block such refs via a terraform-git ruleset; private repos are not protected, so a writer in a private repo can read that repo's other refs' secrets.
`user_claim` is `jti`, so every run creates its own Vault identity entity; Vault never garbage-collects these, so entity count grows with CI volume.
Extra policies are granted via a policy yaml `auth: {jwt/github/iuclabs: [default]}`; the KV read policy is attached automatically.
@@ -1,9 +0,0 @@
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,9 +0,0 @@
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 +0,0 @@
# GitHub Actions OIDC for the IUCLabs org; the org-wide role lives in
# config/auth_jwt_role/jwt/github/iuclabs/.
---
oidc_discovery_url: "https://token.actions.githubusercontent.com"
bound_issuer: "https://token.actions.githubusercontent.com"
description: "GitHub Actions OIDC - IUCLabs"
default_role: "default"
@@ -1,22 +0,0 @@
---
bound_audiences: ["https://vault.service.consul:8200"]
bound_claims:
repository_owner: IUCLabs
event_name: push,release,schedule,pull_request
ref: refs/heads/*,refs/tags/*,refs/pull/*/merge
claim_mappings:
repository: repository
repository_owner: repository_owner
ref: ref
ref_type: ref_type
event_name: event_name
workflow: workflow
job_workflow_ref: job_workflow_ref
run_id: run_id
run_attempt: run_attempt
sha: sha
actor: actor
environment: environment
user_claim: jti
token_ttl: 900
token_max_ttl: 3600
@@ -1,10 +0,0 @@
---
# Kubernetes auth role for rspamd: the rspamd service account in the mail
# namespace, used by VaultDynamicSecret to read litellm/creds/rspamd.
bound_service_account_names:
- rspamd
bound_service_account_namespaces:
- mail
token_ttl: 600
token_max_ttl: 600
audience: vault
@@ -1,15 +0,0 @@
bound_service_account_names:
- default
- arrproxy-ci
- autobackup-operator-ci
- ghp
- jellyfin-plugin-sso
- kea-operator-ci
- mediamark-ci
- plugin-docker-buildx
- repospawner-ci
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -1,7 +0,0 @@
bound_service_account_names:
- terraform-binarylane
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
token_max_ttl: 600
audience: https://kubernetes.default.svc.cluster.local
@@ -1,5 +1,5 @@
bound_service_account_names:
- terraform-netbox
- terraform-infra
bound_service_account_namespaces:
- woodpecker
token_ttl: 600
-54
View File
@@ -118,19 +118,6 @@ locals {
})
if startswith(file_path, "auth_oidc_group/")
}
auth_jwt_backend = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "auth_jwt_backend/", ""), ".yaml") => content
if startswith(file_path, "auth_jwt_backend/")
}
auth_jwt_role = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "auth_jwt_role/", ""), ".yaml") => merge(content, {
role_name = trimsuffix(basename(file_path), ".yaml")
backend = dirname(replace(file_path, "auth_jwt_role/", ""))
})
if startswith(file_path, "auth_jwt_role/")
}
auth_kubernetes_backend = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "auth_kubernetes_backend/", ""), ".yaml") => content
@@ -313,47 +300,6 @@ locals {
})
if startswith(file_path, "netbox_secret_backend_role/")
}
identity_oidc = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "identity_oidc/")
}
identity_oidc_key = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "identity_oidc_key/")
}
identity_oidc_role = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "identity_oidc_role/", ""), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
key = dirname(replace(file_path, "identity_oidc_role/", ""))
})
if startswith(file_path, "identity_oidc_role/")
}
identity_oidc_client = {
for file_path, content in local.all_configs :
trimsuffix(replace(file_path, "identity_oidc_client/", ""), ".yaml") => merge(content, {
name = trimsuffix(basename(file_path), ".yaml")
key = dirname(replace(file_path, "identity_oidc_client/", ""))
})
if startswith(file_path, "identity_oidc_client/")
}
identity_oidc_scope = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "identity_oidc_scope/")
}
identity_oidc_assignment = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "identity_oidc_assignment/")
}
identity_oidc_provider = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "identity_oidc_provider/")
}
ghp_secret_backend = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
@@ -1,5 +0,0 @@
consul_roles:
- terraform-binarylane
ttl: 120
max_ttl: 300
datacenters: []
@@ -1,5 +1,5 @@
consul_roles:
- terraform-netbox
- terraform-infra
ttl: 120
max_ttl: 300
datacenters: []
-2
View File
@@ -1,2 +0,0 @@
---
issuer: https://vault.service.consul:8200
@@ -1,2 +0,0 @@
---
algorithm: RS256
-2
View File
@@ -1,2 +0,0 @@
---
algorithm: RS256
@@ -1,3 +0,0 @@
---
client_id: artifactapi
ttl: 900
@@ -1,3 +0,0 @@
---
client_id: artifactapi
ttl: 900
@@ -1,3 +0,0 @@
---
client_id: encapi-testing
ttl: 900
@@ -1,3 +0,0 @@
---
client_id: encapi
ttl: 900
@@ -1,6 +0,0 @@
allowed_kubernetes_namespaces:
- "vm-system"
- "observability"
- "logging"
kubernetes_role_type: "Role"
extra_labels: {}
@@ -1,6 +1,7 @@
---
models:
- claude-haiku-4-5
- claude-sonnet-4-6
max_budget: 50
ttl: 3600 # seconds (1h)
max_ttl: 86400 # seconds (24h)
@@ -1,44 +1,25 @@
# Single declarative source for the terraform-netbox NetBox service identity. The
# Single declarative source for the terraform-infra NetBox service identity. The
# filename stem is the engine role name AND the NetBox username (1:1); config.hcl
# derives both from it, so neither is repeated below. Creating this file creates
# the user: the netbox_user_management module synthesizes the NetBox user + object
# permissions from the permissions block, and the engine role mints ephemeral
# tokens for that same user. write_enabled true because terraform-netbox manages
# tokens for that same user. write_enabled true because terraform-infra manages
# NetBox IPAM/DCIM; very short TTLs because a token is minted per plan/apply and
# revoked when the run's lease ends.
#
# extras.customfield is what lets the runner declare the custom fields bootapi
# reads (pxe_enabled, provision_template, domain, gateway, nameservers), and
# dcim.platform is how a device gets the platform slug bootapi selects a distro
# catalog entry by.
---
write_enabled: true
ttl: 120 # 2m
max_ttl: 300 # 5m
permissions:
- name: terraform-netbox-write
object_types:
- object_types:
- ipam.prefix
- ipam.ipaddress
- ipam.iprange
- ipam.role
- dcim.device
- dcim.devicerole
- dcim.devicetype
- dcim.interface
- dcim.macaddress
- dcim.manufacturer
- dcim.platform
- extras.customfield
- extras.tag
actions:
- view
- add
- change
- delete
# Sites are looked up with a data source, never created here.
- name: terraform-netbox-read
object_types:
- dcim.site
actions:
- view
@@ -7,12 +7,6 @@
# sha256 pins the released v0.2.0 binary; bump it in lockstep with any RPM
# upgrade or OpenBao will refuse to launch the plugin. Registration only
# succeeds once the Puppet PR has installed the binary on the nodes.
#
# Terraform retunes mounts onto a bumped version; after apply, run
# `bao write sys/plugins/reload/backend mounts=arrstack scope=global`.
#
# Once versioned, keep `version` set: dropping it leaves the mount pinned to a
# deregistered version.
type: secret
command: vault-plugin-secrets-arrstack
version: "0.2.0"
@@ -3,8 +3,6 @@ algorithm_signer: rsa-sha2-256
ttl: 315360000 # 87600 * 3600
allow_host_certificates: true
allow_user_certificates: false
# "*" must be the sole entry: OpenBao only treats allowed_domains as unrestricted
# when the whole string is "*", and otherwise matches entries exactly or by suffix.
allowed_domains: "*"
allowed_domains: "unkin.net,main.unkin.net,consul"
allow_subdomains: true
allow_bare_domains: false
-10
View File
@@ -63,8 +63,6 @@ inputs = {
auth_oidc_backend = local.config.auth_oidc_backend
auth_oidc_role = local.config.auth_oidc_role
auth_oidc_group = local.config.auth_oidc_group
auth_jwt_backend = local.config.auth_jwt_backend
auth_jwt_role = local.config.auth_jwt_role
auth_kubernetes_backend = local.config.auth_kubernetes_backend
auth_kubernetes_role = local.config.auth_kubernetes_role
kv_secret_backend = local.config.kv_secret_backend
@@ -100,14 +98,6 @@ inputs = {
ghp_secret_backend = local.config.ghp_secret_backend
ghp_secret_backend_role = local.config.ghp_secret_backend_role
identity_oidc = local.config.identity_oidc
identity_oidc_key = local.config.identity_oidc_key
identity_oidc_role = local.config.identity_oidc_role
identity_oidc_client = local.config.identity_oidc_client
identity_oidc_scope = local.config.identity_oidc_scope
identity_oidc_assignment = local.config.identity_oidc_assignment
identity_oidc_provider = local.config.identity_oidc_provider
# Pass policy maps to vault_cluster module
policy_auth_map = local.policies.policy_auth_map
policy_rules_map = local.policies.policy_rules_map
-173
View File
@@ -113,40 +113,6 @@ module "auth_oidc_group" {
depends_on = [module.auth_oidc_backend]
}
module "auth_jwt_backend" {
source = "./modules/auth_jwt_backend"
for_each = var.auth_jwt_backend
path = each.key
description = each.value.description
oidc_discovery_url = each.value.oidc_discovery_url
bound_issuer = each.value.bound_issuer
default_role = each.value.default_role
listing_visibility = each.value.listing_visibility
default_lease_ttl = each.value.default_lease_ttl
max_lease_ttl = each.value.max_lease_ttl
}
module "auth_jwt_role" {
source = "./modules/auth_jwt_role"
for_each = var.auth_jwt_role
backend = each.value.backend
role_name = each.value.role_name
user_claim = each.value.user_claim
bound_audiences = each.value.bound_audiences
backend_accessor = module.auth_jwt_backend[each.value.backend].accessor
bound_claims = each.value.bound_claims
claim_mappings = each.value.claim_mappings
token_ttl = each.value.token_ttl
token_max_ttl = each.value.token_max_ttl
token_policies = try(var.policy_auth_map[each.value.backend][each.value.role_name], [])
depends_on = [module.auth_jwt_backend]
}
module "auth_kubernetes_backend" {
source = "./modules/auth_kubernetes_backend"
@@ -430,43 +396,6 @@ module "plugin" {
plugin_version = each.value.version
}
locals {
plugin_mounts = merge(
{ for path, mount in var.arrstack_secret_backend : path => mount.plugin },
{ for path, mount in var.ghp_secret_backend : path => mount.plugin },
{ for path, mount in var.gitea_secret_backend : path => mount.plugin },
{ for path, mount in var.gpg_secret_backend : path => mount.plugin },
{ for path, mount in var.litellm_secret_backend : path => mount.plugin },
{ for path, mount in var.netbox_secret_backend : path => mount.plugin },
{ for path, mount in var.rancher_secret_backend : path => mount.plugin },
)
}
# Pins each plugin-backed mount to its plugin's catalog version. Unversioned
# catalog entries need no pin (OpenBao rejects an empty plugin_version tune).
module "mount_plugin_version" {
source = "./modules/mount_plugin_version"
for_each = {
for path, plugin in local.plugin_mounts : path => var.plugins[plugin].version
if try(var.plugins[plugin].version, null) != null
}
path = each.key
plugin_version = each.value
depends_on = [
module.plugin,
module.arrstack_secret_backend,
module.ghp_secret_backend,
module.gitea_secret_backend,
module.gpg_secret_backend,
module.litellm_secret_backend,
module.netbox_secret_backend,
module.rancher_secret_backend,
]
}
module "arrstack_secret_backend" {
source = "./modules/arrstack_secret_backend"
@@ -754,105 +683,3 @@ module "pki_mount_only" {
delta_rebuild_interval = each.value.delta_rebuild_interval
}
module "identity_oidc" {
source = "./modules/identity_oidc"
for_each = var.identity_oidc
issuer = each.value.issuer
}
module "identity_oidc_key" {
source = "./modules/identity_oidc_key"
for_each = var.identity_oidc_key
name = each.key
algorithm = each.value.algorithm
rotation_period = each.value.rotation_period
verification_ttl = each.value.verification_ttl
depends_on = [module.identity_oidc]
}
module "identity_oidc_role" {
source = "./modules/identity_oidc_role"
for_each = var.identity_oidc_role
name = each.value.name
key = each.value.key
template = each.value.template
ttl = each.value.ttl
client_id = each.value.client_id
depends_on = [module.identity_oidc, module.identity_oidc_key]
}
module "identity_oidc_assignment" {
source = "./modules/identity_oidc_assignment"
for_each = var.identity_oidc_assignment
name = each.key
entity_ids = each.value.entity_ids
group_ids = each.value.group_ids
}
module "identity_oidc_client" {
source = "./modules/identity_oidc_client"
for_each = var.identity_oidc_client
name = each.value.name
key = each.value.key
redirect_uris = each.value.redirect_uris
assignments = each.value.assignments
client_type = each.value.client_type
id_token_ttl = each.value.id_token_ttl
access_token_ttl = each.value.access_token_ttl
depends_on = [module.identity_oidc, module.identity_oidc_key, module.identity_oidc_assignment]
}
module "identity_oidc_key_allowed_client_id" {
source = "./modules/identity_oidc_key_allowed_client_id"
for_each = merge(
{ for k, v in var.identity_oidc_role : "role/${k}" => { key = v.key, client_id = module.identity_oidc_role[k].client_id } },
{ for k, v in var.identity_oidc_client : "client/${k}" => { key = v.key, client_id = module.identity_oidc_client[k].client_id } },
)
key_name = each.value.key
allowed_client_id = each.value.client_id
depends_on = [module.identity_oidc_key]
}
module "identity_oidc_scope" {
source = "./modules/identity_oidc_scope"
for_each = var.identity_oidc_scope
name = each.key
description = each.value.description
template = each.value.template
}
module "identity_oidc_provider" {
source = "./modules/identity_oidc_provider"
for_each = var.identity_oidc_provider
name = each.key
issuer_host = each.value.issuer_host
https_enabled = each.value.https_enabled
scopes_supported = each.value.scopes_supported
allowed_client_ids = [
for c in each.value.allowed_clients :
c == "*" ? c : one([for k, v in var.identity_oidc_client : module.identity_oidc_client[k].client_id if v.name == c])
]
depends_on = [module.identity_oidc, module.identity_oidc_scope, module.identity_oidc_client]
}
@@ -1,18 +0,0 @@
resource "vault_jwt_auth_backend" "jwt" {
path = var.path
type = "jwt"
description = var.description
oidc_discovery_url = var.oidc_discovery_url
bound_issuer = var.bound_issuer
default_role = var.default_role
# provider 5.6.0 panics on an all-null tune block
dynamic "tune" {
for_each = anytrue([for v in [var.default_lease_ttl, var.max_lease_ttl, var.listing_visibility] : v != null]) ? [1] : []
content {
default_lease_ttl = var.default_lease_ttl
max_lease_ttl = var.max_lease_ttl
listing_visibility = var.listing_visibility
}
}
}
@@ -1,3 +0,0 @@
output "accessor" {
value = vault_jwt_auth_backend.jwt.accessor
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,26 +0,0 @@
mock_provider "vault" {}
variables {
path = "jwt/github/iuclabs"
oidc_discovery_url = "https://token.actions.githubusercontent.com"
bound_issuer = "https://token.actions.githubusercontent.com"
}
run "omit_tune_when_unset" {
command = plan
assert {
condition = length(vault_jwt_auth_backend.jwt.tune) == 0
error_message = "no tune block may render when every tune value is null"
}
}
run "render_tune_when_set" {
command = plan
variables { max_lease_ttl = "1h" }
assert {
condition = length(vault_jwt_auth_backend.jwt.tune) == 1 && vault_jwt_auth_backend.jwt.tune[0].max_lease_ttl == "1h"
error_message = "tune block must render with the configured values"
}
}
@@ -1,48 +0,0 @@
variable "path" {
description = "Mount path of the JWT auth backend"
type = string
}
variable "description" {
description = "Human-readable description of the auth mount"
type = string
default = null
}
variable "oidc_discovery_url" {
description = "OIDC discovery URL used to fetch the issuer's signing keys"
type = string
}
variable "bound_issuer" {
description = "Value the iss claim of every presented token must match"
type = string
}
variable "default_role" {
description = "Role used when a login request names none"
type = string
default = null
}
variable "listing_visibility" {
description = "Specifies whether to show this mount in the UI-specific listing endpoint. Valid values are 'unauth' or 'hidden'"
type = string
default = null
validation {
condition = var.listing_visibility == null || contains(["unauth", "hidden"], var.listing_visibility)
error_message = "listing_visibility must be either 'unauth' or 'hidden'."
}
}
variable "default_lease_ttl" {
description = "Specifies the default time-to-live. If set, this overrides the global default. Must be a valid duration string"
type = string
default = null
}
variable "max_lease_ttl" {
description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string"
type = string
default = null
}
@@ -1,29 +0,0 @@
locals {
alias = "identity.entity.aliases.${var.backend_accessor}.metadata"
kv_prefixes = [
"github/{{${local.alias}.repository}}/event/{{${local.alias}.event_name}}",
"github/{{${local.alias}.repository}}/ref/{{${local.alias}.ref}}/.secrets",
]
}
resource "vault_policy" "kv" {
name = "${var.backend}/${var.role_name}/kv-read"
policy = join("\n", flatten([for p in local.kv_prefixes : [
"path \"kv/data/${p}/*\" {\n capabilities = [\"read\"]\n}",
"path \"kv/metadata/${p}/*\" {\n capabilities = [\"read\", \"list\"]\n}",
]]))
}
resource "vault_jwt_auth_backend_role" "role" {
backend = var.backend
role_name = var.role_name
role_type = "jwt"
user_claim = var.user_claim
bound_audiences = var.bound_audiences
bound_claims = var.bound_claims
bound_claims_type = "glob"
claim_mappings = var.claim_mappings
token_ttl = var.token_ttl
token_max_ttl = var.token_max_ttl
token_policies = concat(var.token_policies, [vault_policy.kv.name])
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,167 +0,0 @@
mock_provider "vault" {}
variables {
backend = "jwt/github/iuclabs"
role_name = "default"
backend_accessor = "auth_jwt_test"
bound_audiences = ["https://vault.service.consul:8200"]
}
run "accept_org_role" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
assert {
condition = vault_policy.kv.policy == chomp(<<-EOT
path "kv/data/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/event/{{identity.entity.aliases.auth_jwt_test.metadata.event_name}}/*" {
capabilities = ["read"]
}
path "kv/metadata/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/event/{{identity.entity.aliases.auth_jwt_test.metadata.event_name}}/*" {
capabilities = ["read", "list"]
}
path "kv/data/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/ref/{{identity.entity.aliases.auth_jwt_test.metadata.ref}}/.secrets/*" {
capabilities = ["read"]
}
path "kv/metadata/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/ref/{{identity.entity.aliases.auth_jwt_test.metadata.ref}}/.secrets/*" {
capabilities = ["read", "list"]
}
EOT
)
error_message = "rendered KV policy paths changed"
}
assert {
condition = contains(vault_jwt_auth_backend_role.role.token_policies, "jwt/github/iuclabs/default/kv-read")
error_message = "role must carry the KV read policy"
}
assert {
condition = vault_jwt_auth_backend_role.role.bound_claims_type == "glob" && vault_jwt_auth_backend_role.role.user_claim == "jti"
error_message = "role must glob-match claims and alias on jti"
}
}
run "ref_paths_cannot_overlap_child_refs" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push", ref = "refs/heads/*" } }
assert {
condition = length(regexall("metadata\\.ref}}/[^.]", vault_policy.kv.policy)) == 0
error_message = "ref paths must end in a dot-prefixed segment git refnames cannot contain"
}
}
run "accept_exact_refs" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/main,refs/tags/v1.2.3,refs/pull/7/merge" } }
assert {
condition = vault_jwt_auth_backend_role.role.bound_claims.ref == "refs/heads/main,refs/tags/v1.2.3,refs/pull/7/merge"
error_message = "exact refs must pass through to bound_claims unchanged"
}
}
run "accept_with_repository" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", repository = "IUCLabs/app" } }
}
run "reject_missing_repository_owner" {
command = plan
variables { bound_claims = { event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_missing_ref" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request" } }
expect_failures = [var.bound_claims]
}
run "reject_missing_event_name" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_repository_owner_wildcard" {
command = plan
variables { bound_claims = { repository_owner = "*", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_repository_owner_glob" {
command = plan
variables { bound_claims = { repository_owner = "IUC*", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_event_pull_request_target" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,pull_request_target", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_event_workflow_run" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "workflow_run", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_event_issue_comment" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "issue_comment", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_event_workflow_dispatch" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "workflow_dispatch", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_star" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/*" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_bare_star" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "*" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_pull_star" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/pull/*" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_pull_head" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/pull/*/head" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_heads_prefix_glob" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/rel*" } }
expect_failures = [var.bound_claims]
}
run "reject_ref_empty" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "" } }
expect_failures = [var.bound_claims]
}
run "reject_repository_glob" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", repository = "IUCLabs/*" } }
expect_failures = [var.bound_claims]
}
run "reject_environment_empty" {
command = plan
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", environment = "" } }
expect_failures = [var.bound_claims]
}
@@ -1,79 +0,0 @@
variable "backend" {
description = "The unique path of the JWT auth backend to configure"
type = string
}
variable "role_name" {
description = "The name of the role"
type = string
}
variable "backend_accessor" {
description = "Accessor of the JWT auth mount, used to template the KV read policy"
type = string
}
variable "user_claim" {
description = "Claim used as the entity alias name"
type = string
default = "jti"
}
variable "claim_mappings" {
description = "Token claims copied into entity alias metadata"
type = map(string)
default = {}
}
variable "bound_audiences" {
description = "List of audiences (aud claim) accepted in the token"
type = list(string)
validation {
condition = length(var.bound_audiences) > 0
error_message = "bound_audiences must not be empty."
}
}
variable "bound_claims" {
description = "Claims the token must carry, glob-matched; only ref may use the allowed glob patterns, comma-separated values form a list"
type = map(string)
validation {
condition = can(regex("^[A-Za-z0-9-]+$", lookup(var.bound_claims, "repository_owner", "")))
error_message = "bound_claims.repository_owner must be one exact org name."
}
validation {
condition = alltrue([for v in split(",", lookup(var.bound_claims, "event_name", "")) : contains(["push", "release", "schedule", "pull_request"], trimspace(v))])
error_message = "bound_claims.event_name must only list push, release, schedule or pull_request."
}
validation {
condition = alltrue([for v in split(",", lookup(var.bound_claims, "ref", "")) :
contains(["refs/heads/*", "refs/tags/*", "refs/pull/*/merge"], trimspace(v)) ||
can(regex("^(refs/(heads|tags)/[A-Za-z0-9._/-]+|refs/pull/[0-9]+/merge)$", trimspace(v)))
])
error_message = "bound_claims.ref must be refs/heads/*, refs/tags/*, refs/pull/*/merge or exact refs of those shapes."
}
validation {
condition = alltrue(flatten([for k, v in var.bound_claims : [
for e in split(",", v) : trimspace(e) != "" && !strcontains(e, "*")
] if k != "ref"]))
error_message = "bound_claims values other than ref must be exact and non-empty."
}
}
variable "token_ttl" {
description = "The TTL period of tokens issued using this role, in seconds"
type = number
default = 900
}
variable "token_max_ttl" {
description = "The maximum lifetime for generated tokens in number of seconds"
type = number
default = 3600
}
variable "token_policies" {
description = "List of policies to assign to the role (passed from policy_auth_map)"
type = list(string)
default = []
}
@@ -1,3 +0,0 @@
resource "vault_identity_oidc" "this" {
issuer = var.issuer
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,5 +0,0 @@
variable "issuer" {
description = "Issuer URL for identity tokens (scheme://host:port, no path); null uses Vault's api_addr"
type = string
default = null
}
@@ -1,5 +0,0 @@
resource "vault_identity_oidc_assignment" "this" {
name = var.name
entity_ids = var.entity_ids
group_ids = var.group_ids
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,16 +0,0 @@
variable "name" {
description = "Assignment name"
type = string
}
variable "entity_ids" {
description = "Identity entity ids in this assignment"
type = list(string)
default = []
}
variable "group_ids" {
description = "Identity group ids in this assignment"
type = list(string)
default = []
}
@@ -1,9 +0,0 @@
resource "vault_identity_oidc_client" "this" {
name = var.name
key = var.key
redirect_uris = var.redirect_uris
assignments = var.assignments
client_type = var.client_type
id_token_ttl = var.id_token_ttl
access_token_ttl = var.access_token_ttl
}
@@ -1,3 +0,0 @@
output "client_id" {
value = vault_identity_oidc_client.this.client_id
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,39 +0,0 @@
variable "name" {
description = "OIDC client name"
type = string
}
variable "key" {
description = "Named key that signs this client's ID tokens"
type = string
}
variable "redirect_uris" {
description = "Allowed redirect URIs"
type = list(string)
default = []
}
variable "assignments" {
description = "Assignment names allowed to authenticate with this client"
type = list(string)
default = []
}
variable "client_type" {
description = "confidential or public"
type = string
default = null
}
variable "id_token_ttl" {
description = "ID token TTL in seconds"
type = number
default = null
}
variable "access_token_ttl" {
description = "Access token TTL in seconds"
type = number
default = null
}
@@ -1,6 +0,0 @@
resource "vault_identity_oidc_key" "this" {
name = var.name
algorithm = var.algorithm
rotation_period = var.rotation_period
verification_ttl = var.verification_ttl
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,22 +0,0 @@
variable "name" {
description = "Name of the named key"
type = string
}
variable "algorithm" {
description = "Signing algorithm (RS256, RS384, RS512, ES256, ES384, ES512, EdDSA)"
type = string
default = null
}
variable "rotation_period" {
description = "Seconds between key rotations"
type = number
default = null
}
variable "verification_ttl" {
description = "Seconds a rotated public key stays published for verification"
type = number
default = null
}
@@ -1,4 +0,0 @@
resource "vault_identity_oidc_key_allowed_client_id" "this" {
key_name = var.key_name
allowed_client_id = var.allowed_client_id
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,9 +0,0 @@
variable "key_name" {
description = "Named key the client id may sign with"
type = string
}
variable "allowed_client_id" {
description = "Role or client id allowed to use the key"
type = string
}
@@ -1,7 +0,0 @@
resource "vault_identity_oidc_provider" "this" {
name = var.name
issuer_host = var.issuer_host
https_enabled = var.https_enabled
scopes_supported = var.scopes_supported
allowed_client_ids = var.allowed_client_ids
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,28 +0,0 @@
variable "name" {
description = "OIDC provider name"
type = string
}
variable "issuer_host" {
description = "Issuer host[:port]; null uses Vault's api_addr"
type = string
default = null
}
variable "https_enabled" {
description = "Use https in the issuer URL"
type = bool
default = true
}
variable "scopes_supported" {
description = "Scope names the provider serves"
type = list(string)
default = []
}
variable "allowed_client_ids" {
description = "Client ids allowed to use the provider (\"*\" allows all)"
type = list(string)
default = []
}
@@ -1,7 +0,0 @@
resource "vault_identity_oidc_role" "this" {
name = var.name
key = var.key
template = var.template
ttl = var.ttl
client_id = var.client_id
}
@@ -1,3 +0,0 @@
output "client_id" {
value = vault_identity_oidc_role.this.client_id
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,27 +0,0 @@
variable "name" {
description = "Role name (read identity/oidc/token/<name> to mint a token)"
type = string
}
variable "key" {
description = "Named key that signs this role's tokens"
type = string
}
variable "template" {
description = "JSON claims template for issued tokens"
type = string
default = null
}
variable "ttl" {
description = "Token TTL in seconds"
type = number
default = null
}
variable "client_id" {
description = "Token audience (aud); null lets Vault generate one"
type = string
default = null
}
@@ -1,5 +0,0 @@
resource "vault_identity_oidc_scope" "this" {
name = var.name
description = var.description
template = var.template
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,16 +0,0 @@
variable "name" {
description = "Scope name"
type = string
}
variable "description" {
description = "Scope description"
type = string
default = null
}
variable "template" {
description = "JSON claims template added when this scope is requested"
type = string
default = null
}
@@ -16,7 +16,7 @@ locals {
resource "vault_kubernetes_secret_backend_role" "role" {
backend = var.backend
name = var.name
allowed_kubernetes_namespaces = sort(var.allowed_kubernetes_namespaces)
allowed_kubernetes_namespaces = var.allowed_kubernetes_namespaces
kubernetes_role_type = local.use_service_account ? null : var.kubernetes_role_type
generated_role_rules = local.use_service_account ? null : file("${path.module}/../../../../../../../../${local.role_rules_file}")
service_account_name = var.service_account_name
@@ -1,9 +0,0 @@
# Pins a plugin-backed mount to its catalog version (sys/mounts/<path>/tune).
# OpenBao normalises plugin_version to a leading "v" and reads it back that way.
# Tune has no delete; destroying this leaves the mount's pin in place.
resource "vault_generic_endpoint" "this" {
path = "sys/mounts/${var.path}/tune"
data_json = jsonencode({ plugin_version = "v${trimprefix(var.plugin_version, "v")}" })
ignore_absent_fields = true
disable_delete = true
}
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -1,9 +0,0 @@
variable "path" {
description = "Mount path of the plugin-backed secrets engine"
type = string
}
variable "plugin_version" {
description = "Catalog version to pin the mount to (with or without a leading v)"
type = string
}
@@ -9,10 +9,4 @@ resource "vault_plugin" "this" {
command = coalesce(var.command, var.name)
sha256 = var.sha256
version = var.plugin_version
# A version bump registers the new entry, retunes mounts onto it, then
# deregisters the old one, so no mount is left pinned to a missing version.
lifecycle {
create_before_destroy = true
}
}
@@ -425,9 +425,3 @@ moved {
from = vault_policy.policies["pki_root/pki_root_roles_admin"]
to = module.vault_policy["pki_root/roles/admin"].vault_policy.this
}
# Mount enabled by a create that crashed before state was written
import {
to = module.auth_jwt_backend["jwt/github/iuclabs"].vault_jwt_auth_backend.jwt
id = "jwt/github/iuclabs"
}
-102
View File
@@ -102,35 +102,6 @@ variable "auth_oidc_group" {
default = {}
}
variable "auth_jwt_backend" {
description = "Map of JWT auth backends (machine OIDC token login) to create"
type = map(object({
oidc_discovery_url = string
bound_issuer = string
default_role = optional(string)
description = optional(string)
listing_visibility = optional(string)
default_lease_ttl = optional(string)
max_lease_ttl = optional(string)
}))
default = {}
}
variable "auth_jwt_role" {
description = "Map of JWT auth roles to create"
type = map(object({
role_name = string
backend = string
bound_audiences = list(string)
bound_claims = map(string)
claim_mappings = optional(map(string), {})
user_claim = optional(string, "jti")
token_ttl = optional(number, 900)
token_max_ttl = optional(number, 3600)
}))
default = {}
}
variable "auth_kubernetes_backend" {
description = "Map of Kubernetes auth backends to create"
type = map(object({
@@ -607,76 +578,3 @@ variable "policy_rules_map" {
default = {}
}
variable "identity_oidc" {
description = "Identity token issuer config (singleton in practice)"
type = map(object({
issuer = optional(string)
}))
default = {}
}
variable "identity_oidc_key" {
description = "Map of identity OIDC named keys to create"
type = map(object({
algorithm = optional(string, "RS256")
rotation_period = optional(number, 86400)
verification_ttl = optional(number, 86400)
}))
default = {}
}
variable "identity_oidc_role" {
description = "Map of identity token roles to create, keyed <key>/<role>"
type = map(object({
name = string
key = string
template = optional(string)
ttl = optional(number, 86400)
client_id = optional(string)
}))
default = {}
}
variable "identity_oidc_client" {
description = "Map of identity OIDC provider clients to create, keyed <key>/<client>"
type = map(object({
name = string
key = string
redirect_uris = optional(list(string), [])
assignments = optional(list(string), [])
client_type = optional(string, "confidential")
id_token_ttl = optional(number, 86400)
access_token_ttl = optional(number, 86400)
}))
default = {}
}
variable "identity_oidc_scope" {
description = "Map of identity OIDC provider scopes to create"
type = map(object({
description = optional(string)
template = optional(string)
}))
default = {}
}
variable "identity_oidc_assignment" {
description = "Map of identity OIDC client assignments to create"
type = map(object({
entity_ids = optional(list(string), [])
group_ids = optional(list(string), [])
}))
default = {}
}
variable "identity_oidc_provider" {
description = "Map of identity OIDC providers to create; allowed_clients are client names or \"*\""
type = map(object({
issuer_host = optional(string)
https_enabled = optional(bool, true)
scopes_supported = optional(list(string), [])
allowed_clients = optional(list(string), [])
}))
default = {}
}
@@ -1,17 +0,0 @@
# Allow administration of the GitHub Actions (IUCLabs) JWT auth backend: mount
# config and per-repo workflow roles.
---
rules:
- path: "auth/jwt/github/iuclabs/*"
capabilities:
- create
- update
- read
- delete
- list
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
@@ -1,11 +0,0 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-binarylane"
capabilities:
- read
auth:
approle:
- terraform_binarylane
k8s/au/syd1:
- woodpecker_terraform_binarylane
@@ -0,0 +1,11 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-infra"
capabilities:
- read
auth:
approle:
- terraform_infra
k8s/au/syd1:
- woodpecker_terraform_infra
@@ -1,11 +0,0 @@
---
rules:
- path: "consul_root/au/syd1/creds/terraform-netbox"
capabilities:
- read
auth:
approle:
- terraform_netbox
k8s/au/syd1:
- woodpecker_terraform_netbox
+15 -3
View File
@@ -1,8 +1,20 @@
# Allow the vault deployer to manage the gpg mount's OpenPGP keys via the
# gpgvaultsecret provider. Registering the plugin itself is a sys/plugins/catalog
# grant, carried by policies/sys/plugins/catalog/admin.yaml.
# 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:
-24
View File
@@ -1,24 +0,0 @@
# Allow the deployer to manage the identity group aliases that map external OIDC
# group membership (the ak_groups claim) onto the Vault groups managed under
# policies/identity/group/. Both endpoints are needed: create posts to
# identity/group-alias, subsequent reads and updates address
# identity/group-alias/id/<id>.
---
rules:
- path: "identity/group-alias"
capabilities:
- create
- update
- path: "identity/group-alias/*"
capabilities:
- create
- update
- read
- delete
- list
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
+19 -4
View File
@@ -1,7 +1,7 @@
# Allow the deployer to manage the external identity groups that OIDC group
# membership (the ak_groups claim) maps onto Vault policies through. Both the
# collection endpoint and the per-id endpoints are needed: create posts to
# identity/group, subsequent reads and updates address identity/group/id/<id>.
# Allow the deployer to manage external identity groups and their aliases, which
# is how OIDC group membership (the ak_groups claim) maps onto Vault policies.
# Both the collection endpoints and the per-id endpoints are needed: create posts
# to identity/group, subsequent reads and updates address identity/group/id/<id>.
---
rules:
- path: "identity/group"
@@ -15,6 +15,21 @@ rules:
- read
- delete
- list
- path: "identity/group-alias"
capabilities:
- create
- update
- path: "identity/group-alias/*"
capabilities:
- create
- update
- read
- delete
- list
- path: "identity/lookup/group"
capabilities:
- create
- update
auth:
approle:
-14
View File
@@ -1,14 +0,0 @@
# Allow the deployer to look up an identity group by name, which is how it
# resolves the group it is about to update under policies/identity/group/.
---
rules:
- path: "identity/lookup/group"
capabilities:
- create
- update
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
-51
View File
@@ -1,51 +0,0 @@
# Allow the deployer to manage identity OIDC objects through the vault_identity_oidc*
# resources, which only read, write and delete by name and never list or rotate.
---
rules:
- path: "identity/oidc/config"
capabilities:
- create
- read
- update
- path: "identity/oidc/key/*"
capabilities:
- create
- read
- update
- delete
- path: "identity/oidc/role/*"
capabilities:
- create
- read
- update
- delete
- path: "identity/oidc/client/*"
capabilities:
- create
- read
- update
- delete
- path: "identity/oidc/scope/*"
capabilities:
- create
- read
- update
- delete
- path: "identity/oidc/assignment/*"
capabilities:
- create
- read
- update
- delete
- path: "identity/oidc/provider/*"
capabilities:
- create
- read
- update
- delete
auth:
approle:
- tf_vault
k8s/au/syd1:
- woodpecker_terraform_vault
@@ -1,13 +0,0 @@
---
rules:
- path: "identity/oidc/token/artifactapi-writer"
capabilities:
- read
auth:
k8s/au/syd1:
- woodpecker_artifactapi_writer
ldap:
- vault_admin
oidc:
- akP-vault-admin
@@ -1,11 +0,0 @@
---
rules:
- path: "identity/oidc/token/encapi-testing"
capabilities:
- read
auth:
ldap:
- vault_admin
oidc:
- akP-vault-admin
@@ -1,11 +0,0 @@
---
rules:
- path: "identity/oidc/token/terraform-artifactapi"
capabilities:
- read
auth:
approle:
- terraform_artifactapi
k8s/au/syd1:
- woodpecker_terraform_artifactapi
@@ -1,11 +0,0 @@
---
rules:
- path: "identity/oidc/token/terraform-enc"
capabilities:
- read
auth:
approle:
- terraform_enc
k8s/au/syd1:
- woodpecker_terraform_enc
@@ -1,12 +0,0 @@
# Allow access to agent-observability Kubernetes credentials
---
rules:
- path: "kubernetes/au/syd1/creds/agent-observability"
capabilities:
- update
auth:
ldap:
- kubernetes_au_syd1_cluster_operator
approle:
- agents
@@ -1,15 +0,0 @@
# Allow the Terraform Authentik runner to read the OAuth2/OIDC client secret that
# backs an authentik provider, in whichever namespace the target service lives
# (the module's data.vault_kv_secret_v2). The literal + is a Vault single-segment
# wildcard: one oauth-credentials secret per onboarded namespace.
---
rules:
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
capabilities:
- read
auth:
approle:
- terraform_authentik
k8s/au/syd1:
- woodpecker_terraform_authentik
@@ -1,14 +0,0 @@
# Allow the Terraform Rancher runner to read the OAuth2/OIDC client secret backing
# the Rancher keycloakoidc AuthConfig (the same secret Authentik sets on the
# provider).
---
rules:
- path: "kv/data/kubernetes/namespace/cattle-system/default/oauth-credentials"
capabilities:
- read
auth:
approle:
- terraform_rancher
k8s/au/syd1:
- woodpecker_terraform_rancher
@@ -1,12 +0,0 @@
# Allow reading the IUCLabs GitHub App credentials
---
rules:
- path: "kv/data/service/github/iuclabs/terraform"
capabilities:
- read
auth:
approle:
- terraform_git
k8s/au/syd1:
- woodpecker_terraform_git
+7 -3
View File
@@ -1,11 +1,15 @@
# Allow the Terraform Authentik runner to read its own Authentik API token
# (provider auth). The OAuth2/OIDC client secrets backing authentik providers are
# granted per secret under policies/kv/kubernetes/namespace/.
# 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:
@@ -1,13 +0,0 @@
# Allow the Terraform BinaryLane runner to read its BinaryLane API token
# (provider auth).
---
rules:
- path: "kv/data/service/terraform/binarylane"
capabilities:
- read
auth:
approle:
- terraform_binarylane
k8s/au/syd1:
- woodpecker_terraform_binarylane
@@ -1,16 +0,0 @@
# Lets the agents AppRole seed and rotate the BinaryLane API token (no delete).
---
rules:
- path: "kv/data/service/terraform/binarylane"
capabilities:
- create
- read
- update
- path: "kv/metadata/service/terraform/binarylane"
capabilities:
- read
- list
auth:
approle:
- agents
+18
View File
@@ -0,0 +1,18 @@
# Allow the terraform-infra runner to read the NetBox + KeaAPI tokens
# (netbox_token / kea_token fields) used by the netbox and kea providers.
---
rules:
- path: "kv/data/service/terraform/infra"
capabilities:
- read
# vault_kv_secret_v2 (terraform-infra providers.tf data source) reads the kv-v2
# metadata path on every plan/apply; a 403 here fails the plan.
- path: "kv/metadata/service/terraform/infra"
capabilities:
- read
auth:
approle:
- terraform_infra
k8s/au/syd1:
- woodpecker_terraform_infra
+7 -3
View File
@@ -1,11 +1,15 @@
# Allow the Terraform Rancher runner to read its own Rancher admin API token
# (rancher2 provider auth). The OAuth2/OIDC client secret backing the Rancher
# keycloakoidc AuthConfig is granted under policies/kv/kubernetes/namespace/.
# 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:
@@ -1,15 +0,0 @@
# Lets the agents AppRole read the BinaryLane gateway WireGuard keypairs so they
# can be eyaml-encrypted into puppet hiera. Read-only; terraform-binarylane owns writes.
---
rules:
- path: "kv/data/service/wireguard/binarylane/*"
capabilities:
- read
- path: "kv/metadata/service/wireguard/binarylane/*"
capabilities:
- read
- list
auth:
approle:
- agents
@@ -1,23 +0,0 @@
# Let the terraform-binarylane runner store the WireGuard keypairs it generates
# for the BinaryLane gateways. delete lets `terraform destroy` and keypair
# replacement remove a secret and its metadata; vault_kv_secret_v2 reads
# metadata on every plan/apply.
---
rules:
- path: "kv/data/service/wireguard/binarylane/*"
capabilities:
- create
- read
- update
- delete
- path: "kv/metadata/service/wireguard/binarylane/*"
capabilities:
- read
- list
- delete
auth:
approle:
- terraform_binarylane
k8s/au/syd1:
- woodpecker_terraform_binarylane
-10
View File
@@ -1,10 +0,0 @@
# Lets rspamd mint a Haiku-scoped LiteLLM virtual key for its GPT/LLM module.
---
rules:
- path: "litellm/creds/rspamd"
capabilities:
- read
auth:
k8s/au/syd1:
- rspamd
@@ -0,0 +1,21 @@
# Allow the terraform-infra runner to mint an ephemeral NetBox token from the
# terraform-infra role (netbox/creds/terraform-infra), replacing the static
# netbox_token it used to read from kv/service/terraform/*. The e-breuninger
# netbox provider authenticates with the minted token; the lease revokes it when
# the run ends.
#
# Bound to both the terraform-infra AppRole and its Woodpecker k8s auth role,
# mirroring the terraform-ipam pattern. Both principals are created by the
# terraform-infra Vault onboarding (separate from this netbox change); until
# that onboarding lands this policy exists but attaches to nothing.
---
rules:
- path: "netbox/creds/terraform-infra"
capabilities:
- read
auth:
approle:
- terraform_infra
k8s/au/syd1:
- woodpecker_terraform_infra

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