Pin plugin-backed mounts to their catalog version (#171)
ci/woodpecker/push/apply Pipeline was successful

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/<path>/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: #171
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 #171.
This commit is contained in:
2026-10-23 22:55:04 +11:00
committed by BenVincent
parent 7c7b1df720
commit cc298526e9
6 changed files with 76 additions and 0 deletions
@@ -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"
+37
View File
@@ -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"
@@ -0,0 +1,9 @@
# Pins a plugin-backed mount to its catalog version (sys/mounts/<path>/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
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
}
}
@@ -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
}
@@ -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
}
}