Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ec552d6fd | |||
| 563b2164c7 | |||
| b576524553 | |||
| 14144584e3 | |||
| c1e60dad83 | |||
| 9e18627567 | |||
| 94e8ac1b2a | |||
| a19256fdba | |||
| a1e7029615 | |||
| a3a8854a16 | |||
| 67e79e72dc | |||
| 1f03bcdc67 | |||
| 6c489c6003 | |||
| bcbf121818 | |||
| 9fa51f401a | |||
| 5879fbd2d8 | |||
| f3a3e4b874 | |||
| 7c7826997e | |||
| 36d2b99255 | |||
| 9fcd9d7ab5 | |||
| 57fe279d7a | |||
| 6c68057e73 | |||
| aa8d41a7bb | |||
| 36fbfb86bc | |||
| a33fc2b42f | |||
| 0e82cda02d | |||
| 08ec281b1d | |||
| 392c5d2ac7 | |||
| d1cb790de8 | |||
| 31a7abec99 | |||
| 2525bae1d7 | |||
| df0510e33b | |||
| 41935d5603 | |||
| 5084a53015 | |||
| 600a7747b0 |
@@ -22,7 +22,7 @@ init:
|
|||||||
|
|
||||||
plan: init
|
plan: init
|
||||||
@$(call vault_env) && \
|
@$(call vault_env) && \
|
||||||
terragrunt run --all --parallelism 4 --non-interactive plan
|
terragrunt run --all --parallelism 4 --non-interactive plan -- -lock=false
|
||||||
|
|
||||||
apply: init
|
apply: init
|
||||||
@$(call vault_env) && \
|
@$(call vault_env) && \
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Mounts the arrstack dynamic secrets engine at "arrstack" and writes its config.
|
||||||
|
# The arrproxy admin token is sensitive and read from KV, not stored here:
|
||||||
|
# kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token -> key "token"
|
||||||
|
# (seeded by argocd-apps #384). arrstack.unkin.net terminates on traefik-external
|
||||||
|
# with an internal-CA cert the OpenBao nodes already trust, so ca_cert is omitted
|
||||||
|
# (system trust store), mirroring the gitea engine against git.unkin.net.
|
||||||
|
description: "arrstack dynamic arrproxy API keys"
|
||||||
|
base_url: "https://arrstack.unkin.net"
|
||||||
|
request_timeout_seconds: 30
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
# Mints an arrproxy API key scoped to all three arr apps.
|
||||||
|
apps:
|
||||||
|
- prowlarr
|
||||||
|
- radarr
|
||||||
|
- sonarr
|
||||||
|
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||||
|
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||||
|
# arrproxy admin token's fixed mint expiry.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
# Mints an arrproxy API key for the mediamark kids-content marking UI, scoped to
|
||||||
|
# Sonarr and Radarr only (no Prowlarr: mediamark never touches indexers).
|
||||||
|
#
|
||||||
|
# mediamark only lists series/movies and reads metadata + artwork, so its key is
|
||||||
|
# read-only in intent. The engine role cannot express that today: an
|
||||||
|
# arrstack_secret_backend_role carries apps/ttl/max_ttl only, and arrproxy scopes
|
||||||
|
# machine tokens by app, not by HTTP method (the GET/HEAD restriction on the
|
||||||
|
# cheeztv/kids tier is a grant on an OIDC *group*, not on a minted token). Until
|
||||||
|
# the engine and arrproxy grow per-token method scoping, mediamark's read-only
|
||||||
|
# posture is enforced by the app itself and by the arrproxy tier it routes to.
|
||||||
|
apps:
|
||||||
|
- radarr
|
||||||
|
- sonarr
|
||||||
|
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||||
|
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||||
|
# arrproxy admin token's fixed mint expiry.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
# Mints an arrproxy API key scoped to Prowlarr only.
|
||||||
|
apps:
|
||||||
|
- prowlarr
|
||||||
|
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||||
|
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||||
|
# arrproxy admin token's fixed mint expiry.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
# Mints an arrproxy API key scoped to Radarr only.
|
||||||
|
apps:
|
||||||
|
- radarr
|
||||||
|
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||||
|
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||||
|
# arrproxy admin token's fixed mint expiry.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
# Mints an arrproxy API key scoped to Sonarr only.
|
||||||
|
apps:
|
||||||
|
- sonarr
|
||||||
|
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||||
|
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||||
|
# arrproxy admin token's fixed mint expiry.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
# Kubernetes auth role for the mediamark app: the default service account in the
|
||||||
|
# mediamark namespace, used by VaultDynamicSecret to read arrstack/creds/mediamark.
|
||||||
|
bound_service_account_names:
|
||||||
|
- default
|
||||||
|
bound_service_account_namespaces:
|
||||||
|
- mediamark
|
||||||
|
token_ttl: 600
|
||||||
|
token_max_ttl: 600
|
||||||
|
audience: vault
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Kubernetes auth for the puppetserver compilers, which run certmanager
|
||||||
|
# server-side during catalog compilation. The AppRole equivalent is pinned to
|
||||||
|
# the legacy VM master IPs and cannot be used from autoscaled compiler pods.
|
||||||
|
bound_service_account_names:
|
||||||
|
- default
|
||||||
|
bound_service_account_namespaces:
|
||||||
|
- puppet
|
||||||
|
token_ttl: 30
|
||||||
|
token_max_ttl: 30
|
||||||
|
audience: https://kubernetes.default.svc.cluster.local
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
# Kubernetes auth for the puppetserver compilers, which run sshsignhost
|
||||||
|
# server-side during catalog compilation.
|
||||||
|
bound_service_account_names:
|
||||||
|
- default
|
||||||
|
bound_service_account_namespaces:
|
||||||
|
- puppet
|
||||||
|
token_ttl: 30
|
||||||
|
token_max_ttl: 30
|
||||||
|
audience: https://kubernetes.default.svc.cluster.local
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
bound_service_account_names:
|
bound_service_account_names:
|
||||||
- ghp
|
- repospawner
|
||||||
bound_service_account_namespaces:
|
bound_service_account_namespaces:
|
||||||
- ghp
|
- repospawner
|
||||||
token_ttl: 600
|
token_ttl: 600
|
||||||
token_max_ttl: 600
|
token_max_ttl: 600
|
||||||
audience: vault
|
audience: vault
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Authentik (OIDC) human SSO. The mount path is the filename and MUST stay
|
||||||
|
# "oidc": the Authentik provider registers strict redirect URIs containing
|
||||||
|
# /ui/vault/auth/oidc/oidc/callback, which encode the mount path.
|
||||||
|
#
|
||||||
|
# identity.unkin.net serves the public LetsEncrypt wildcard, so the OpenBao VMs
|
||||||
|
# validate discovery against the system roots with no CA bundle work. Do not
|
||||||
|
# swap it for identity.k8s.syd1.au.unkin.net (internal CA).
|
||||||
|
#
|
||||||
|
# listing_visibility unauth makes OIDC the offered default on the UI login page.
|
||||||
|
# client_id/client_secret are read from kv/service/authentik/oidc-vault.
|
||||||
|
---
|
||||||
|
oidc_discovery_url: "https://identity.unkin.net/application/o/vault/"
|
||||||
|
default_role: "default"
|
||||||
|
description: "Authentik SSO - default human login"
|
||||||
|
listing_visibility: "unauth"
|
||||||
|
default_lease_ttl: 24h
|
||||||
|
max_lease_ttl: 168h
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
|
||||||
|
# The filename is the Authentik group name asserted in the ak_groups claim.
|
||||||
|
# Only akP-* permission groups may appear here; akR-* roles are grouping-only.
|
||||||
|
description: foo
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Default OIDC login role (the mount's default_role), used by both the web UI
|
||||||
|
# and `bao login -method=oidc`.
|
||||||
|
#
|
||||||
|
# The role grants no policies of its own: authorization comes from the external
|
||||||
|
# identity groups under config/auth_oidc_group, matched on the ak_groups claim.
|
||||||
|
# ak_groups is Authentik's hierarchy-expanding claim (plain `groups` only carries
|
||||||
|
# direct memberships), so nested akP-* permission groups resolve.
|
||||||
|
#
|
||||||
|
# allowed_redirect_uris must match the provider's strict URIs exactly.
|
||||||
|
---
|
||||||
|
user_claim: "email"
|
||||||
|
groups_claim: "ak_groups"
|
||||||
|
oidc_scopes:
|
||||||
|
- openid
|
||||||
|
- profile
|
||||||
|
- email
|
||||||
|
- ak_groups
|
||||||
|
bound_audiences:
|
||||||
|
- vault
|
||||||
|
allowed_redirect_uris:
|
||||||
|
- "http://localhost:8250/oidc/callback"
|
||||||
|
- "https://vault.k8s.syd1.au.unkin.net/ui/vault/auth/oidc/oidc/callback"
|
||||||
|
- "https://vault.service.consul:8200/ui/vault/auth/oidc/oidc/callback"
|
||||||
|
token_ttl: 86400
|
||||||
|
token_max_ttl: 604800
|
||||||
@@ -97,6 +97,27 @@ locals {
|
|||||||
})
|
})
|
||||||
if startswith(file_path, "auth_ldap_group/")
|
if startswith(file_path, "auth_ldap_group/")
|
||||||
}
|
}
|
||||||
|
auth_oidc_backend = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(basename(file_path), ".yaml") => content
|
||||||
|
if startswith(file_path, "auth_oidc_backend/")
|
||||||
|
}
|
||||||
|
auth_oidc_role = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(replace(file_path, "auth_oidc_role/", ""), ".yaml") => merge(content, {
|
||||||
|
role_name = trimsuffix(basename(file_path), ".yaml")
|
||||||
|
backend = dirname(replace(file_path, "auth_oidc_role/", ""))
|
||||||
|
})
|
||||||
|
if startswith(file_path, "auth_oidc_role/")
|
||||||
|
}
|
||||||
|
auth_oidc_group = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(replace(file_path, "auth_oidc_group/", ""), ".yaml") => merge(content, {
|
||||||
|
groupname = trimsuffix(basename(file_path), ".yaml")
|
||||||
|
backend = split("/", replace(file_path, "auth_oidc_group/", ""))[0]
|
||||||
|
})
|
||||||
|
if startswith(file_path, "auth_oidc_group/")
|
||||||
|
}
|
||||||
auth_kubernetes_backend = {
|
auth_kubernetes_backend = {
|
||||||
for file_path, content in local.all_configs :
|
for file_path, content in local.all_configs :
|
||||||
trimsuffix(replace(file_path, "auth_kubernetes_backend/", ""), ".yaml") => content
|
trimsuffix(replace(file_path, "auth_kubernetes_backend/", ""), ".yaml") => content
|
||||||
@@ -198,6 +219,19 @@ locals {
|
|||||||
})
|
})
|
||||||
if startswith(file_path, "litellm_secret_backend_role/")
|
if startswith(file_path, "litellm_secret_backend_role/")
|
||||||
}
|
}
|
||||||
|
arrstack_secret_backend = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(basename(file_path), ".yaml") => content
|
||||||
|
if startswith(file_path, "arrstack_secret_backend/")
|
||||||
|
}
|
||||||
|
arrstack_secret_backend_role = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(replace(file_path, "arrstack_secret_backend_role/", ""), ".yaml") => merge(content, {
|
||||||
|
name = trimsuffix(basename(file_path), ".yaml")
|
||||||
|
backend = dirname(replace(file_path, "arrstack_secret_backend_role/", ""))
|
||||||
|
})
|
||||||
|
if startswith(file_path, "arrstack_secret_backend_role/")
|
||||||
|
}
|
||||||
plugins = {
|
plugins = {
|
||||||
for file_path, content in local.all_configs :
|
for file_path, content in local.all_configs :
|
||||||
trimsuffix(basename(file_path), ".yaml") => merge(content, {
|
trimsuffix(basename(file_path), ".yaml") => merge(content, {
|
||||||
@@ -266,5 +300,18 @@ locals {
|
|||||||
})
|
})
|
||||||
if startswith(file_path, "netbox_secret_backend_role/")
|
if startswith(file_path, "netbox_secret_backend_role/")
|
||||||
}
|
}
|
||||||
|
ghp_secret_backend = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(basename(file_path), ".yaml") => content
|
||||||
|
if startswith(file_path, "ghp_secret_backend/")
|
||||||
|
}
|
||||||
|
ghp_secret_backend_role = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
trimsuffix(replace(file_path, "ghp_secret_backend_role/", ""), ".yaml") => merge(content, {
|
||||||
|
name = trimsuffix(basename(file_path), ".yaml")
|
||||||
|
backend = dirname(replace(file_path, "ghp_secret_backend_role/", ""))
|
||||||
|
})
|
||||||
|
if startswith(file_path, "ghp_secret_backend_role/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Mounts the ghp token secrets engine at "ghp" and writes its config.
|
||||||
|
# The seeded ghp service token is sensitive and read from KV, not stored here:
|
||||||
|
# kv/service/vault/au/syd1/secret_backend/ghp/config
|
||||||
|
# -> key: admin_token (required) the shared ghpsvc_... service token
|
||||||
|
#
|
||||||
|
# admin_token is a static shared secret provisioned into KV by an operator. The
|
||||||
|
# SAME token value must also be present in the running ghp deployment's accepted
|
||||||
|
# service tokens (GHP_AUTH_SERVICE_TOKENS) so ghp ACCEPTS what this engine
|
||||||
|
# PRESENTS. ghp has no rotate endpoint, so the engine never rotates it in place;
|
||||||
|
# the mount uses ignore_changes=[admin_token], making the KV seed create-only
|
||||||
|
# (re-reading a stale KV value never re-pushes it to a live mount).
|
||||||
|
description: "ghp ephemeral scoped agent token engine"
|
||||||
|
base_url: "https://ghp.unkin.net"
|
||||||
|
tls_skip_verify: false
|
||||||
|
request_timeout_seconds: 30
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Role minting ephemeral tokens for the repospawner service user. repospawner
|
||||||
|
# creates and seeds Gitea repositories on demand, so it gets write on
|
||||||
|
# repositories (create + push) and write on issues (issue/PR comments). Read is
|
||||||
|
# implied by write. No admin/org/user-write scopes.
|
||||||
|
# read:user is required because most API clients validate the login via
|
||||||
|
# GET /api/v1/user, which 403s without it.
|
||||||
|
# Reading gitea/creds/repospawner mints a lease-bound token deleted from Gitea
|
||||||
|
# on revoke/expiry. Consumed by the repospawner pods via Kubernetes auth (see
|
||||||
|
# policies/gitea/creds/repospawner.yaml) -- the agents AppRole is CIDR-bound to
|
||||||
|
# Ben's workstation and cannot be used from in-cluster.
|
||||||
|
---
|
||||||
|
username: repospawner
|
||||||
|
scopes:
|
||||||
|
- write:repository
|
||||||
|
- write:issue
|
||||||
|
- read:user
|
||||||
|
token_name_prefix: vault-repospawner
|
||||||
|
ttl: 3600 # 1h
|
||||||
|
max_ttl: 14400 # 4h
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
allowed_kubernetes_namespaces:
|
||||||
|
- "vm-system"
|
||||||
|
- "observability"
|
||||||
|
- "logging"
|
||||||
|
kubernetes_role_type: "Role"
|
||||||
|
extra_labels: {}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# config/plugins/vault-plugin-secrets-arrstack.yaml
|
||||||
|
# Imports (registers) the arrstack secrets plugin in the catalog. Filename =
|
||||||
|
# catalog name = mount type. The binary is installed on the OpenBao nodes by
|
||||||
|
# Puppet (openbao-plugin-secrets-arrstack RPM ->
|
||||||
|
# /opt/openbao-plugins/vault-plugin-secrets-arrstack).
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
type: secret
|
||||||
|
command: vault-plugin-secrets-arrstack
|
||||||
|
version: "0.2.0"
|
||||||
|
sha256: "9ea7f16013b3bcbadfcb15b08fd1545f8069893cf864bfdaa57e7e7e72612fa1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# config/plugins/vault-plugin-secrets-ghp.yaml
|
||||||
|
# Imports (registers) the ghp secrets plugin in the catalog. Filename =
|
||||||
|
# catalog name = mount type. The binary is installed on the OpenBao nodes by
|
||||||
|
# Puppet (openbao-plugin-secrets-ghp RPM ->
|
||||||
|
# /opt/openbao-plugins/vault-plugin-secrets-ghp).
|
||||||
|
#
|
||||||
|
# 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-ghp
|
||||||
|
sha256: "85761421cd532788ed28fb57e93d3868f3577320a538289936d9ed3be5f396de"
|
||||||
@@ -3,6 +3,8 @@ algorithm_signer: rsa-sha2-256
|
|||||||
ttl: 315360000 # 87600 * 3600
|
ttl: 315360000 # 87600 * 3600
|
||||||
allow_host_certificates: true
|
allow_host_certificates: true
|
||||||
allow_user_certificates: false
|
allow_user_certificates: false
|
||||||
allowed_domains: "main.unkin.net,consul"
|
# "*" 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: "*"
|
||||||
allow_subdomains: true
|
allow_subdomains: true
|
||||||
allow_bare_domains: false
|
allow_bare_domains: false
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ inputs = {
|
|||||||
auth_approle_role = local.config.auth_approle_role
|
auth_approle_role = local.config.auth_approle_role
|
||||||
auth_ldap_backend = local.config.auth_ldap_backend
|
auth_ldap_backend = local.config.auth_ldap_backend
|
||||||
auth_ldap_group = local.config.auth_ldap_group
|
auth_ldap_group = local.config.auth_ldap_group
|
||||||
|
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_kubernetes_backend = local.config.auth_kubernetes_backend
|
auth_kubernetes_backend = local.config.auth_kubernetes_backend
|
||||||
auth_kubernetes_role = local.config.auth_kubernetes_role
|
auth_kubernetes_role = local.config.auth_kubernetes_role
|
||||||
kv_secret_backend = local.config.kv_secret_backend
|
kv_secret_backend = local.config.kv_secret_backend
|
||||||
@@ -76,6 +79,8 @@ inputs = {
|
|||||||
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 = local.config.litellm_secret_backend
|
||||||
litellm_secret_backend_role = local.config.litellm_secret_backend_role
|
litellm_secret_backend_role = local.config.litellm_secret_backend_role
|
||||||
|
arrstack_secret_backend = local.config.arrstack_secret_backend
|
||||||
|
arrstack_secret_backend_role = local.config.arrstack_secret_backend_role
|
||||||
plugins = local.config.plugins
|
plugins = local.config.plugins
|
||||||
gpg_secret_backend = local.config.gpg_secret_backend
|
gpg_secret_backend = local.config.gpg_secret_backend
|
||||||
gpg_key = local.config.gpg_key
|
gpg_key = local.config.gpg_key
|
||||||
@@ -90,6 +95,9 @@ inputs = {
|
|||||||
netbox_secret_backend = local.config.netbox_secret_backend
|
netbox_secret_backend = local.config.netbox_secret_backend
|
||||||
netbox_secret_backend_role = local.config.netbox_secret_backend_role
|
netbox_secret_backend_role = local.config.netbox_secret_backend_role
|
||||||
|
|
||||||
|
ghp_secret_backend = local.config.ghp_secret_backend
|
||||||
|
ghp_secret_backend_role = local.config.ghp_secret_backend_role
|
||||||
|
|
||||||
# Pass policy maps to vault_cluster module
|
# Pass policy maps to vault_cluster module
|
||||||
policy_auth_map = local.policies.policy_auth_map
|
policy_auth_map = local.policies.policy_auth_map
|
||||||
policy_rules_map = local.policies.policy_rules_map
|
policy_rules_map = local.policies.policy_rules_map
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ provider "rancher" {
|
|||||||
address = local.vault_addr
|
address = local.vault_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The arrstack (arrproxy API key) secrets engine is managed through its own
|
||||||
|
# provider (same Vault server; token falls back to VAULT_TOKEN).
|
||||||
|
provider "arrstack" {
|
||||||
|
address = local.vault_addr
|
||||||
|
}
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
backend "consul" {
|
backend "consul" {
|
||||||
address = "https://consul.service.consul"
|
address = "https://consul.service.consul"
|
||||||
@@ -59,6 +65,10 @@ terraform {
|
|||||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/ranchervaultsecret"
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/ranchervaultsecret"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
}
|
}
|
||||||
|
arrstack = {
|
||||||
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||||
|
version = "0.2.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -64,6 +64,55 @@ module "auth_ldap_group" {
|
|||||||
depends_on = [module.auth_ldap_backend]
|
depends_on = [module.auth_ldap_backend]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "auth_oidc_backend" {
|
||||||
|
source = "./modules/auth_oidc_backend"
|
||||||
|
|
||||||
|
for_each = var.auth_oidc_backend
|
||||||
|
|
||||||
|
path = each.key
|
||||||
|
description = each.value.description
|
||||||
|
oidc_discovery_url = each.value.oidc_discovery_url
|
||||||
|
client_secret_mount = each.value.client_secret_mount
|
||||||
|
client_secret_path = each.value.client_secret_path
|
||||||
|
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_oidc_role" {
|
||||||
|
source = "./modules/auth_oidc_role"
|
||||||
|
|
||||||
|
for_each = var.auth_oidc_role
|
||||||
|
|
||||||
|
backend = each.value.backend
|
||||||
|
role_name = each.value.role_name
|
||||||
|
user_claim = each.value.user_claim
|
||||||
|
groups_claim = each.value.groups_claim
|
||||||
|
oidc_scopes = each.value.oidc_scopes
|
||||||
|
bound_audiences = each.value.bound_audiences
|
||||||
|
allowed_redirect_uris = each.value.allowed_redirect_uris
|
||||||
|
token_ttl = each.value.token_ttl
|
||||||
|
token_max_ttl = each.value.token_max_ttl
|
||||||
|
# Human authorization comes from the external identity groups below, so a
|
||||||
|
# login role carrying no policies is the normal case.
|
||||||
|
token_policies = try(var.policy_auth_map[each.value.backend][each.value.role_name], [])
|
||||||
|
|
||||||
|
depends_on = [module.auth_oidc_backend]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "auth_oidc_group" {
|
||||||
|
source = "./modules/auth_oidc_group"
|
||||||
|
|
||||||
|
for_each = var.auth_oidc_group
|
||||||
|
|
||||||
|
groupname = each.value.groupname
|
||||||
|
policies = var.policy_auth_map[each.value.backend][each.value.groupname]
|
||||||
|
mount_accessor = module.auth_oidc_backend[each.value.backend].accessor
|
||||||
|
|
||||||
|
depends_on = [module.auth_oidc_backend]
|
||||||
|
}
|
||||||
|
|
||||||
module "auth_kubernetes_backend" {
|
module "auth_kubernetes_backend" {
|
||||||
source = "./modules/auth_kubernetes_backend"
|
source = "./modules/auth_kubernetes_backend"
|
||||||
|
|
||||||
@@ -347,6 +396,36 @@ module "plugin" {
|
|||||||
plugin_version = each.value.version
|
plugin_version = each.value.version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "arrstack_secret_backend" {
|
||||||
|
source = "./modules/arrstack_secret_backend"
|
||||||
|
|
||||||
|
for_each = var.arrstack_secret_backend
|
||||||
|
|
||||||
|
path = each.key
|
||||||
|
plugin = each.value.plugin
|
||||||
|
description = each.value.description
|
||||||
|
base_url = each.value.base_url
|
||||||
|
ca_cert = each.value.ca_cert
|
||||||
|
request_timeout_seconds = each.value.request_timeout_seconds
|
||||||
|
|
||||||
|
depends_on = [module.plugin]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "arrstack_secret_backend_role" {
|
||||||
|
source = "./modules/arrstack_secret_backend_role"
|
||||||
|
|
||||||
|
for_each = var.arrstack_secret_backend_role
|
||||||
|
|
||||||
|
name = each.value.name
|
||||||
|
backend = each.value.backend
|
||||||
|
apps = each.value.apps
|
||||||
|
methods = each.value.methods
|
||||||
|
ttl = each.value.ttl
|
||||||
|
max_ttl = each.value.max_ttl
|
||||||
|
|
||||||
|
depends_on = [module.arrstack_secret_backend]
|
||||||
|
}
|
||||||
|
|
||||||
module "gpg_secret_backend" {
|
module "gpg_secret_backend" {
|
||||||
source = "./modules/gpg_secret_backend"
|
source = "./modules/gpg_secret_backend"
|
||||||
|
|
||||||
@@ -536,6 +615,43 @@ module "netbox_secret_backend_role" {
|
|||||||
depends_on = [module.netbox_secret_backend, module.netbox_user_management]
|
depends_on = [module.netbox_secret_backend, module.netbox_user_management]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "ghp_secret_backend" {
|
||||||
|
source = "./modules/ghp_secret_backend"
|
||||||
|
|
||||||
|
for_each = var.ghp_secret_backend
|
||||||
|
|
||||||
|
path = each.key
|
||||||
|
plugin = each.value.plugin
|
||||||
|
description = each.value.description
|
||||||
|
base_url = each.value.base_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 "ghp_secret_backend_role" {
|
||||||
|
source = "./modules/ghp_secret_backend_role"
|
||||||
|
|
||||||
|
for_each = var.ghp_secret_backend_role
|
||||||
|
|
||||||
|
backend = each.value.backend
|
||||||
|
name = each.value.name
|
||||||
|
token_type = each.value.token_type
|
||||||
|
installation_id = each.value.installation_id
|
||||||
|
app_record_id = each.value.app_record_id
|
||||||
|
repositories = each.value.repositories
|
||||||
|
scopes = each.value.scopes
|
||||||
|
session_prefix = each.value.session_prefix
|
||||||
|
ttl = each.value.ttl
|
||||||
|
max_ttl = each.value.max_ttl
|
||||||
|
|
||||||
|
depends_on = [module.ghp_secret_backend]
|
||||||
|
}
|
||||||
|
|
||||||
module "vault_policy" {
|
module "vault_policy" {
|
||||||
source = "./modules/vault_policy"
|
source = "./modules/vault_policy"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Mounts the arrstack dynamic secrets engine and writes its config via the
|
||||||
|
# vault-secrets-arrstack provider. The plugin is registered in the catalog
|
||||||
|
# separately (config/plugins/vault-plugin-secrets-arrstack.yaml). The arrproxy
|
||||||
|
# admin token is sensitive and read from KV, not stored in git:
|
||||||
|
# kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token -> key "token"
|
||||||
|
# (seeded by argocd-apps #384).
|
||||||
|
data "vault_kv_secret_v2" "admin_token" {
|
||||||
|
mount = "kv"
|
||||||
|
name = var.admin_token_kv_name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "arrstack_secret_backend" "this" {
|
||||||
|
path = var.path
|
||||||
|
plugin = var.plugin
|
||||||
|
description = var.description
|
||||||
|
base_url = var.base_url
|
||||||
|
admin_token = data.vault_kv_secret_v2.admin_token.data[var.admin_token_kv_key]
|
||||||
|
ca_cert = var.ca_cert
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
arrstack = {
|
||||||
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||||
|
version = "0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
variable "path" {
|
||||||
|
description = "Mount path of the arrstack secrets engine (e.g. \"arrstack\")"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "plugin" {
|
||||||
|
description = "Registered plugin name/type to mount (the catalog name = mount type)"
|
||||||
|
type = string
|
||||||
|
default = "vault-plugin-secrets-arrstack"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "description" {
|
||||||
|
description = "Human-friendly description of the mount"
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "base_url" {
|
||||||
|
description = "Base URL of the arrproxy front door (e.g. https://arrstack.unkin.net)"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_token_kv_name" {
|
||||||
|
description = "kv-v2 secret name (relative to the \"kv\" mount) holding the seeded arrproxy admin token"
|
||||||
|
type = string
|
||||||
|
default = "kubernetes/namespace/arrstack/default/arrproxy-admin-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_token_kv_key" {
|
||||||
|
description = "Key within the KV secret that holds the arrproxy admin token"
|
||||||
|
type = string
|
||||||
|
default = "token"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ca_cert" {
|
||||||
|
description = "PEM CA certificate that signed the arrproxy server's TLS cert (optional; omit to use the system trust store)"
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "request_timeout_seconds" {
|
||||||
|
description = "HTTP timeout in seconds for calls from the plugin to arrproxy"
|
||||||
|
type = number
|
||||||
|
default = 30
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
resource "arrstack_secret_backend_role" "this" {
|
||||||
|
backend = var.backend
|
||||||
|
name = var.name
|
||||||
|
apps = var.apps
|
||||||
|
methods = var.methods
|
||||||
|
ttl = var.ttl
|
||||||
|
max_ttl = var.max_ttl
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
arrstack = {
|
||||||
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||||
|
version = "0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
variable "name" {
|
||||||
|
description = "Name of the role"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "backend" {
|
||||||
|
description = "Mount path of the arrstack secrets engine this role belongs to"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "apps" {
|
||||||
|
description = "arr apps a generated key may access (subset of sonarr, radarr, prowlarr)"
|
||||||
|
type = list(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "methods" {
|
||||||
|
description = "HTTP methods a generated key is limited to (subset of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS). Null leaves the role unrestricted"
|
||||||
|
type = set(string)
|
||||||
|
# null, not [], so a role yaml that omits methods matches the provider's null read-back and shows no drift.
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Expected keys in KV secret: client_id, client_secret (generated and written by
|
||||||
|
# terraform-authentik's oauth2 provider module, never seeded by hand).
|
||||||
|
data "vault_kv_secret_v2" "oidc_client" {
|
||||||
|
mount = var.client_secret_mount
|
||||||
|
name = var.client_secret_path
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_jwt_auth_backend" "oidc" {
|
||||||
|
path = var.path
|
||||||
|
type = "oidc"
|
||||||
|
description = var.description
|
||||||
|
oidc_discovery_url = var.oidc_discovery_url
|
||||||
|
oidc_client_id = data.vault_kv_secret_v2.oidc_client.data["client_id"]
|
||||||
|
oidc_client_secret = data.vault_kv_secret_v2.oidc_client.data["client_secret"]
|
||||||
|
default_role = var.default_role
|
||||||
|
|
||||||
|
tune {
|
||||||
|
default_lease_ttl = var.default_lease_ttl
|
||||||
|
max_lease_ttl = var.max_lease_ttl
|
||||||
|
listing_visibility = var.listing_visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
output "accessor" {
|
||||||
|
description = "Accessor of the OIDC auth mount, used to bind identity group aliases"
|
||||||
|
value = vault_jwt_auth_backend.oidc.accessor
|
||||||
|
}
|
||||||
|
|
||||||
|
output "path" {
|
||||||
|
description = "Mount path of the OIDC auth backend"
|
||||||
|
value = vault_jwt_auth_backend.oidc.path
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "5.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
variable "path" {
|
||||||
|
description = "Mount path of the OIDC auth backend"
|
||||||
|
type = string
|
||||||
|
default = "oidc"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "description" {
|
||||||
|
description = "Human-readable description of the auth mount"
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "oidc_discovery_url" {
|
||||||
|
description = "OIDC issuer discovery URL of the identity provider"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "client_secret_mount" {
|
||||||
|
description = "KV-v2 mount holding the OIDC client credentials"
|
||||||
|
type = string
|
||||||
|
default = "kv"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "client_secret_path" {
|
||||||
|
description = "Path within the KV-v2 mount holding client_id and client_secret"
|
||||||
|
type = string
|
||||||
|
default = "service/authentik/oidc-vault"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_role" {
|
||||||
|
description = "Role used when none is supplied at login (the UI/CLI default)"
|
||||||
|
type = string
|
||||||
|
default = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# External identity group: membership is asserted by the IdP through the role's
|
||||||
|
# groups_claim, matched by the alias name below.
|
||||||
|
resource "vault_identity_group" "group" {
|
||||||
|
name = var.groupname
|
||||||
|
type = "external"
|
||||||
|
policies = var.policies
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_identity_group_alias" "alias" {
|
||||||
|
name = var.groupname
|
||||||
|
mount_accessor = var.mount_accessor
|
||||||
|
canonical_id = vault_identity_group.group.id
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "5.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
variable "groupname" {
|
||||||
|
description = "Name of the IdP group, as it appears in the groups claim"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "policies" {
|
||||||
|
description = "List of policies to assign to the identity group"
|
||||||
|
type = list(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "mount_accessor" {
|
||||||
|
description = "Accessor of the OIDC auth mount the alias is bound to"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
resource "vault_jwt_auth_backend_role" "role" {
|
||||||
|
backend = var.backend
|
||||||
|
role_name = var.role_name
|
||||||
|
role_type = "oidc"
|
||||||
|
user_claim = var.user_claim
|
||||||
|
groups_claim = var.groups_claim
|
||||||
|
oidc_scopes = var.oidc_scopes
|
||||||
|
bound_audiences = var.bound_audiences
|
||||||
|
allowed_redirect_uris = var.allowed_redirect_uris
|
||||||
|
token_ttl = var.token_ttl
|
||||||
|
token_max_ttl = var.token_max_ttl
|
||||||
|
token_policies = var.token_policies
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "5.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
variable "backend" {
|
||||||
|
description = "The unique path of the OIDC auth backend to configure"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "role_name" {
|
||||||
|
description = "The name of the role"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "user_claim" {
|
||||||
|
description = "Claim used as the entity alias name (the Vault identity of the human)"
|
||||||
|
type = string
|
||||||
|
default = "email"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "groups_claim" {
|
||||||
|
description = "Claim holding the caller's group memberships, matched against identity group aliases"
|
||||||
|
type = string
|
||||||
|
default = "ak_groups"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "oidc_scopes" {
|
||||||
|
description = "Scopes requested from the identity provider during the authorization request"
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "bound_audiences" {
|
||||||
|
description = "List of audiences (aud claim) accepted in the ID token"
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "allowed_redirect_uris" {
|
||||||
|
description = "Redirect URIs accepted for this role. Must match the provider's registered URIs exactly"
|
||||||
|
type = list(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "token_ttl" {
|
||||||
|
description = "The TTL period of tokens issued using this role, in seconds"
|
||||||
|
type = number
|
||||||
|
default = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "token_max_ttl" {
|
||||||
|
description = "The maximum lifetime for generated tokens in number of seconds. Its current value will be referenced at renewal time."
|
||||||
|
type = number
|
||||||
|
default = 28800
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "token_policies" {
|
||||||
|
description = "List of policies to assign to the role (passed from policy_auth_map). Human authorization normally comes from external identity groups instead"
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Mounts the ghp secrets engine and writes its connection config via the
|
||||||
|
# vault-secrets-ghp provider. The plugin is registered ("imported") in the
|
||||||
|
# catalog separately (config/plugins/vault-plugin-secrets-ghp.yaml). The seeded
|
||||||
|
# ghp service token is sensitive and read from KV, not stored in git:
|
||||||
|
# kv/service/vault/<country>/<region>/secret_backend/<path>/config
|
||||||
|
# Expected key: admin_token (a ghpsvc_... service token that ghp accepts via its
|
||||||
|
# GHP_AUTH_SERVICE_TOKENS list). ghp has no rotate endpoint, so this static
|
||||||
|
# shared secret is the single credential the engine authenticates with.
|
||||||
|
data "vault_kv_secret_v2" "config" {
|
||||||
|
mount = "kv"
|
||||||
|
name = "service/vault/${var.country}/${var.region}/secret_backend/${var.path}/config"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ghp_secret_backend" "this" {
|
||||||
|
path = var.path
|
||||||
|
plugin = var.plugin
|
||||||
|
description = var.description
|
||||||
|
base_url = var.base_url
|
||||||
|
admin_token = data.vault_kv_secret_v2.config.data["admin_token"]
|
||||||
|
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 consumed only when the engine config
|
||||||
|
# is first created. ghp has no rotate endpoint, so re-reading a (possibly
|
||||||
|
# stale) KV value must never re-push it into the live mount. Ignoring the
|
||||||
|
# token makes this module create-only for it (mirrors gitea/netbox config).
|
||||||
|
ignore_changes = [admin_token]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "5.6.0"
|
||||||
|
}
|
||||||
|
ghp = {
|
||||||
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-ghp"
|
||||||
|
version = "0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
variable "path" {
|
||||||
|
description = "Mount path of the ghp secrets engine (e.g. \"ghp\")"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "plugin" {
|
||||||
|
description = "Registered plugin name to mount (the catalog name = mount type)"
|
||||||
|
type = string
|
||||||
|
default = "vault-plugin-secrets-ghp"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "description" {
|
||||||
|
description = "Human-friendly description of the mount"
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "base_url" {
|
||||||
|
description = "Base URL of the ghp server (e.g. https://ghp.unkin.net)"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "country" {
|
||||||
|
description = "Country segment of the KV path holding the seeded admin token"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "region" {
|
||||||
|
description = "Region segment of the KV path holding the seeded admin token"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ca_cert" {
|
||||||
|
description = "PEM CA certificate that signed the ghp 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 ghp server (not recommended)"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "request_timeout_seconds" {
|
||||||
|
description = "HTTP timeout in seconds for calls from the plugin to ghp"
|
||||||
|
type = number
|
||||||
|
default = 30
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# A role that mints short-lived, scoped ghp tokens. Reading ghp/creds/<name>
|
||||||
|
# produces a lease-bound token that is revoked from ghp when the lease is
|
||||||
|
# revoked or reaches max_ttl.
|
||||||
|
resource "ghp_secret_role" "this" {
|
||||||
|
backend = var.backend
|
||||||
|
name = var.name
|
||||||
|
token_type = var.token_type
|
||||||
|
installation_id = var.installation_id
|
||||||
|
app_record_id = var.app_record_id
|
||||||
|
repositories = var.repositories
|
||||||
|
scopes = var.scopes
|
||||||
|
session_prefix = var.session_prefix
|
||||||
|
ttl = var.ttl
|
||||||
|
max_ttl = var.max_ttl
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
ghp = {
|
||||||
|
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-ghp"
|
||||||
|
version = "0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
variable "backend" {
|
||||||
|
description = "Mount path of the ghp secrets engine this role belongs to"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "name" {
|
||||||
|
description = "Role name (read ghp/creds/<name> to mint a token)"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "token_type" {
|
||||||
|
description = "ghp token type to mint: \"agent\" (default) or \"proxy\""
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "installation_id" {
|
||||||
|
description = "ghp App installation id the minted agent token is bound to (required when token_type is \"agent\")"
|
||||||
|
type = number
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "app_record_id" {
|
||||||
|
description = "Optional ghp App record id (UUID) to pin agent tokens to; empty selects ghp's default app"
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "repositories" {
|
||||||
|
description = "Optional repositories the minted token is restricted to; empty is open-scoped (all repositories)"
|
||||||
|
type = list(string)
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "scopes" {
|
||||||
|
description = "Optional ghp permission:level scopes (e.g. [\"contents:read\"]); empty is open-scoped"
|
||||||
|
type = list(string)
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "session_prefix" {
|
||||||
|
description = "Prefix for the ghp session id of each minted token (default \"vault\")"
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -62,6 +62,46 @@ variable "auth_ldap_group" {
|
|||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "auth_oidc_backend" {
|
||||||
|
description = "Map of OIDC (JWT) auth backends to create"
|
||||||
|
type = map(object({
|
||||||
|
oidc_discovery_url = string
|
||||||
|
description = optional(string)
|
||||||
|
client_secret_mount = optional(string, "kv")
|
||||||
|
client_secret_path = optional(string, "service/authentik/oidc-vault")
|
||||||
|
default_role = optional(string, "default")
|
||||||
|
listing_visibility = optional(string)
|
||||||
|
default_lease_ttl = optional(string)
|
||||||
|
max_lease_ttl = optional(string)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_oidc_role" {
|
||||||
|
description = "Map of OIDC auth roles to create"
|
||||||
|
type = map(object({
|
||||||
|
role_name = string
|
||||||
|
backend = string
|
||||||
|
allowed_redirect_uris = list(string)
|
||||||
|
user_claim = optional(string, "email")
|
||||||
|
groups_claim = optional(string, "ak_groups")
|
||||||
|
oidc_scopes = optional(list(string), [])
|
||||||
|
bound_audiences = optional(list(string), [])
|
||||||
|
token_ttl = optional(number, 3600)
|
||||||
|
token_max_ttl = optional(number, 28800)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_oidc_group" {
|
||||||
|
description = "Map of external identity groups bound to an OIDC auth mount"
|
||||||
|
type = map(object({
|
||||||
|
groupname = string
|
||||||
|
backend = string
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "auth_kubernetes_backend" {
|
variable "auth_kubernetes_backend" {
|
||||||
description = "Map of Kubernetes auth backends to create"
|
description = "Map of Kubernetes auth backends to create"
|
||||||
type = map(object({
|
type = map(object({
|
||||||
@@ -316,6 +356,31 @@ variable "litellm_secret_backend_role" {
|
|||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "arrstack_secret_backend" {
|
||||||
|
description = "Map of arrstack secret engines to create (mount + config). The arrproxy admin token is read from KV"
|
||||||
|
type = map(object({
|
||||||
|
plugin = optional(string, "vault-plugin-secrets-arrstack")
|
||||||
|
description = optional(string)
|
||||||
|
base_url = string
|
||||||
|
ca_cert = optional(string)
|
||||||
|
request_timeout_seconds = optional(number, 30)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "arrstack_secret_backend_role" {
|
||||||
|
description = "Map of arrstack roles to create"
|
||||||
|
type = map(object({
|
||||||
|
name = string
|
||||||
|
backend = string
|
||||||
|
apps = list(string)
|
||||||
|
methods = optional(set(string))
|
||||||
|
ttl = optional(number)
|
||||||
|
max_ttl = optional(number)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "plugins" {
|
variable "plugins" {
|
||||||
description = "Map of plugins to import (register) in the catalog, keyed by catalog name"
|
description = "Map of plugins to import (register) in the catalog, keyed by catalog name"
|
||||||
type = map(object({
|
type = map(object({
|
||||||
@@ -468,6 +533,36 @@ variable "netbox_backend_aliases" {
|
|||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "ghp_secret_backend" {
|
||||||
|
description = "Map of ghp token secret engines to create (mount + config; seeded service token read from KV)"
|
||||||
|
type = map(object({
|
||||||
|
plugin = optional(string, "vault-plugin-secrets-ghp")
|
||||||
|
description = optional(string)
|
||||||
|
base_url = string
|
||||||
|
ca_cert = optional(string)
|
||||||
|
tls_skip_verify = optional(bool, false)
|
||||||
|
request_timeout_seconds = optional(number, 30)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ghp_secret_backend_role" {
|
||||||
|
description = "Map of ghp engine roles; reading ghp/creds/<name> mints a short-lived scoped ghp token"
|
||||||
|
type = map(object({
|
||||||
|
name = string
|
||||||
|
backend = string
|
||||||
|
token_type = optional(string)
|
||||||
|
installation_id = optional(number)
|
||||||
|
app_record_id = optional(string)
|
||||||
|
repositories = optional(list(string))
|
||||||
|
scopes = optional(list(string))
|
||||||
|
session_prefix = optional(string)
|
||||||
|
ttl = optional(number)
|
||||||
|
max_ttl = optional(number)
|
||||||
|
}))
|
||||||
|
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,27 @@
|
|||||||
|
# Allow management of the arrstack secrets engine (config and roles) by the
|
||||||
|
# terraform-vault deployer.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "arrstack/config"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- read
|
||||||
|
- delete
|
||||||
|
- path: "arrstack/roles/*"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
- path: "arrstack/roles"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Allow the mediamark app to mint its own Sonarr/Radarr-scoped arrproxy key.
|
||||||
|
# Bound to the dedicated "mediamark" k8s role only, never the shared "default"
|
||||||
|
# role, so no other namespace can read these creds.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "arrstack/creds/mediamark"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
k8s/au/syd1:
|
||||||
|
- mediamark
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Allow the terraform-prowlarr run to mint a Prowlarr-scoped arrproxy key.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "arrstack/creds/prowlarr"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- terraform_prowlarr
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_prowlarr
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Allow the terraform-radarr run to mint a Radarr-scoped arrproxy key.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "arrstack/creds/radarr"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- terraform_radarr
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_radarr
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Allow the terraform-sonarr run to mint a Sonarr-scoped arrproxy key.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "arrstack/creds/sonarr"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- terraform_sonarr
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_sonarr
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Allow full administration of the OIDC auth backend (mount config and login
|
||||||
|
# roles), mirroring policies/auth/ldap/admin.yaml. sys/auth/* already covers
|
||||||
|
# enabling the mount itself; this covers writing auth/oidc/config and
|
||||||
|
# auth/oidc/role/*.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "auth/oidc/*"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- read
|
||||||
|
- delete
|
||||||
|
- list
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Allow the vault deployer to manage the ghp token secrets engine: its
|
||||||
|
# connection config (seeded service token) and its token-minting roles.
|
||||||
|
#
|
||||||
|
# Scoped to ghp/* only, and deliberately excludes ghp/creds/* - minting tokens
|
||||||
|
# is for consumers, not the deployer. ghp has NO rotate endpoint, so unlike the
|
||||||
|
# gitea/netbox engines there is no config/rotate grant here. 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 gitea/netbox engines).
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
# Engine connection config (base_url, TLS, seeded service token).
|
||||||
|
- path: "ghp/config"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- read
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
# Token-minting roles.
|
||||||
|
- path: "ghp/roles/*"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- read
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- list
|
||||||
|
- path: "ghp/roles"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Lets the agents AppRole mint ephemeral ghp agent tokens, so AI coding agents
|
||||||
|
# authenticate to ghp as their own short-lived, least-privilege identity.
|
||||||
|
# Reading ghp/creds/agent returns a lease-bound token scoped by the role
|
||||||
|
# (token_type agent, contents:read). Mirrors the gitea/creds/unkin-agent binding.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "ghp/creds/agent"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- agents
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Lets the repospawner pods mint ephemeral Gitea tokens for the repospawner
|
||||||
|
# service user, so the service authenticates to git.unkin.net as its own
|
||||||
|
# least-privilege identity. Reading gitea/creds/repospawner returns a
|
||||||
|
# lease-bound token scoped by the role (write:repository, write:issue,
|
||||||
|
# read:user -- never merge/admin).
|
||||||
|
# Kubernetes auth only: repospawner runs in-cluster and the agents AppRole is
|
||||||
|
# CIDR-bound to Ben's workstation.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "gitea/creds/repospawner"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
k8s/au/syd1:
|
||||||
|
- repospawner
|
||||||
@@ -10,6 +10,12 @@ rules:
|
|||||||
- list
|
- list
|
||||||
- sudo
|
- sudo
|
||||||
|
|
||||||
|
# The oidc entry is an Authentik akP-* permission group, not an LDAP group name:
|
||||||
|
# it names the external identity group under config/auth_oidc_group, so a human
|
||||||
|
# who logs in via Authentik SSO lands on exactly the policy the LDAP vault_admin
|
||||||
|
# group already carries. akR-* roles are grouping-only and never named here.
|
||||||
auth:
|
auth:
|
||||||
ldap:
|
ldap:
|
||||||
- vault_admin
|
- vault_admin
|
||||||
|
oidc:
|
||||||
|
- akP-vault-admin
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# 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"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- path: "identity/group/*"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- 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:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 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,17 +0,0 @@
|
|||||||
# Allow ghp to read its GitHub App credentials and encryption key
|
|
||||||
#
|
|
||||||
# kv/kubernetes/ghp/github-app (app_id/client_id/client_secret/private_key)
|
|
||||||
# kv/kubernetes/ghp/app (encryption_key)
|
|
||||||
---
|
|
||||||
rules:
|
|
||||||
- path: "kv/data/kubernetes/ghp/*"
|
|
||||||
capabilities:
|
|
||||||
- read
|
|
||||||
- path: "kv/metadata/kubernetes/ghp/*"
|
|
||||||
capabilities:
|
|
||||||
- read
|
|
||||||
- list
|
|
||||||
|
|
||||||
auth:
|
|
||||||
k8s/au/syd1:
|
|
||||||
- ghp
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Allow the terraform-vault deployer to read the seeded arrproxy admin token so
|
||||||
|
# the arrstack engine config module can source it. The token is seeded by
|
||||||
|
# argocd-apps #384 at kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token.
|
||||||
|
# The deployer's existing secret_backends_read policy only covers
|
||||||
|
# kv/data/service/vault/+/+/secret_backend/*, which does not match this
|
||||||
|
# kubernetes/namespace path, so this adds the minimal read grant rather than
|
||||||
|
# duplicating the secret into the litellm-style path. vault_kv_secret_v2 also
|
||||||
|
# reads the kv-v2 metadata path on every plan/apply, so grant that too.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/kubernetes/namespace/arrstack/default/arrproxy-admin-token"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
- path: "kv/metadata/kubernetes/namespace/arrstack/default/arrproxy-admin-token"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Lets the agents AppRole read a dedicated, scoped Authentik API token seeded by
|
||||||
|
# the operator. Automation seeds OAuth2 client secrets and LDAP outpost tokens as
|
||||||
|
# part of normal IaC workflows; fetching an outpost token requires calling the
|
||||||
|
# Authentik API, so a scoped token (not the CI admin credential) is read here.
|
||||||
|
# Mirrors the gitea/creds/unkin-agent agents-approle read grant pattern.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/service/authentik/agent-api-token"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- agents
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Let the terraform-vault deployer read the OpenBao OIDC client credentials that
|
||||||
|
# Authentik's provider module generates and writes here (terraform_authentik owns
|
||||||
|
# kv/service/authentik/* — see policies/kv/service/authentik/write.yaml). The
|
||||||
|
# deployer consumes client_id/client_secret to configure the auth/oidc backend.
|
||||||
|
#
|
||||||
|
# OIDC becomes the default human auth path; approle and k8s (CI and agents) plus
|
||||||
|
# the break-glass root path are unchanged.
|
||||||
|
#
|
||||||
|
# AppRole capabilities are fixed at login, so this grant must be applied before
|
||||||
|
# the PR that adds the auth/oidc modules.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/service/authentik/oidc-vault"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- tf_vault
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_vault
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Let the terraform-authentik runner manage the Authentik service KV subtree as
|
||||||
|
# IaC (vault_kv_secret_v2), starting with kv/service/authentik/agent-api-token,
|
||||||
|
# which is currently seeded by hand.
|
||||||
|
#
|
||||||
|
# Scoping: the ask was to grant only *-token paths, but Vault ACL paths support
|
||||||
|
# a trailing glob only (no infix/suffix wildcards), so the grant is the whole
|
||||||
|
# kv/service/authentik/ subtree. That is acceptable because terraform-authentik
|
||||||
|
# is the owner of every secret under kv/service/authentik/.
|
||||||
|
#
|
||||||
|
# delete is included (unlike the agents kv/kubernetes grant) so `terraform
|
||||||
|
# destroy` and resource replacement can remove a secret and its metadata
|
||||||
|
# cleanly; vault_kv_secret_v2 also reads the kv-v2 metadata path on every
|
||||||
|
# plan/apply, so metadata read/list is required for the plan to succeed.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/service/authentik/*"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- read
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- path: "kv/metadata/service/authentik/*"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
- delete
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- terraform_authentik
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_authentik
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Allow reading Ceph mediafs CSI secrets
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/service/kubernetes/au/syd1/csi/ceph-mediafs-secret"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
k8s/au/syd1:
|
||||||
|
- ceph-csi
|
||||||
@@ -10,6 +10,11 @@ rules:
|
|||||||
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
|
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
|
||||||
capabilities:
|
capabilities:
|
||||||
- read
|
- read
|
||||||
|
# Second OIDC client in an already-onboarded namespace, so it cannot use the
|
||||||
|
# one-per-namespace oauth-credentials path above.
|
||||||
|
- path: "kv/data/kubernetes/namespace/logging/default/vlogs-oauth-credentials"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Allow the agents AppRole to seed the ghp secrets engine's admin credentials.
|
||||||
|
# The ghp engine (managed by the tf_vault deployer) reads its ghpsvc_ service
|
||||||
|
# token from admin_token at ghp/config creation time; that key is currently
|
||||||
|
# unseeded, so the master apply aborts on the ghp data-source read. This grants
|
||||||
|
# the agents AppRole scoped write here so an agent can seed the value. Deployer
|
||||||
|
# read is already granted via policies/kv/service/vault/secret_backends_read.yaml,
|
||||||
|
# so this only adds the write side for the agents identity.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
# Seed value: admin_token (a ghpsvc_ service token). create/update to seed,
|
||||||
|
# read so the agent can verify what it wrote. No delete (least privilege).
|
||||||
|
- path: "kv/data/service/vault/au/syd1/secret_backend/ghp/config"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- read
|
||||||
|
# vault_kv_secret_v2 also reads the kv-v2 metadata path on every plan/apply.
|
||||||
|
- path: "kv/metadata/service/vault/au/syd1/secret_backend/ghp/config"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- agents
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Lets the agents AppRole read and maintain a dedicated Woodpecker API token.
|
||||||
|
# Agents query the Woodpecker API to inspect pipeline runs and failing steps when
|
||||||
|
# reviewing PRs; today that token is pasted into agent config by hand. Granting
|
||||||
|
# create/update as well as read lets automation seed and rotate it in place,
|
||||||
|
# mirroring the agents-approle grant on kv/kubernetes/*. delete is excluded, as
|
||||||
|
# it is there.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "kv/data/service/woodpecker/tokens/agents"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- read
|
||||||
|
- update
|
||||||
|
- path: "kv/metadata/service/woodpecker/tokens/agents"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- agents
|
||||||
@@ -17,3 +17,5 @@ rules:
|
|||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
- certmanager
|
- certmanager
|
||||||
|
k8s/au/syd1:
|
||||||
|
- puppet_certmanager
|
||||||
|
|||||||
@@ -17,3 +17,5 @@ rules:
|
|||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
- certmanager
|
- certmanager
|
||||||
|
k8s/au/syd1:
|
||||||
|
- puppet_certmanager
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Allow the terraform-rancher runner to mint an ephemeral Rancher API token
|
||||||
|
# from the "ci" role, so its Makefile can swap
|
||||||
|
# vault kv get kv/service/terraform/rancher
|
||||||
|
# for
|
||||||
|
# vault read -field=token rancher/creds/ci
|
||||||
|
# and retire the static 90-day admin token. The minted token is lease-bound
|
||||||
|
# (ttl 1h, max 8h) and deleted from Rancher on revoke; it inherits the seeded
|
||||||
|
# admin service account's RBAC, so this is the same privilege as the static
|
||||||
|
# token it replaces, just short-lived.
|
||||||
|
---
|
||||||
|
rules:
|
||||||
|
- path: "rancher/creds/ci"
|
||||||
|
capabilities:
|
||||||
|
- read
|
||||||
|
|
||||||
|
auth:
|
||||||
|
approle:
|
||||||
|
- terraform_rancher
|
||||||
|
k8s/au/syd1:
|
||||||
|
- woodpecker_terraform_rancher
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Allow signing SSH host certificates
|
# Allow signing SSH host certificates
|
||||||
---
|
---
|
||||||
rules:
|
rules:
|
||||||
- path: "sshca/sign/host"
|
- path: "sshca/sign/signhost"
|
||||||
capabilities:
|
capabilities:
|
||||||
- create
|
- create
|
||||||
- update
|
- update
|
||||||
@@ -9,3 +9,5 @@ rules:
|
|||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
- sshsigner
|
- sshsigner
|
||||||
|
k8s/au/syd1:
|
||||||
|
- puppet_sshsigner
|
||||||
@@ -9,3 +9,5 @@ rules:
|
|||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
- sshsigner
|
- sshsigner
|
||||||
|
k8s/au/syd1:
|
||||||
|
- puppet_sshsigner
|
||||||
|
|||||||
@@ -8,6 +8,24 @@ rules:
|
|||||||
- delete
|
- delete
|
||||||
- read
|
- read
|
||||||
- list
|
- list
|
||||||
|
# Enabling/tuning an auth mount is sudo-protected, and an exact path match
|
||||||
|
# wins over the wildcard above, so both rules repeat the full capability set.
|
||||||
|
- path: "sys/auth/oidc"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
- sudo
|
||||||
|
- path: "sys/auth/oidc/tune"
|
||||||
|
capabilities:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- read
|
||||||
|
- list
|
||||||
|
- sudo
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
approle:
|
approle:
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
rules:
|
||||||
|
# Verbs are listed explicitly rather than "*": delete on a VLCluster reclaims
|
||||||
|
# its cephrbd-fast-delete PVCs, destroying the log store.
|
||||||
|
- apiGroups:
|
||||||
|
- "operator.victoriametrics.com"
|
||||||
|
resources:
|
||||||
|
- "*"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "list"
|
||||||
|
- "watch"
|
||||||
|
- "create"
|
||||||
|
- "patch"
|
||||||
|
- "update"
|
||||||
|
- apiGroups:
|
||||||
|
- "apps"
|
||||||
|
resources:
|
||||||
|
- "deployments"
|
||||||
|
- "statefulsets"
|
||||||
|
- "daemonsets"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "list"
|
||||||
|
- "watch"
|
||||||
|
- "patch"
|
||||||
|
- "update"
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- "pods"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "list"
|
||||||
|
- "watch"
|
||||||
|
- "delete"
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- "pods/log"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- "services"
|
||||||
|
- "configmaps"
|
||||||
|
- "endpoints"
|
||||||
|
- "events"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "list"
|
||||||
|
- "watch"
|
||||||
|
- apiGroups:
|
||||||
|
- "gateway.networking.k8s.io"
|
||||||
|
resources:
|
||||||
|
- "gateways"
|
||||||
|
- "httproutes"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "list"
|
||||||
|
- "watch"
|
||||||
@@ -22,6 +22,28 @@ rules:
|
|||||||
- "elasticsearch.k8s.elastic.co"
|
- "elasticsearch.k8s.elastic.co"
|
||||||
- "kibana.k8s.elastic.co"
|
- "kibana.k8s.elastic.co"
|
||||||
- "gateway.networking.k8s.io"
|
- "gateway.networking.k8s.io"
|
||||||
|
- "apm.k8s.elastic.co"
|
||||||
|
- "beat.k8s.elastic.co"
|
||||||
|
- "agent.k8s.elastic.co"
|
||||||
|
- "maps.k8s.elastic.co"
|
||||||
|
- "enterprisesearch.k8s.elastic.co"
|
||||||
|
- "autoscaling.k8s.elastic.co"
|
||||||
|
- "stackconfigpolicy.k8s.elastic.co"
|
||||||
|
- "valkey.io"
|
||||||
|
- "ceph.unkin.net"
|
||||||
|
- "bind.unkin.net"
|
||||||
|
- "kea.unkin.net"
|
||||||
|
- "k8up.io"
|
||||||
|
- "grafana.integreatly.org"
|
||||||
|
- "operator.victoriametrics.com"
|
||||||
|
- "clickhouse.altinity.com"
|
||||||
|
- "clickhouse-keeper.altinity.com"
|
||||||
|
- "acme.cert-manager.io"
|
||||||
|
- "deviceplugin.intel.com"
|
||||||
|
- "fpga.intel.com"
|
||||||
|
- "autoscaling.k8s.io"
|
||||||
|
- "snapshot.storage.k8s.io"
|
||||||
|
- "groupsnapshot.storage.k8s.io"
|
||||||
resources:
|
resources:
|
||||||
- "*"
|
- "*"
|
||||||
verbs:
|
verbs:
|
||||||
|
|||||||
Reference in New Issue
Block a user