From cc298526e95c6c33f5aad997b6a4ffb6cb2dd5d3 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Fri, 23 Oct 2026 22:55:04 +1100 Subject: [PATCH] Pin plugin-backed mounts to their catalog version (#171) Plugin-backed mounts keep their pinned `plugin_version` when a catalog yaml bumps `version`, so the old catalog entry is deregistered while the mount still points at it and the backend fails to start on the next leader change. - add `mount_plugin_version` module tuning `sys/mounts//tune` to the catalog yaml version for each plugin-backed mount - register new plugin versions before deregistering old ones (`create_before_destroy`) - document the post-bump backend reload in the arrstack plugin yaml - document that a versioned plugin yaml must keep `version` (removing it strands the mount on the old version) Reviewed-on: https://git.unkin.net/unkin/terraform-vault/pulls/171 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- .../vault-plugin-secrets-arrstack.yaml | 6 +++ modules/vault_cluster/main.tf | 37 +++++++++++++++++++ .../modules/mount_plugin_version/main.tf | 9 +++++ .../modules/mount_plugin_version/terraform.tf | 9 +++++ .../modules/mount_plugin_version/variables.tf | 9 +++++ modules/vault_cluster/modules/plugin/main.tf | 6 +++ 6 files changed, 76 insertions(+) create mode 100644 modules/vault_cluster/modules/mount_plugin_version/main.tf create mode 100644 modules/vault_cluster/modules/mount_plugin_version/terraform.tf create mode 100644 modules/vault_cluster/modules/mount_plugin_version/variables.tf diff --git a/config/plugins/vault-plugin-secrets-arrstack.yaml b/config/plugins/vault-plugin-secrets-arrstack.yaml index 2c1426a..54fb0a4 100644 --- a/config/plugins/vault-plugin-secrets-arrstack.yaml +++ b/config/plugins/vault-plugin-secrets-arrstack.yaml @@ -7,6 +7,12 @@ # 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. +# +# Terraform retunes mounts onto a bumped version; after apply, run +# `bao write sys/plugins/reload/backend mounts=arrstack scope=global`. +# +# Once versioned, keep `version` set: dropping it leaves the mount pinned to a +# deregistered version. type: secret command: vault-plugin-secrets-arrstack version: "0.2.0" diff --git a/modules/vault_cluster/main.tf b/modules/vault_cluster/main.tf index be0bfd0..9ebe451 100644 --- a/modules/vault_cluster/main.tf +++ b/modules/vault_cluster/main.tf @@ -396,6 +396,43 @@ module "plugin" { plugin_version = each.value.version } +locals { + plugin_mounts = merge( + { for path, mount in var.arrstack_secret_backend : path => mount.plugin }, + { for path, mount in var.ghp_secret_backend : path => mount.plugin }, + { for path, mount in var.gitea_secret_backend : path => mount.plugin }, + { for path, mount in var.gpg_secret_backend : path => mount.plugin }, + { for path, mount in var.litellm_secret_backend : path => mount.plugin }, + { for path, mount in var.netbox_secret_backend : path => mount.plugin }, + { for path, mount in var.rancher_secret_backend : path => mount.plugin }, + ) +} + +# Pins each plugin-backed mount to its plugin's catalog version. Unversioned +# catalog entries need no pin (OpenBao rejects an empty plugin_version tune). +module "mount_plugin_version" { + source = "./modules/mount_plugin_version" + + for_each = { + for path, plugin in local.plugin_mounts : path => var.plugins[plugin].version + if try(var.plugins[plugin].version, null) != null + } + + path = each.key + plugin_version = each.value + + depends_on = [ + module.plugin, + module.arrstack_secret_backend, + module.ghp_secret_backend, + module.gitea_secret_backend, + module.gpg_secret_backend, + module.litellm_secret_backend, + module.netbox_secret_backend, + module.rancher_secret_backend, + ] +} + module "arrstack_secret_backend" { source = "./modules/arrstack_secret_backend" diff --git a/modules/vault_cluster/modules/mount_plugin_version/main.tf b/modules/vault_cluster/modules/mount_plugin_version/main.tf new file mode 100644 index 0000000..49b2b66 --- /dev/null +++ b/modules/vault_cluster/modules/mount_plugin_version/main.tf @@ -0,0 +1,9 @@ +# Pins a plugin-backed mount to its catalog version (sys/mounts//tune). +# OpenBao normalises plugin_version to a leading "v" and reads it back that way. +# Tune has no delete; destroying this leaves the mount's pin in place. +resource "vault_generic_endpoint" "this" { + path = "sys/mounts/${var.path}/tune" + data_json = jsonencode({ plugin_version = "v${trimprefix(var.plugin_version, "v")}" }) + ignore_absent_fields = true + disable_delete = true +} diff --git a/modules/vault_cluster/modules/mount_plugin_version/terraform.tf b/modules/vault_cluster/modules/mount_plugin_version/terraform.tf new file mode 100644 index 0000000..ce22437 --- /dev/null +++ b/modules/vault_cluster/modules/mount_plugin_version/terraform.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.10" + required_providers { + vault = { + source = "hashicorp/vault" + version = "5.6.0" + } + } +} diff --git a/modules/vault_cluster/modules/mount_plugin_version/variables.tf b/modules/vault_cluster/modules/mount_plugin_version/variables.tf new file mode 100644 index 0000000..91d284a --- /dev/null +++ b/modules/vault_cluster/modules/mount_plugin_version/variables.tf @@ -0,0 +1,9 @@ +variable "path" { + description = "Mount path of the plugin-backed secrets engine" + type = string +} + +variable "plugin_version" { + description = "Catalog version to pin the mount to (with or without a leading v)" + type = string +} diff --git a/modules/vault_cluster/modules/plugin/main.tf b/modules/vault_cluster/modules/plugin/main.tf index 2d27cfb..b85a8aa 100644 --- a/modules/vault_cluster/modules/plugin/main.tf +++ b/modules/vault_cluster/modules/plugin/main.tf @@ -9,4 +9,10 @@ resource "vault_plugin" "this" { command = coalesce(var.command, var.name) sha256 = var.sha256 version = var.plugin_version + + # A version bump registers the new entry, retunes mounts onto it, then + # deregisters the old one, so no mount is left pinned to a missing version. + lifecycle { + create_before_destroy = true + } }