From 743ad5ac3343b675ade9068b49f68738babfb453 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Tue, 11 Aug 2026 21:56:45 +1000 Subject: [PATCH] Validate token_version matches the admin token kind; document live-mount repair A live re-test showed the plugin's own NetBox calls (username resolution, cred minting) still 403 after the KV admin_token was re-seeded bare, because the mount uses ignore_changes=[token] and kept the old scheme-prefixed value. The plugin builds its admin Authorization header from the token value's nbt_ prefix, not from token_version, so a stale "Bearer nbt_..." value double-mangles into a malformed header. - Extend the netbox_secret_backend postcondition to also assert token_version matches the admin token kind (nbt_ v2 <-> token_version 2; bare v1 <-> 1), so a version/token mismatch fails at plan time with a clear message. - Document that token_version does not affect the admin header (only minted token version), and give the exact command to push a corrected token into a running mount (vault write netbox/config token=), warning against -replace which would drop the mount's roles. --- config/netbox_secret_backend/netbox.yaml | 11 ++++++++++- .../modules/netbox_secret_backend/main.tf | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/config/netbox_secret_backend/netbox.yaml b/config/netbox_secret_backend/netbox.yaml index cc877e4..993b9c3 100644 --- a/config/netbox_secret_backend/netbox.yaml +++ b/config/netbox_secret_backend/netbox.yaml @@ -22,7 +22,16 @@ # # token_version 2 is the NetBox 4.6.5 default and requires API_TOKEN_PEPPERS to # be configured on the NetBox server; set token_version: 1 here if the server -# has no peppers. +# has no peppers. token_version does NOT change how the plugin authenticates its +# own calls (that scheme comes from the admin_token value's nbt_ prefix); it only +# sets the version of the per-user tokens the engine mints. It must still MATCH +# the admin_token kind: nbt_ v2 token -> token_version 2; bare v1 token -> 1. +# +# The mount uses ignore_changes=[token], so editing KV alone does NOT reach the +# live mount. To push a corrected/rotated admin token into a running mount: +# vault write netbox/config token= +# (netbox_url/token_version are preserved on a partial update). Do NOT -replace +# the mount to force a re-read - that recreates it and drops all roles/config. description: "NetBox ephemeral scoped API token engine" netbox_url: "https://netbox.k8s.syd1.au.unkin.net" token_version: 2 diff --git a/modules/vault_cluster/modules/netbox_secret_backend/main.tf b/modules/vault_cluster/modules/netbox_secret_backend/main.tf index 48dbfb5..585d6d9 100644 --- a/modules/vault_cluster/modules/netbox_secret_backend/main.tf +++ b/modules/vault_cluster/modules/netbox_secret_backend/main.tf @@ -10,15 +10,23 @@ data "vault_kv_secret_v2" "config" { name = "service/vault/${var.country}/${var.region}/secret_backend/${var.path}/config" lifecycle { - # The plugin adds the Authorization scheme itself (Bearer for v2 nbt_ tokens, - # Token for v1), so admin_token must be the BARE token. A literal `Bearer `/ - # `Token ` prefix in the seed yields a malformed header and 403s on mint/rotate. + # The plugin builds its own Authorization header from the token VALUE, not + # token_version: a value starting with the nbt_ prefix is sent as + # "Bearer " (v2), otherwise "Token " (v1). So admin_token must + # be the BARE token - a literal `Bearer `/`Token ` scheme prefix yields a + # malformed three-part header and 403s on the plugin's own NetBox calls. + # + # token_version does NOT change that header; it only selects the version of + # the per-user tokens the engine mints for roles. It must still MATCH the + # admin token's kind so the mount and its minted creds line up: an nbt_ v2 + # admin token pairs with token_version=2, a bare v1 token with token_version=1. postcondition { condition = nonsensitive( !startswith(self.data["admin_token"], "Bearer ") && - !startswith(self.data["admin_token"], "Token ") + !startswith(self.data["admin_token"], "Token ") && + startswith(self.data["admin_token"], "nbt_") == (var.token_version == 2) ) - error_message = "KV admin_token for netbox backend '${var.path}' must be a BARE NetBox token with no 'Bearer '/'Token ' scheme prefix (v2: nbt_.; v1: the 40-char value)." + error_message = "KV admin_token for netbox backend '${var.path}' must be a BARE NetBox token with no 'Bearer '/'Token ' scheme prefix, AND its version must match token_version: a v2 token (nbt_.) requires token_version=2; a v1 token (bare 40-char value) requires token_version=1." } } }