vault/sdk/physical/physical.go
Johan Brandhorst-Satzkorn 8d83c5d047
physical: use permitpool from go-secure-stdlib (#29331)
* sdk/physical: use permitpool from go-secure-stdlib

* physical: use permitpool from go-secure-stdlib

* fixup! sdk/physical: use permitpool from go-secure-stdlib

* fixup! sdk/physical: use permitpool from go-secure-stdlib
2025-01-24 12:33:44 -05:00

221 lines
8.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package physical
import (
"context"
"strings"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/permitpool"
)
const DefaultParallelOperations = 128
// The operation type
type Operation string
const (
DeleteOperation Operation = "delete"
GetOperation = "get"
ListOperation = "list"
PutOperation = "put"
)
const (
ErrValueTooLarge = "put failed due to value being too large"
ErrKeyTooLarge = "put failed due to key being too large"
)
// Backend is the interface required for a physical
// backend. A physical backend is used to durably store
// data outside of Vault. As such, it is completely untrusted,
// and is only accessed via a security barrier. The backends
// must represent keys in a hierarchical manner. All methods
// are expected to be thread safe.
type Backend interface {
// Put is used to insert or update an entry
Put(ctx context.Context, entry *Entry) error
// Get is used to fetch an entry
Get(ctx context.Context, key string) (*Entry, error)
// Delete is used to permanently delete an entry
Delete(ctx context.Context, key string) error
// List is used to list all the keys under a given
// prefix, up to the next prefix.
List(ctx context.Context, prefix string) ([]string, error)
}
// HABackend is an extensions to the standard physical
// backend to support high-availability. Vault only expects to
// use mutual exclusion to allow multiple instances to act as a
// hot standby for a leader that services all requests.
type HABackend interface {
// LockWith is used for mutual exclusion based on the given key.
LockWith(key, value string) (Lock, error)
// Whether or not HA functionality is enabled
HAEnabled() bool
}
// RemovableNodeHABackend is used for HA backends that can remove nodes from
// their cluster
type RemovableNodeHABackend interface {
HABackend
// IsNodeRemoved checks if the node with the given ID has been removed.
// This will only be called on the active node.
IsNodeRemoved(ctx context.Context, nodeID string) (bool, error)
// NodeID returns the ID for this node
NodeID() string
// IsRemoved checks if this node has been removed
IsRemoved() bool
// RemoveSelf marks this node as being removed
RemoveSelf() error
}
// FencingHABackend is an HABackend which provides the additional guarantee that
// each Lock it returns from LockWith is also a FencingLock. A FencingLock
// provides a mechanism to retrieve a fencing token that can be included by
// future writes by the backend to ensure that it is still the current lock
// holder at the time the write commits. Without this timing might allow a lock
// holder not to notice it's no longer the active node for long enough for it to
// write data to storage even while a new active node is writing causing
// corruption. For Consul backend the fencing token is the session id which is
// submitted with `check-session` operation on each write to ensure the write
// only completes if the session is still holding the lock. For raft backend
// this isn't needed because our in-process raft library is unable to write if
// it's not the leader anyway.
//
// If you implement this, Vault will call RegisterActiveNodeLock with the Lock
// instance returned by LockWith after it successfully locks it. This keeps the
// backend oblivious to the specific key we use for active node locks and allows
// potential future usage of locks for other purposes in the future.
//
// Note that all implementations must support writing to storage before
// RegisterActiveNodeLock is called to support initialization of a new cluster.
// They must also skip fencing writes if the write's Context contains a special
// value. This is necessary to allow Vault to clear and re-initialise secondary
// clusters even though there is already an active node with a specific lock
// session since we clear the cluster while Vault is sealed and clearing the
// data might remove the lock in some storages (e.g. Consul). As noted above
// it's not generally safe to allow unfenced writes after a lock so instead we
// special case just a few types of writes that only happen rarely while the
// cluster is sealed. See the IsUnfencedWrite helper function.
type FencingHABackend interface {
HABackend
RegisterActiveNodeLock(l Lock) error
}
// unfencedWriteContextKeyType is a special type to identify context values to
// disable fencing. It's a separate type per the best-practice in Context.Value
// docs to avoid collisions even if the key might match.
type unfencedWriteContextKeyType string
const (
// unfencedWriteContextKey is the context key we pass the option to bypass
// fencing through to a FencingHABackend. Note that this is not an ideal use
// of context values and violates the "do not use it for optional arguments"
// guidance but has been agreed as a pragmatic option for this case rather
// than needing to specialize every physical.Backend to understand this
// option.
unfencedWriteContextKey unfencedWriteContextKeyType = "vault-disable-fencing"
)
// UnfencedWriteCtx adds metadata to a ctx such that any writes performed
// directly on a FencingHABackend using that context will _not_ add a fencing
// token.
func UnfencedWriteCtx(ctx context.Context) context.Context {
return context.WithValue(ctx, unfencedWriteContextKey, true)
}
// IsUnfencedWrite returns whether or not the context passed has the unfenced
// flag value set.
func IsUnfencedWrite(ctx context.Context) bool {
isUnfenced, ok := ctx.Value(unfencedWriteContextKey).(bool)
return ok && isUnfenced
}
// ToggleablePurgemonster is an interface for backends that can toggle on or
// off special functionality and/or support purging. This is only used for the
// cache, don't use it for other things.
type ToggleablePurgemonster interface {
Purge(ctx context.Context)
SetEnabled(bool)
}
// RedirectDetect is an optional interface that an HABackend
// can implement. If they do, a redirect address can be automatically
// detected.
type RedirectDetect interface {
// DetectHostAddr is used to detect the host address
DetectHostAddr() (string, error)
}
// MountTableLimitingBackend is an optional interface a Backend can implement
// that allows it to support different entry size limits for mount-table-related
// paths. It will only be called in Vault Enterprise.
type MountTableLimitingBackend interface {
// RegisterMountTablePath informs the Backend that the given path represents
// part of the mount tables or related metadata. This allows the backend to
// apply different limits for this entry if configured to do so.
RegisterMountTablePath(path string)
}
type Lock interface {
// Lock is used to acquire the given lock
// The stopCh is optional and if closed should interrupt the lock
// acquisition attempt. The return struct should be closed when
// leadership is lost.
Lock(stopCh <-chan struct{}) (<-chan struct{}, error)
// Unlock is used to release the lock
Unlock() error
// Returns the value of the lock and if it is held by _any_ node
Value() (bool, string, error)
}
// Factory is the factory function to create a physical backend.
type Factory func(config map[string]string, logger log.Logger) (Backend, error)
// PermitPool is used to limit maximum outstanding requests
// Deprecated: use permitpool.Pool from go-secure-stdlib.
type PermitPool struct {
*permitpool.Pool
}
// NewPermitPool returns a new permit pool with the provided
// number of permits.
// Deprecated: use permitpool.New from go-secure-stdlib.
func NewPermitPool(permits int) *PermitPool {
return &PermitPool{
Pool: permitpool.New(permits),
}
}
// Acquire returns when a permit has been acquired
// Deprecated: use permitpool.Acquire from go-secure-stdlib.
func (c *PermitPool) Acquire() {
_ = c.Pool.Acquire(context.Background())
}
// Prefixes is a shared helper function returns all parent 'folders' for a
// given vault key.
// e.g. for 'foo/bar/baz', it returns ['foo', 'foo/bar']
func Prefixes(s string) []string {
components := strings.Split(s, "/")
result := []string{}
for i := 1; i < len(components); i++ {
result = append(result, strings.Join(components[:i], "/"))
}
return result
}