0e82cda02d
ci/woodpecker/push/apply Pipeline failed
## Why Creates the arrstack secrets engine itself: the mount + config and the per-scope roles that mint arrproxy API keys. **PR 3 of 3 (resources)**, stacked on #126 (policy). Final step of the register -> policy -> resources split (was #124). ## Change - Adds `config/arrstack_secret_backend/arrstack.yaml`: mounts the engine at `arrstack` and writes its config (`base_url`, timeout). The arrproxy admin token stays out of git and is read from KV by the module. - Adds `config/arrstack_secret_backend_role/arrstack/{all,sonarr,radarr,prowlarr}.yaml`: roles scoped to each arr app (plus one covering all three). Default `ttl` is **60s** (short-lived, renewed on demand); `max_ttl` 86400 mirrors the litellm sibling convention. The engine additionally caps renewal at the arrproxy admin token's fixed mint expiry. - Adds `modules/vault_cluster/modules/arrstack_secret_backend{,_role}` and wires them in: `config/config.hcl` maps, `modules/vault_cluster/main.tf`, `variables.tf`, the environment inputs, and the root provider block. - Provider source is `artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack` (repo `terraform-provider-vault-secrets-arrstack`), local name `arrstack`. ## Apply order Apply **after PR #126 (policy) AND after `terraform-provider-vault-secrets-arrstack` v0.1.0 is published** to the artifactapi terraform registry. Until the provider is published, `tofu init` cannot resolve it, so **CI/plan on this PR is red by design** — that is expected, not a regression. Note **plan-green != apply-green**: the KV-sourced `admin_token` is only fetched at apply time, so a green plan does not prove the seeded token is readable. ## Stack 1. register -> #125 2. policy -> #126 3. **resources (this PR)** -> `benvin/arrstack-resources` off `benvin/arrstack-policy` Supersedes #124. --------- Co-authored-by: unkin-agent <unkin-agent@git.unkin.net> Co-authored-by: BenVincent <benvin@main.unkin.net> Reviewed-on: #127 Co-authored-by: unkin-agent <unkin-agent@unkin.net> Co-committed-by: unkin-agent <unkin-agent@unkin.net>
540 lines
18 KiB
Terraform
540 lines
18 KiB
Terraform
variable "country" {
|
|
description = "Country identifier"
|
|
type = string
|
|
}
|
|
|
|
variable "region" {
|
|
description = "Region identifier"
|
|
type = string
|
|
}
|
|
|
|
variable "auth_approle_backend" {
|
|
description = "Map of AppRole auth backends to create"
|
|
type = map(object({
|
|
listing_visibility = optional(string)
|
|
default_lease_ttl = optional(string)
|
|
max_lease_ttl = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "auth_approle_role" {
|
|
description = "Map of AppRole roles to create"
|
|
type = map(object({
|
|
approle_name = string
|
|
mount_path = string
|
|
token_ttl = optional(number)
|
|
token_max_ttl = optional(number)
|
|
bind_secret_id = optional(bool, false)
|
|
secret_id_ttl = optional(number)
|
|
token_bound_cidrs = optional(list(string), [])
|
|
alias_metadata = optional(map(string))
|
|
use_deterministic_role_id = optional(bool, true)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "auth_ldap_backend" {
|
|
description = "Map of LDAP auth backends to create"
|
|
type = map(object({
|
|
userdn = string
|
|
userattr = optional(string, "uid")
|
|
upndomain = optional(string)
|
|
discoverdn = optional(bool, false)
|
|
groupdn = optional(string)
|
|
groupfilter = optional(string)
|
|
groupattr = optional(string, "cn")
|
|
alias_metadata = optional(map(string))
|
|
username_as_alias = optional(bool, true)
|
|
listing_visibility = optional(string)
|
|
default_lease_ttl = optional(string)
|
|
max_lease_ttl = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "auth_ldap_group" {
|
|
description = "Map of LDAP groups to create"
|
|
type = map(object({
|
|
groupname = string
|
|
backend = string
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "auth_kubernetes_backend" {
|
|
description = "Map of Kubernetes auth backends to create"
|
|
type = map(object({
|
|
kubernetes_host = string
|
|
disable_iss_validation = optional(bool, true)
|
|
use_annotations_as_alias_metadata = optional(bool, true)
|
|
listing_visibility = optional(string)
|
|
default_lease_ttl = optional(string)
|
|
max_lease_ttl = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "auth_kubernetes_role" {
|
|
description = "Map of Kubernetes auth roles to create"
|
|
type = map(object({
|
|
role_name = string
|
|
backend = string
|
|
bound_service_account_names = list(string)
|
|
bound_service_account_namespaces = list(string)
|
|
token_ttl = optional(number, 3600)
|
|
token_max_ttl = optional(number, 86400)
|
|
audience = optional(string, "vault")
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "kv_secret_backend" {
|
|
description = "Map of KV secret engines to create"
|
|
type = map(object({
|
|
type = optional(string, "kv-v2")
|
|
description = optional(string)
|
|
version = optional(string, "2")
|
|
max_versions = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "transit_secret_backend" {
|
|
description = "Map of Transit secret engines to create"
|
|
type = map(object({
|
|
description = optional(string)
|
|
default_lease_ttl_seconds = optional(number, 3600)
|
|
max_lease_ttl_seconds = optional(number, 86400)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "transit_secret_backend_key" {
|
|
description = "Map of Transit keys to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
type = optional(string, "aes256-gcm96")
|
|
deletion_allowed = optional(bool, false)
|
|
derived = optional(bool, false)
|
|
exportable = optional(bool, false)
|
|
allow_plaintext_backup = optional(bool, false)
|
|
auto_rotate_period = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "ssh_secret_backend" {
|
|
description = "Map of SSH secret engines to create"
|
|
type = map(object({
|
|
description = optional(string)
|
|
max_lease_ttl_seconds = optional(number, 315360000)
|
|
generate_signing_key = optional(bool)
|
|
key_type = optional(string, "ssh-rsa")
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "ssh_secret_backend_role" {
|
|
description = "Map of SSH roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
key_type = optional(string, "ca")
|
|
algorithm_signer = optional(string, "rsa-sha2-256")
|
|
ttl = optional(number, 315360000)
|
|
allow_host_certificates = optional(bool, false)
|
|
allow_user_certificates = optional(bool, false)
|
|
allowed_domains = optional(string)
|
|
allow_subdomains = optional(bool, false)
|
|
allow_bare_domains = optional(bool, false)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "pki_secret_backend" {
|
|
description = "Map of PKI secret engines to create"
|
|
type = map(object({
|
|
description = optional(string)
|
|
max_lease_ttl_seconds = optional(number, 315360000)
|
|
common_name = string
|
|
issuer_name = string
|
|
ttl = optional(number, 315360000)
|
|
format = optional(string, "pem")
|
|
issuing_certificates = optional(list(string), [])
|
|
crl_distribution_points = optional(list(string), [])
|
|
ocsp_servers = optional(list(string), [])
|
|
enable_templating = optional(bool, false)
|
|
default_follows_latest_issuer = optional(bool, false)
|
|
crl_expiry = optional(string, "72h")
|
|
crl_disable = optional(bool, false)
|
|
ocsp_disable = optional(bool, false)
|
|
auto_rebuild = optional(bool, false)
|
|
enable_delta = optional(bool, false)
|
|
delta_rebuild_interval = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "pki_secret_backend_role" {
|
|
description = "Map of PKI roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
allow_ip_sans = optional(bool, false)
|
|
allowed_domains = optional(list(string), [])
|
|
allow_subdomains = optional(bool, false)
|
|
allow_glob_domains = optional(bool, false)
|
|
allow_bare_domains = optional(bool, false)
|
|
enforce_hostnames = optional(bool, false)
|
|
allow_any_name = optional(bool, false)
|
|
max_ttl = optional(number)
|
|
key_bits = optional(number, 4096)
|
|
country = optional(list(string), [])
|
|
use_csr_common_name = optional(bool, false)
|
|
use_csr_sans = optional(bool, false)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "pki_mount_only" {
|
|
description = "Map of PKI mounts to create (without certificate generation)"
|
|
type = map(object({
|
|
description = optional(string)
|
|
max_lease_ttl_seconds = optional(number, 315360000)
|
|
issuing_certificates = optional(list(string), [])
|
|
crl_distribution_points = optional(list(string), [])
|
|
ocsp_servers = optional(list(string), [])
|
|
enable_templating = optional(bool, false)
|
|
default_issuer_ref = optional(string)
|
|
default_follows_latest_issuer = optional(bool, false)
|
|
crl_expiry = optional(string, "72h")
|
|
crl_disable = optional(bool, false)
|
|
ocsp_disable = optional(bool, false)
|
|
auto_rebuild = optional(bool, false)
|
|
enable_delta = optional(bool, false)
|
|
delta_rebuild_interval = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "consul_secret_backend" {
|
|
description = "Map of Consul secret engines to create"
|
|
type = map(object({
|
|
description = optional(string)
|
|
address = string
|
|
bootstrap = optional(bool, false)
|
|
bootstrap_token = optional(string)
|
|
scheme = optional(string, "https")
|
|
ca_cert = optional(string)
|
|
client_cert = optional(string)
|
|
client_key = optional(string)
|
|
default_lease_ttl_seconds = optional(number)
|
|
max_lease_ttl_seconds = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "consul_secret_backend_role" {
|
|
description = "Map of Consul roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
consul_roles = optional(list(string), [])
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
local = optional(bool, false)
|
|
datacenters = optional(list(string))
|
|
description = optional(string)
|
|
service_identities = optional(list(object({
|
|
service_name = string
|
|
datacenters = optional(list(string))
|
|
})))
|
|
node_identities = optional(list(object({
|
|
node_name = string
|
|
datacenter = string
|
|
})))
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "consul_backend_aliases" {
|
|
description = "Map of consul backend names to sanitized provider aliases"
|
|
type = map(string)
|
|
default = {}
|
|
}
|
|
|
|
variable "kubernetes_secret_backend" {
|
|
description = "Map of Kubernetes secret engines to create"
|
|
type = map(object({
|
|
description = optional(string)
|
|
default_lease_ttl_seconds = optional(number, 600)
|
|
max_lease_ttl_seconds = optional(number, 86400)
|
|
kubernetes_host = string
|
|
disable_local_ca_jwt = optional(bool, false)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "kubernetes_secret_backend_role" {
|
|
description = "Map of Kubernetes secret backend roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
allowed_kubernetes_namespaces = optional(list(string), ["*"])
|
|
kubernetes_role_type = optional(string, "Role")
|
|
extra_labels = optional(map(string), {})
|
|
service_account_name = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "litellm_secret_backend" {
|
|
description = "Map of LiteLLM secret engines to create (mount + config). The master key is read from KV"
|
|
type = map(object({
|
|
plugin = optional(string, "vault-plugin-secrets-litellm")
|
|
description = optional(string)
|
|
base_url = string
|
|
request_timeout_seconds = optional(number, 30)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "litellm_secret_backend_role" {
|
|
description = "Map of LiteLLM roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
models = optional(list(string))
|
|
max_budget = optional(number)
|
|
key_alias_prefix = optional(string)
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
metadata = optional(map(string))
|
|
}))
|
|
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)
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "plugins" {
|
|
description = "Map of plugins to import (register) in the catalog, keyed by catalog name"
|
|
type = map(object({
|
|
name = string
|
|
type = optional(string, "secret")
|
|
command = optional(string)
|
|
sha256 = string
|
|
version = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "gpg_secret_backend" {
|
|
description = "Map of GPG/OpenPGP secret engines to mount (path => registered plugin + description). The plugin is registered separately via config/plugins."
|
|
type = map(object({
|
|
plugin = optional(string, "vault-plugin-secrets-gpg")
|
|
description = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "gpg_key" {
|
|
description = "Map of OpenPGP keys to manage in a gpg engine mount"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
algorithm = optional(string, "rsa-3072")
|
|
identity = optional(string)
|
|
exportable = optional(bool, false)
|
|
deletion_allowed = optional(bool, false)
|
|
min_decryption_version = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "rancher_secret_backend" {
|
|
description = "Map of rancher token secret engines to create (mount + config)"
|
|
type = map(object({
|
|
plugin = optional(string, "vault-plugin-secrets-rancher")
|
|
description = optional(string)
|
|
rancher_url = string
|
|
ca_cert = optional(string)
|
|
tls_skip_verify = optional(bool, false)
|
|
request_timeout_seconds = optional(number, 30)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "rancher_secret_backend_service_account" {
|
|
description = "Map of seeded, auto-rotated rancher service-account tokens (seed token read from KV)"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
token_ttl = optional(number)
|
|
rotation_period = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "rancher_secret_backend_role" {
|
|
description = "Map of rancher token-minting roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
service_account = string
|
|
cluster_name = optional(string)
|
|
description = optional(string)
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "gitea_secret_backend" {
|
|
description = "Map of gitea token secret engines to create (mount + config; seeded admin creds read from KV)"
|
|
type = map(object({
|
|
plugin = optional(string, "vault-plugin-secrets-gitea")
|
|
description = optional(string)
|
|
gitea_url = string
|
|
ca_cert = optional(string)
|
|
tls_skip_verify = optional(bool, false)
|
|
request_timeout_seconds = optional(number, 30)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "gitea_secret_backend_role" {
|
|
description = "Map of gitea token-minting roles to create"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
username = string
|
|
scopes = list(string)
|
|
token_name_prefix = optional(string)
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "netbox_secret_backend" {
|
|
description = "Map of netbox token secret engines to create (mount + config; seeded admin token read from KV)"
|
|
type = map(object({
|
|
plugin = optional(string, "vault-plugin-secrets-netbox")
|
|
description = optional(string)
|
|
netbox_url = string
|
|
token_version = optional(number, 2)
|
|
ca_cert = optional(string)
|
|
tls_skip_verify = optional(bool, false)
|
|
request_timeout_seconds = optional(number, 30)
|
|
# Pre-existing NetBox superuser (or add_user + add_token + grant_token) the
|
|
# engine mints an ephemeral user-admin token for, so netbox_user_management
|
|
# authenticates with a Vault-minted credential derived from the single static
|
|
# admin token instead of a second static one. Unset = use the static
|
|
# admin_token directly (bootstrap/degraded; breaks after admin-token rotation).
|
|
user_mgmt_username = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "netbox_secret_backend_role" {
|
|
description = "Map of netbox engine roles; each role's filename-derived name is both the engine role and the NetBox username it mints tokens for, and its permissions block is the user's object-permission set"
|
|
type = map(object({
|
|
name = string
|
|
backend = string
|
|
netbox_username = optional(string)
|
|
netbox_user_id = optional(number)
|
|
write_enabled = optional(bool, false)
|
|
description = optional(string)
|
|
ttl = optional(number)
|
|
max_ttl = optional(number)
|
|
active = optional(bool, true)
|
|
staff = optional(bool, false)
|
|
email = optional(string)
|
|
permissions = optional(list(object({
|
|
name = optional(string)
|
|
object_types = list(string)
|
|
actions = optional(list(string), ["view", "add", "change", "delete"])
|
|
constraints = optional(string)
|
|
description = optional(string)
|
|
enabled = optional(bool, true)
|
|
})), [])
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "netbox_backend_aliases" {
|
|
description = "Map of netbox backend names to sanitized provider aliases"
|
|
type = map(string)
|
|
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)))
|
|
default = {}
|
|
}
|
|
|
|
variable "policy_rules_map" {
|
|
description = "Map of policy names to their rules"
|
|
type = map(list(object({
|
|
path = string
|
|
capabilities = list(string)
|
|
})))
|
|
default = {}
|
|
}
|
|
|