diff --git a/config/git.unkin.net/user/gitea-vault-admin.yaml b/config/git.unkin.net/user/gitea-vault-admin.yaml new file mode 100644 index 0000000..2d3dbaa --- /dev/null +++ b/config/git.unkin.net/user/gitea-vault-admin.yaml @@ -0,0 +1,21 @@ +# Purpose-built Gitea site-admin bot for the vault-plugin-secrets-gitea engine. +# The engine seeds itself from this account's credentials (Basic Auth) to mint +# and delete per-user tokens for any user. Its randomly generated password is +# written ONCE to Vault KV (vault_seed_path below) and never updated after; the +# Vault gitea engine reads that seed only when first creating gitea/config and +# then rotates it (rotate-root) so only Vault holds the live password. +# +# A local (not external-auth) account with 2FA disabled is required so the +# engine can change the password via the admin API during rotate-root. +email: gitea-vault-admin@unkin.net +full_name: "Gitea Vault Admin" +description: "site-admin bot; credentials seeded to Vault for vault-plugin-secrets-gitea" +# Site admin so the engine may mint/delete tokens for any user. No org/repo +# creation; profile visible only to signed-in users. +visibility: limited +admin: true +allow_create_organization: false +max_repo_creation: 0 +# Seed this account's generated password to Vault KV (mount "kv") at this path, +# where the Vault gitea secrets engine reads it at creation time. Written once. +vault_seed_path: "service/vault/au/syd1/secret_backend/gitea/config" diff --git a/environments/root.hcl b/environments/root.hcl index aa28cfd..83b3e5b 100644 --- a/environments/root.hcl +++ b/environments/root.hcl @@ -11,6 +11,11 @@ provider "woodpecker" { server = "https://ci.k8s.syd1.au.unkin.net" } +# Address + token come from VAULT_ADDR / VAULT_TOKEN in the environment (the +# Makefile authenticates via k8s auth before running terragrunt). Used to seed +# bot-account credentials into Vault KV (see modules/user vault_seed_path). +provider "vault" {} + terraform { backend "consul" { address = "https://consul.service.consul" diff --git a/modules/gitea_instance/main.tf b/modules/gitea_instance/main.tf index 8a93840..5247cbc 100644 --- a/modules/gitea_instance/main.tf +++ b/modules/gitea_instance/main.tf @@ -55,6 +55,8 @@ module "user" { allow_create_organization = each.value.allow_create_organization max_repo_creation = each.value.max_repo_creation must_change_password = each.value.must_change_password + vault_seed_path = each.value.vault_seed_path + vault_seed_mount = each.value.vault_seed_mount } module "team" { diff --git a/modules/gitea_instance/modules/user/main.tf b/modules/gitea_instance/modules/user/main.tf index c51e41e..110b4bc 100644 --- a/modules/gitea_instance/modules/user/main.tf +++ b/modules/gitea_instance/modules/user/main.tf @@ -35,3 +35,24 @@ resource "gitea_user" "this" { ignore_changes = [password] } } + +# Optional one-time seed of the account's credentials to Vault KV, for the +# vault-plugin-secrets-gitea engine to consume when it first creates gitea/config. +# random_password never regenerates (no keepers), so the seed is stable; the +# ignore_changes below makes the write strictly create-only, so a later +# rotate-root on the Vault side (which diverges the live password from this seed) +# is never clobbered by a re-apply here. +resource "vault_kv_secret_v2" "seed" { + count = var.vault_seed_path != null ? 1 : 0 + + mount = var.vault_seed_mount + name = var.vault_seed_path + data_json = jsonencode({ + admin_username = var.username + admin_password = random_password.this.result + }) + + lifecycle { + ignore_changes = [data_json] + } +} diff --git a/modules/gitea_instance/modules/user/terraform.tf b/modules/gitea_instance/modules/user/terraform.tf index 0befbc9..d593d08 100644 --- a/modules/gitea_instance/modules/user/terraform.tf +++ b/modules/gitea_instance/modules/user/terraform.tf @@ -9,5 +9,9 @@ terraform { source = "hashicorp/random" version = ">= 3.5" } + vault = { + source = "hashicorp/vault" + version = ">= 4.3" + } } } diff --git a/modules/gitea_instance/modules/user/variables.tf b/modules/gitea_instance/modules/user/variables.tf index 4ccea98..60a1400 100644 --- a/modules/gitea_instance/modules/user/variables.tf +++ b/modules/gitea_instance/modules/user/variables.tf @@ -67,3 +67,21 @@ variable "must_change_password" { type = bool default = false } + +variable "vault_seed_path" { + description = <<-EOT + Optional. When set, the account's generated password is seeded ONCE to Vault + KV at this secret name (under vault_seed_mount) as admin_username + + admin_password, for the vault-plugin-secrets-gitea engine to consume at + creation time. The write is create-only; subsequent changes are ignored so + the seed never churns (and never overwrites a rotated password). + EOT + type = string + default = null +} + +variable "vault_seed_mount" { + description = "KV v2 mount holding the seeded credential (used only when vault_seed_path is set)" + type = string + default = "kv" +} diff --git a/modules/gitea_instance/terraform.tf b/modules/gitea_instance/terraform.tf index 98258fa..b86e08a 100644 --- a/modules/gitea_instance/terraform.tf +++ b/modules/gitea_instance/terraform.tf @@ -13,5 +13,9 @@ terraform { source = "hashicorp/random" version = ">= 3.5" } + vault = { + source = "hashicorp/vault" + version = ">= 4.3" + } } } diff --git a/modules/gitea_instance/variables.tf b/modules/gitea_instance/variables.tf index 5b70da7..7ab9c2f 100644 --- a/modules/gitea_instance/variables.tf +++ b/modules/gitea_instance/variables.tf @@ -79,6 +79,8 @@ variable "user" { allow_create_organization = optional(bool, false) max_repo_creation = optional(number, 0) must_change_password = optional(bool, false) + vault_seed_path = optional(string) + vault_seed_mount = optional(string, "kv") })) default = {} }