vault: wire up ghp secrets engine (backend + role + policies) (#121)
ci/woodpecker/push/apply Pipeline failed
ci/woodpecker/push/apply Pipeline failed
Why: final step of the ordered ghp plugin add. Mounts + configures the ghp OpenBao secrets engine and its token-minting role (catalog registration is #123; policies #122). How: config/ghp_secret_backend/ghp.yaml (mount at ghp; base_url https://ghp.unkin.net; admin_token read from KV kv/service/vault/au/syd1/secret_backend/ghp/config), config/ghp_secret_backend_role/ghp/agent.yaml (agent role), the two vault_cluster submodules, and the config.hcl ghp_secret_backend/role discovery + terragrunt inputs pulling the ghp provider (vault-secrets-ghp v0.1.0). ORDER — apply in sequence: 1. puppet-prod#520 — install openbao-plugin-secrets-ghp binary (merged). 2. terraform-vault#122 — config-write policy (merged). 3. terraform-vault#123 — register plugin in catalog. 4. THIS PR — mount + config + role. Before apply is clean: set the role real ghp App installation_id (currently sentinel 0), and KV dual-seed the ghpsvc_ token (this engine admin_token + the ghp deployment GHP_AUTH_SERVICE_TOKENS). Reviewed-on: #121 Co-authored-by: unkin-agent <unkin-agent@unkin.net> Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #121.
This commit is contained in:
@@ -536,6 +536,43 @@ module "netbox_secret_backend_role" {
|
||||
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" {
|
||||
source = "./modules/vault_policy"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -468,6 +468,36 @@ variable "netbox_backend_aliases" {
|
||||
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" {
|
||||
description = "Map of auth mounts -> auth roles -> policy names"
|
||||
type = map(map(list(string)))
|
||||
|
||||
Reference in New Issue
Block a user