Scaffold the bind-tsig secrets engine
A Vault/OpenBao secrets engine that manages BIND TSIG keys via the bind-operator companion API (Vault -> HTTP API -> BindTSIGKey CRs). - backend + cmd entry point (plugin.ServeMultiplex), modelled on vault-plugin-secrets-litellm - config path: companion API url/token/tls + defaults - static-roles/static-creds: stable named key with managed rotation - roles/creds: dynamic, lease-bound keys (revoke deletes the CR) - tsig_key secret type with revoke/renew - HTTP client for the companion API contract (/v1/keys CRUD + rotate) - Makefile, Woodpecker CI (pre-commit/build/test + tag release RPMs), nfpm packaging (vault + openbao flavours) - e2e: mock companion API + Vault + OpenBao in docker-compose, full lifecycle per engine; unit tests for the dynamic + static flows
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
// Package bindtsig implements a Vault / OpenBao secrets engine that manages
|
||||
// BIND TSIG keys through the bind-operator's companion API. Static roles expose
|
||||
// a named key with managed rotation; dynamic roles mint unique, lease-bound
|
||||
// keys. The companion API owns the BindTSIGKey CRs; the operator reconciles
|
||||
// them into the actual key material.
|
||||
package bindtsig
|
||||
|
||||
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
|
||||
// companion API connection has been configured.
|
||||
var errBackendNotConfigured = errors.New("bind-tsig backend not configured; write config first")
|
||||
|
||||
type bindTSIGBackend struct {
|
||||
*framework.Backend
|
||||
|
||||
lock sync.RWMutex
|
||||
client *apiClient
|
||||
}
|
||||
|
||||
// Factory returns a configured bind-tsig 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() *bindTSIGBackend {
|
||||
b := &bindTSIGBackend{}
|
||||
|
||||
b.Backend = &framework.Backend{
|
||||
Help: strings.TrimSpace(backendHelp),
|
||||
BackendType: logical.TypeLogical,
|
||||
PathsSpecial: &logical.Paths{
|
||||
SealWrapStorage: []string{configStoragePath},
|
||||
},
|
||||
Paths: framework.PathAppend(
|
||||
[]*framework.Path{
|
||||
pathConfig(b),
|
||||
pathRole(b),
|
||||
pathRolesList(b),
|
||||
pathCredentials(b),
|
||||
pathStaticRole(b),
|
||||
pathStaticRolesList(b),
|
||||
pathStaticCredentials(b),
|
||||
},
|
||||
),
|
||||
Secrets: []*framework.Secret{
|
||||
b.tsigKeySecret(),
|
||||
},
|
||||
Invalidate: b.invalidate,
|
||||
WALRollback: nil,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// reset drops the cached API client so it is rebuilt from storage on the next
|
||||
// request. Called when the config changes.
|
||||
func (b *bindTSIGBackend) reset() {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.client = nil
|
||||
}
|
||||
|
||||
// invalidate clears the cached client when the config is written from another
|
||||
// cluster node.
|
||||
func (b *bindTSIGBackend) invalidate(_ context.Context, key string) {
|
||||
if key == configStoragePath {
|
||||
b.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// getClient returns a cached companion-API client, building one from stored
|
||||
// config if necessary.
|
||||
func (b *bindTSIGBackend) getClient(ctx context.Context, s logical.Storage) (*apiClient, error) {
|
||||
b.lock.RLock()
|
||||
if b.client != nil {
|
||||
defer b.lock.RUnlock()
|
||||
return b.client, nil
|
||||
}
|
||||
b.lock.RUnlock()
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if b.client != nil {
|
||||
return b.client, nil
|
||||
}
|
||||
|
||||
config, err := getConfig(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, errBackendNotConfigured
|
||||
}
|
||||
|
||||
client, err := newClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.client = client
|
||||
return b.client, nil
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The bind-tsig secrets engine manages BIND TSIG keys via the bind-operator
|
||||
companion API.
|
||||
|
||||
Static roles expose a stable, named key whose material Vault rotates on a
|
||||
schedule (the key name never changes, so zone allow-update clauses stay valid).
|
||||
Dynamic roles mint a unique key per request, bound to a Vault lease and deleted
|
||||
on revocation.
|
||||
`
|
||||
Reference in New Issue
Block a user