mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 11:07:00 +02:00
* Add test to demonstrate a split-brain active node when using Consul * Add Consul session check to prevent split-brain updates * It's not right Co-authored-by: Josh Black <raskchanky@gmail.com> --------- Co-authored-by: Josh Black <raskchanky@gmail.com>
201 lines
7.0 KiB
Go
201 lines
7.0 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package physical
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
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
|
|
type PermitPool struct {
|
|
sem chan int
|
|
}
|
|
|
|
// NewPermitPool returns a new permit pool with the provided
|
|
// number of permits
|
|
func NewPermitPool(permits int) *PermitPool {
|
|
if permits < 1 {
|
|
permits = DefaultParallelOperations
|
|
}
|
|
return &PermitPool{
|
|
sem: make(chan int, permits),
|
|
}
|
|
}
|
|
|
|
// Acquire returns when a permit has been acquired
|
|
func (c *PermitPool) Acquire() {
|
|
c.sem <- 1
|
|
}
|
|
|
|
// Release returns a permit to the pool
|
|
func (c *PermitPool) Release() {
|
|
<-c.sem
|
|
}
|
|
|
|
// Get number of requests in the permit pool
|
|
func (c *PermitPool) CurrentPermits() int {
|
|
return len(c.sem)
|
|
}
|
|
|
|
// 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
|
|
}
|