Add Vault-scoped agent kubernetes roles + agents AppRole (#109)
ci/woodpecker/push/apply Pipeline was successful

## Why
Agentic workloads currently need cluster-admin/root kubeconfig contexts to do routine per-domain work. This adds domain-scoped, Vault-issued kubernetes credentials plus an `agents` AppRole so agents get least-privilege access instead of escalating.

## Changes
- Add `kubernetes_secret_backend_role` configs `agent-dhcp`, `agent-dns`, `agent-certs`, `agent-storage` (au/syd1):
  - **agent-dhcp** (Role, ns `dhcp-system`): full verbs on `kea.unkin.net` CRDs; get/list/watch pods/services/configmaps/events + pods/log.
  - **agent-dns** (`service_account_name` mode): mints tokens for the static `agent-dns` SA (argocd-apps#332) whose per-namespace RoleBindings confine access to bind-system/bind-internal/bind-external/externaldns. `allowed_kubernetes_namespaces: [bind-system]` (the SA's namespace).
  - **agent-certs** (Role, ns `cert-manager`): full verbs on `cert-manager.io` + `acme.cert-manager.io` (closes the orders/challenges debugging gap); get/list/watch/delete secrets; get/list/watch pods + pods/log. Secret delete confined to `cert-manager`.
  - **agent-storage** (Role, ns `cephrgw-system`): full verbs on `ceph.unkin.net` CRDs (buckets/bucketaccesses/objectstoreusers); get/list/watch pods + pods/log.
- Extend the `kubernetes_secret_backend_role` module with an optional `service_account_name`; when set, `generated_role_rules`/`kubernetes_role_type` are omitted (the SA's own bindings supply RBAC).
- Add creds policies for each role, bound to the `kubernetes_au_syd1_cluster_operator` ldap group (human kubectl use) and the `agents` AppRole (programmatic use).
- Add the `agents` AppRole (mirrors the certmanager approle schema): `bind_secret_id: false` (role_id-only login), `token_bound_cidrs: [10.10.12.200/32]` (agent workstation wg0 addr), deterministic role_id, 1h/4h TTLs.
- Add `policies/kv/kubernetes/agents.yaml` granting the AppRole create/read/update/list on `kv/data/kubernetes/*` + read/list on `kv/metadata/kubernetes/*` (no delete).

## Ordering
argocd-apps#332 (the `agent-dns` SA + ClusterRole + per-namespace RoleBindings) must sync **before** the `agent-dns` creds here are usable — Vault mints tokens for a service account that must already exist.

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Reviewed-on: #109
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #109.
This commit is contained in:
2026-08-02 21:55:05 +10:00
committed by BenVincent
parent c0cc74927c
commit 0e6da5cdd3
17 changed files with 184 additions and 2 deletions
@@ -0,0 +1,6 @@
token_ttl: 3600
token_max_ttl: 14400
bind_secret_id: false
token_bound_cidrs:
- "10.10.12.200/32"
use_deterministic_role_id: true
@@ -0,0 +1,4 @@
allowed_kubernetes_namespaces:
- "cert-manager"
kubernetes_role_type: "Role"
extra_labels: {}
@@ -0,0 +1,4 @@
allowed_kubernetes_namespaces:
- "dhcp-system"
kubernetes_role_type: "Role"
extra_labels: {}
@@ -0,0 +1,4 @@
allowed_kubernetes_namespaces:
- "bind-system"
service_account_name: "agent-dns"
extra_labels: {}
@@ -0,0 +1,4 @@
allowed_kubernetes_namespaces:
- "cephrgw-system"
kubernetes_role_type: "Role"
extra_labels: {}
+1
View File
@@ -299,6 +299,7 @@ module "kubernetes_secret_backend_role" {
allowed_kubernetes_namespaces = each.value.allowed_kubernetes_namespaces
kubernetes_role_type = each.value.kubernetes_role_type
extra_labels = each.value.extra_labels
service_account_name = each.value.service_account_name
depends_on = [module.kubernetes_secret_backend]
}
@@ -2,6 +2,10 @@ locals {
# Auto-generate role rules path: resources/secret_backend/{backend_path}/roles/{role_name}.yaml
role_rules_file = "resources/secret_backend/${var.backend}/roles/${var.name}.yaml"
# service_account_name mode mints tokens for a pre-existing SA, so the
# generated_role_rules / kubernetes_role_type binding fields must be unset.
use_service_account = var.service_account_name != null
# Auto-generate extra labels based on country/region and role name
auto_labels = merge(var.extra_labels, {
vault-region = "${var.country}-${var.region}"
@@ -13,7 +17,8 @@ resource "vault_kubernetes_secret_backend_role" "role" {
backend = var.backend
name = var.name
allowed_kubernetes_namespaces = var.allowed_kubernetes_namespaces
kubernetes_role_type = var.kubernetes_role_type
generated_role_rules = file("${path.module}/../../../../../../../../${local.role_rules_file}")
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
extra_labels = local.auto_labels
}
@@ -34,4 +34,10 @@ variable "extra_labels" {
description = "Additional labels to apply to generated Kubernetes objects"
type = map(string)
default = {}
}
variable "service_account_name" {
description = "Pre-existing service account to mint tokens for. When set, RBAC comes from that SA's own bindings instead of generated_role_rules."
type = string
default = null
}
+1
View File
@@ -285,6 +285,7 @@ variable "kubernetes_secret_backend_role" {
allowed_kubernetes_namespaces = optional(list(string), ["*"])
kubernetes_role_type = optional(string, "Role")
extra_labels = optional(map(string), {})
service_account_name = optional(string)
}))
default = {}
}
@@ -0,0 +1,12 @@
# Allow access to agent-certs Kubernetes credentials
---
rules:
- path: "kubernetes/au/syd1/creds/agent-certs"
capabilities:
- update
auth:
ldap:
- kubernetes_au_syd1_cluster_operator
approle:
- agents
@@ -0,0 +1,12 @@
# Allow access to agent-dhcp Kubernetes credentials
---
rules:
- path: "kubernetes/au/syd1/creds/agent-dhcp"
capabilities:
- update
auth:
ldap:
- kubernetes_au_syd1_cluster_operator
approle:
- agents
@@ -0,0 +1,12 @@
# Allow access to agent-dns Kubernetes credentials
---
rules:
- path: "kubernetes/au/syd1/creds/agent-dns"
capabilities:
- update
auth:
ldap:
- kubernetes_au_syd1_cluster_operator
approle:
- agents
@@ -0,0 +1,12 @@
# Allow access to agent-storage Kubernetes credentials
---
rules:
- path: "kubernetes/au/syd1/creds/agent-storage"
capabilities:
- update
auth:
ldap:
- kubernetes_au_syd1_cluster_operator
approle:
- agents
+17
View File
@@ -0,0 +1,17 @@
# Allow the agents AppRole to manage the kubernetes KV subtree (no delete)
---
rules:
- path: "kv/data/kubernetes/*"
capabilities:
- create
- read
- update
- list
- path: "kv/metadata/kubernetes/*"
capabilities:
- read
- list
auth:
approle:
- agents
@@ -0,0 +1,33 @@
---
rules:
- apiGroups:
- "cert-manager.io"
- "acme.cert-manager.io"
resources:
- "*"
verbs:
- "*"
- apiGroups:
- ""
resources:
- "secrets"
verbs:
- "get"
- "list"
- "watch"
- "delete"
- apiGroups:
- ""
resources:
- "pods"
verbs:
- "get"
- "list"
- "watch"
- apiGroups:
- ""
resources:
- "pods/log"
verbs:
- "get"
- "list"
@@ -0,0 +1,26 @@
---
rules:
- apiGroups:
- "kea.unkin.net"
resources:
- "*"
verbs:
- "*"
- apiGroups:
- ""
resources:
- "pods"
- "services"
- "configmaps"
- "events"
verbs:
- "get"
- "list"
- "watch"
- apiGroups:
- ""
resources:
- "pods/log"
verbs:
- "get"
- "list"
@@ -0,0 +1,23 @@
---
rules:
- apiGroups:
- "ceph.unkin.net"
resources:
- "*"
verbs:
- "*"
- apiGroups:
- ""
resources:
- "pods"
verbs:
- "get"
- "list"
- "watch"
- apiGroups:
- ""
resources:
- "pods/log"
verbs:
- "get"
- "list"