Files
Ben Vincent cfbc06669e
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Initial vault-plugin-secrets-netbox
Vault/OpenBao secrets engine that mints NetBox API tokens via
/api/users/tokens/. A single seeded admin token (config) mints short-lived,
per-user tokens (roles -> creds) whose NetBox expiry is aligned to the Vault
lease; revoke deletes the token, renew extends its expiry. config/rotate
reissues the seeded admin token. Handles NetBox 4.6 v2 tokens (Bearer
nbt_<key>.<secret>) and legacy v1. Unit tests against an httptest NetBox mock;
dual Vault/OpenBao RPMs via nfpm; tag-driven release to artifactapi.

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
2026-08-08 22:14:59 +10:00

94 lines
2.8 KiB
Go

// Package netbox implements a Vault / OpenBao secrets engine that mints NetBox
// API tokens through NetBox's REST API (/api/users/tokens/).
//
// The engine is seeded with a single long-lived NetBox admin token (config).
// Unlike some upstreams, NetBox lets an admin token create tokens for *other*
// users, so one seeded credential is enough: a role names a pre-existing NetBox
// service user and the token options (ttl/max_ttl, write_enabled), and each read
// of creds/<role> mints a fresh, lease-bound token for that user whose NetBox
// `expires` is aligned to the Vault lease. Revoking the lease deletes the token
// from NetBox; renewing it PATCHes `expires` forward.
//
// The seeded admin token can be reissued via config/rotate (NetBox has no
// in-place rotate; the engine mints a replacement for the admin user and deletes
// the old one).
package netbox
import (
"context"
"errors"
"strings"
"sync"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
// errBackendNotConfigured is returned when a credential is requested before the
// NetBox connection has been configured.
var errBackendNotConfigured = errors.New("netbox backend not configured; write config first")
type netboxBackend struct {
*framework.Backend
lock sync.RWMutex
}
// Factory returns a configured NetBox secrets backend.
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
func backend() *netboxBackend {
b := &netboxBackend{}
b.Backend = &framework.Backend{
Help: strings.TrimSpace(backendHelp),
BackendType: logical.TypeLogical,
PathsSpecial: &logical.Paths{
SealWrapStorage: []string{configStoragePath},
},
Paths: framework.PathAppend(
[]*framework.Path{
pathConfig(b),
pathConfigRotate(b),
pathRole(b),
pathRolesList(b),
pathCredentials(b),
},
),
Secrets: []*framework.Secret{
b.netboxTokenSecret(),
},
}
return b
}
// client builds a NetBox client from the stored config, authenticated with the
// seeded admin token.
func (b *netboxBackend) client(ctx context.Context, s logical.Storage) (*netboxClient, error) {
config, err := getConfig(ctx, s)
if err != nil {
return nil, err
}
if config == nil {
return nil, errBackendNotConfigured
}
return newClient(config)
}
const backendHelp = `
The netbox secrets engine mints NetBox API tokens via /api/users/tokens/.
Seed a single NetBox admin token in config; roles name a pre-existing NetBox
service user plus token options (ttl/max_ttl, write_enabled). Reading
creds/<role> mints a short-lived token for that user with its NetBox expiry
aligned to the Vault lease, deleted from NetBox on revoke. config/rotate
reissues the seeded admin token.
`