Mint the netbox user-management credential dynamically from the single admin token #119

Merged
benvin merged 3 commits from benvin/netbox-user-mgmt-token-decouple into master 2026-08-12 00:03:13 +10:00

3 Commits

Author SHA1 Message Date
unkinben 483fd21acb Mint the user-management credential dynamically from the single admin token
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Replace the second static user_mgmt_token with a Vault-minted, user-admin-capable
token so only ONE static NetBox admin credential exists. A dedicated engine role
(netbox/roles/vault-user-mgmt) mints a short-lived token for a pre-existing NetBox
superuser (user_mgmt_username); netbox_user_management authenticates the
e-breuninger provider with that minted token to reconcile the service users. This
also resolves rotation-divergence structurally: the credential is always derived
from the current static admin token, so rotating it never strands user management.

Constraints this design works within (documented in the module):
- The hashicorp/vault provider ships ephemeral resources for KV only, not dynamic
  engine creds, so the mint is read via the vault_generic_secret data source; the
  short-lived token transits state (sensitive, lease-revoked) and is re-minted each
  plan. Migrate to an ephemeral resource once the provider ships one.
- A provider cannot be configured from a role created in the same fresh apply, so a
  brand-new backend needs a one-time targeted bootstrap of the mount + role.

- Add user_mgmt_username to the netbox backend config; when set, mint dynamically,
  else fall back to the single static admin_token (a check block warns that
  rotation would then break user management).
- Add module.netbox_user_mgmt_role (vault-user-mgmt, write-enabled, short TTL).
- Grant the deployer read on netbox/creds/vault-user-mgmt (the one deliberate
  exception to the admin policy's creds exclusion).
- Keep the bare-token + token_version postconditions on the single admin token.
2026-08-11 22:19:25 +10:00
unkinben 743ad5ac33 Validate token_version matches the admin token kind; document live-mount repair
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
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=<BARE>), warning against
  -replace which would drop the mount's roles.
2026-08-11 21:56:45 +10:00
unkinben 28d0a6ed79 Decouple netbox user management from the rotating engine seed
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
The netbox secrets engine seeds its admin_token from KV once and then rotates
it in place (netbox/config/rotate), which mints a fresh admin token and deletes
the old one. netbox_user_management re-reads that same KV admin_token on every
apply to drive the e-breuninger provider, so after the first rotation it
authenticates with a token NetBox has already deleted and the apply fails.

A separate failure mode compounds this: a KV admin token carrying a literal
"Bearer "/"Token " scheme prefix produces a malformed three-part Authorization
header and a 403, because NetBox derives the token version from the value's
nbt_ prefix (not the keyword) and expects a bare token.

- Read a dedicated, never-rotated user_mgmt_token for user management, falling
  back to admin_token only as a bootstrap convenience until it is seeded.
- Add a check block that warns when only the rotating seed is present, nudging
  operators to seed user_mgmt_token before enabling engine rotation.
- Add data-source postconditions on both modules rejecting a scheme-prefixed
  admin credential with a clear message instead of a downstream 403.
- Document the two KV keys and the bare-token contract in the backend config.
2026-08-11 21:52:21 +10:00