Vault — Interfaces#

Interface catalog#

logical.Backend#

  • Package: github.com/hashicorp/vault/sdk/logical
  • File: sdk/logical/logical.go:43
  • Methods:
    Initialize(context.Context, *InitializationRequest) error
    HandleRequest(context.Context, *Request) (*Response, error)
    SpecialPaths() *Paths
    System() SystemView
    Logger() log.Logger
    HandleExistenceCheck(context.Context, *Request) (bool, bool, error)
    Cleanup(context.Context)
    InvalidateKey(context.Context, string)
    Setup(context.Context, *BackendConfig) error
    Type() BackendType
  • Purpose: The universal plugin contract — everything mountable in Vault is a logical.Backend. Auth methods, secret engines, the system backend, cubbyhole, and the identity store all satisfy this interface. Core never imports plugin code directly; it only holds factories (func(ctx, *BackendConfig) (Backend, error)) and interacts exclusively through this interface.
  • Implementations: framework.Backend (the SDK’s helper struct that most plugin authors embed), backendGRPCPluginClient (gRPC proxy for out-of-process plugins), BackendTracingMiddleware (observability middleware in sdk/plugin/), SystemBackend (vault/logical_system.go), all KV / PKI / SSH / database secret engines, all auth method backends.
  • Design quality: Well-segregated for its purpose. 10 methods is on the heavy side for an interface, but every method is load-bearing — removing any would break the abstraction. The Setup/Cleanup/Initialize lifecycle trio is a deliberate protocol, not bloat. Follows ISP: providers of extensibility (plugin authors) see only the methods they must implement; Core consumers see the same narrow contract. The gRPC adapter (backendGRPCPluginClient) satisfying the same interface transparently is strong evidence of good abstraction.

logical.Storage#

  • Package: github.com/hashicorp/vault/sdk/logical
  • File: sdk/logical/storage.go:32
  • Methods:
    List(context.Context, string) ([]string, error)
    Get(context.Context, string) (*StorageEntry, error)
    Put(context.Context, *StorageEntry) error
    Delete(context.Context, string) error
  • Purpose: The storage contract exposed to logical backends (plugins). Every backend gets a BarrierView implementing this interface — a namespaced, encrypted, path-prefixed view of barrier storage. Plugins can only read/write their own namespace; they cannot escape to other backends’ data or Core internals. The simplicity (4 methods, no transactions) is intentional.
  • Implementations: BarrierView (namespaced encrypted view), PhysicalAccess (sdk/physical wrapper for SDK tests), View (physical.View — prefix-scoped wrapper), in-memory implementations for tests.
  • Design quality: Near-perfect ISP compliance. Four methods is exactly the right size — hierarchical key-value operations without exposing transactional semantics to plugins. The decision to keep logical.Storage separate from physical.Backend (which uses physical.Entry not logical.StorageEntry) means plugins are always mediated through the barrier and can never see raw physical bytes.

physical.Backend#

  • Package: github.com/hashicorp/vault/sdk/physical
  • File: sdk/physical/physical.go:37
  • Methods:
    Put(ctx context.Context, entry *Entry) error
    Get(ctx context.Context, key string) (*Entry, error)
    Delete(ctx context.Context, key string) error
    List(ctx context.Context, prefix string) ([]string, error)
  • Purpose: The untrusted raw storage contract. Vault’s security model rests on this interface being “completely untrusted” — the barrier encrypts everything before handing it to a physical backend. Implementations include Raft (default), Consul, DynamoDB, GCS, S3, Spanner, etcd, Azure, and an in-memory backend for tests. None of them need to understand encryption.
  • Implementations: physical/raft/FSM, physical/consul/ConsulBackend, physical/gcs/Backend, physical/spanner/Backend, sdk/physical/inmem/InmemBackend, and a dozen more.
  • Design quality: Intentionally minimal at 4 methods. Extended via optional capability interfaces (HABackend, FencingHABackend, Transactional, TransactionalLimits, MountTableLimitingBackend, RedirectDetect) that backends implement if they support those features. This is exemplary ISP: no backend is forced to implement HA or transactional semantics if it doesn’t support them; Core type-asserts to the optional interfaces as needed.

physical.HABackend#

  • Package: github.com/hashicorp/vault/sdk/physical
  • File: sdk/physical/physical.go:56
  • Methods:
    LockWith(key, value string) (Lock, error)
    HAEnabled() bool
  • Purpose: Optional interface for physical backends that support distributed locking for high-availability. When a backend implements HABackend, Vault can elect a leader and run standby nodes. Lock (returned by LockWith) provides the actual mutex semantics with a blocking acquire and a leadership-loss channel.
  • Implementations: Consul backend, etcd backend, GCS HA backend, Spanner HA backend, Zookeeper backend. Raft implements HA differently (in-process consensus) and does not use HABackend.
  • Design quality: Good separation from Backend. The Lock sub-interface (3 methods: Lock, Unlock, Value) is clean. FencingHABackend extends it properly with one extra method for Consul’s session-fencing use case, rather than polluting the base interface.

physical.Transactional / TransactionalBackend#

  • Package: github.com/hashicorp/vault/sdk/physical
  • File: sdk/physical/transactions.go:27
  • Methods:
    // Transactional:
    Transaction(context.Context, []*TxnEntry) error
    
    // TransactionalBackend embeds Backend + Transactional
    // TransactionalLimits extends TransactionalBackend:
    TransactionLimits() (maxEntries int, maxSize int)
  • Purpose: Optional extension for backends that support atomic multi-key operations. Required for replication internals, which need to batch writes. GenericTransactionHandler provides a default implementation using PseudoTransactional (internal get/put/delete without locking) for backends that cannot do native transactions.
  • Implementations: Consul (native transactions), in-memory, Raft (appends to log).
  • Design quality: Clean layering — Transactional is separate from Backend, combined into TransactionalBackend via embedding, then extended by TransactionalLimits. The fallback GenericTransactionHandler demonstrates graceful degradation for non-native implementations.

vault.SecurityBarrier#

  • Package: github.com/hashicorp/vault/vault
  • File: vault/vault/barrier.go:79
  • Methods (selected):
    Initialized(ctx context.Context) (bool, error)
    Initialize(ctx context.Context, rootKey []byte, sealKey []byte, random io.Reader) error
    Sealed() (bool, error)
    Unseal(ctx context.Context, key []byte) error
    Seal() error
    Rotate(ctx context.Context, reader io.Reader) (uint32, error)
    Rekey(context.Context, []byte) error
    ActiveKeyInfo() (*KeyInfo, error)
    Keyring() (*Keyring, error)
    CheckBarrierAutoRotate(ctx context.Context) (string, error)
    ConsumeEncryptionCount(consumer func(int64) error) error
    // + embeds logical.Storage (List/Get/Put/Delete)
    // + embeds BarrierEncryptor (Encrypt/Decrypt)
    DetectDeadlocks() bool
  • Purpose: The central security abstraction — wraps an untrusted physical.Backend and provides an encrypted, authenticated, lifecycle-managed storage surface. The barrier has explicit sealed/unsealed states; no data is accessible while sealed. Rotation, rekey, upgrade paths, and encryption-count tracking are all part of the interface because they are security-critical operations that must be atomic and auditable.
  • Implementations: AESGCMBarrier (the only non-test implementation — AES-256-GCM encryption with a versioned keyring).
  • Design quality: This is a large interface (~25 methods) by Go standards, but justified: the barrier is a single critical security boundary and everything it does is essential. Embedding logical.Storage means it can be used directly as a Storage by internal components. The separation of BarrierStorage and BarrierEncryptor as sub-interfaces allows the WAL and Merkle index to use the barrier’s encryption primitives independently without owning the full lifecycle.

vault.Seal#

  • Package: github.com/hashicorp/vault/vault
  • File: vault/vault/seal.go:56
  • Methods (selected):
    SetCore(*Core)
    Init(context.Context) error
    Finalize(context.Context) error
    StoredKeysSupported() seal.StoredKeysSupport
    SetStoredKeys(context.Context, [][]byte) error
    GetStoredKeys(context.Context) ([][]byte, error)
    BarrierConfig(context.Context) (*SealConfig, error)
    SetBarrierConfig(context.Context, *SealConfig) error
    RecoveryKeySupported() bool
    RecoveryConfig(context.Context) (*SealConfig, error)
    SetRecoveryKey(context.Context, []byte) error
    VerifyRecoveryKey(context.Context, []byte) error
    GetAccess() seal.Access
    Healthy() bool
    SetInitializationFlag(context.Context) error
    // ... + 5 more config/cache methods
  • Purpose: Abstracts the unseal mechanism — Shamir threshold shares vs. auto-unseal via external KMS (AWS KMS, GCP CKMS, Azure Key Vault, HSM). The interface manages barrier seal configuration, stored key handling (for auto-unseal), recovery key handling (for emergency access when KMS is unavailable), and health checks. GetAccess() returns the lower-level seal.Access for actual encrypt/decrypt operations.
  • Implementations: defaultSeal (Shamir), enterprise auto-seal wrappers (AWS, GCP, Azure, HSM).
  • Design quality: Large interface (~20 methods) reflecting the genuine complexity of seal lifecycle. The SetCore(*Core) method is a bidirectional dependency — the Seal needs a reference back to Core — which is somewhat of a design smell (circular reference). This is mitigated in practice by the Core being the single owner.

seal.Access#

  • Package: github.com/hashicorp/vault/vault/seal
  • File: vault/vault/seal/seal.go:285
  • Methods:
    wrapping.InitFinalizer  // Init(ctx) + Finalize(ctx)
    Generation() uint64
    Encrypt(ctx, plaintext []byte, options ...wrapping.Option) (*MultiWrapValue, map[string]error)
    Decrypt(ctx, ciphertext *MultiWrapValue, options ...wrapping.Option) ([]byte, bool, error)
    IsUpToDate(ctx, value *MultiWrapValue, forceKeyIdRefresh bool) (bool, error)
    GetEnabledWrappers() []wrapping.Wrapper
    SetShamirSealKey([]byte) error
    GetShamirKeyBytes(ctx context.Context) ([]byte, error)
    GetAllSealWrappersByPriority() []*SealWrapper
    GetConfiguredSealWrappersByPriority() []*SealWrapper
    GetEnabledSealWrappersByPriority() []*SealWrapper
    AllSealWrappersHealthy() bool
    GetSealGenerationInfo() *SealGenerationInfo
  • Purpose: Low-level KMS encrypt/decrypt contract. Supports multi-seal (multiple KMS providers simultaneously) and seal migration by returning MultiWrapValue (a map of provider name → ciphertext blob). The IsUpToDate method enables lazy re-encryption when seal configuration changes. Embeds wrapping.InitFinalizer from go-kms-wrapping to hook into the HashiCorp wrapping library.
  • Implementations: access struct wrapping one or more wrapping.Wrapper instances; ultimately delegates to AWS KMS SDK, GCP SDK, Azure SDK, or Shamir.
  • Design quality: The multi-seal semantics (map[string]error return from Encrypt indicating partial failures) is unusual but appropriate for operational resilience — if one of three KMS providers is down, Vault can still unseal. Returns the freshest encrypted blob along with any partial errors rather than failing atomically.

audit.Backend#

  • Package: github.com/hashicorp/vault/audit
  • File: audit/backend.go:44
  • Methods:
    // Embeds Salter:
    Salt(context.Context) (*salt.Salt, error)
    // Embeds event.PipelineReader:
    EventType() eventlogger.EventType
    HasFiltering() bool
    Name() string
    NodeIDs() []eventlogger.NodeID
    Nodes() map[eventlogger.NodeID]eventlogger.Node
    // Own methods:
    IsFallback() bool
    LogTestMessage(context.Context, *logical.LogInput) error
    Reload() error
    Invalidate(context.Context)
  • Purpose: Contract for audit sinks (file, socket, syslog). An audit.Broker fans out every request/response audit event to all registered Backend instances. If all backends fail, the request is rejected — audit is mandatory by design. The PipelineReader embedding integrates with HashiCorp’s eventlogger pipeline framework for composable filter/formatter/sink chains. Salter provides HMAC salting for hashing sensitive values (tokens, secrets) in audit logs.
  • Implementations: fileBackend, socketBackend, syslogBackend — all verified with var _ Backend = (*...)(nil) compile-time assertions.
  • Design quality: Clean use of interface embedding to compose Salter + PipelineReader + audit-specific methods. The PipelineReader embedding is a dependency on an external framework (eventlogger) surfaced through the interface — this couples the audit interface to HashiCorp’s event pipeline design, which is a trade-off between framework integration and portability.

logical.SystemView#

  • Package: github.com/hashicorp/vault/sdk/logical
  • File: sdk/logical/system_view.go:22
  • Methods (selected):
    DefaultLeaseTTL() time.Duration
    MaxLeaseTTL() time.Duration
    Tainted() bool
    CachingDisabled() bool
    LocalMount() bool
    ReplicationState() consts.ReplicationState
    HasFeature(license.Features) bool
    ResponseWrapData(ctx, data map[string]interface{}, ttl time.Duration, jwt bool) (*wrapping.ResponseWrapInfo, error)
    LookupPlugin(ctx, pluginName string, pluginType consts.PluginType) (*pluginutil.PluginRunner, error)
    EntityInfo(entityID string) (*Entity, error)
    GroupsForEntity(entityID string) ([]*Group, error)
    GeneratePasswordFromPolicy(ctx, policyName string) (string, error)
    ClusterID(ctx context.Context) (string, error)
    RegisterRotationJob(ctx, req) (string, error)
    // ... + 10 more methods
  • Purpose: The read-only “window into Core” that plugins are given. Plugins cannot import Core directly; instead they receive a SystemView implementation that exposes only safe, curated system information: TTL policy, feature flags, entity identity, plugin management, rotation jobs. This is the boundary that prevents plugins from accessing arbitrary Core state.
  • Implementations: dynamicSystemView (production, wraps a Core reference), StaticSystemView (test stub with configurable values), extendedSystemView (enterprise extension), backendPluginSystemView (gRPC adapter for external plugins).
  • Design quality: Broad interface (~25 methods) reflecting the genuine surface area that plugins need. Extended with ExtendedSystemView (adds WellKnownSystemView + Auditor + ForwardGenericRequest + APILockShouldBlockRequest) via embedding, which is correct: internal Core consumers get the extended view; external plugins get only the base. The HasFeature(license.Features) method is mildly opinionated — it couples the SDK to the Vault licensing model — but pragmatically necessary for enterprise features.

serviceregistration.ServiceRegistration#

  • Package: github.com/hashicorp/vault/serviceregistration
  • File: serviceregistration/service_registration.go:36
  • Methods:
    Run(shutdownCh <-chan struct{}, wait *sync.WaitGroup, redirectAddr string) error
    NotifyActiveStateChange(isActive bool) error
    NotifySealedStateChange(isSealed bool) error
    NotifyPerformanceStandbyStateChange(isStandby bool) error
    NotifyInitializedStateChange(isInitialized bool) error
    NotifyConfigurationReload(conf *map[string]string) error
  • Purpose: Allows Vault to advertise its health and leadership state to service discovery systems (Consul, Kubernetes). Core calls the Notify* methods whenever relevant state changes; the implementation updates the external registry. The interface is entirely event-driven — Core pushes state changes and does not poll.
  • Implementations: consul.ServiceRegistration, kubernetes.ServiceRegistration.
  • Design quality: Well-sized (6 methods, all clearly purposeful). The channel-based Run method for lifecycle management is idiomatic Go. The NotifyConfigurationReload with a *map[string]string (nil means deregister) is slightly awkward but functional.

sdk/database/dbplugin.Database#

  • Package: github.com/hashicorp/vault/sdk/database/dbplugin
  • File: sdk/database/dbplugin/plugin.go:20
  • Methods:
    Type() (string, error)
    CreateUser(ctx, statements, usernameConfig, expiration time.Time) (username, password string, err error)
    RenewUser(ctx, statements, username string, expiration time.Time) error
    RevokeUser(ctx, statements, username string) error
    RotateRootCredentials(ctx, statements []string) (map[string]interface{}, error)
    GenerateCredentials(ctx) (string, error)
    SetCredentials(ctx, statements, staticConfig) (username, password string, err error)
    // + Init, Close, GenerateCredentials
  • Purpose: The sub-plugin contract for database secret engines. The database secrets engine is itself a logical.Backend, but it delegates to per-database Database implementations for actual credential management. This double indirection allows one set of policies/leases/paths to serve MySQL, PostgreSQL, MongoDB, Cassandra, etc. — all via gRPC for out-of-process isolation.
  • Implementations: MySQL, PostgreSQL, MongoDB, Cassandra, MSSQL, Oracle, ElasticSearch, and many community plugins.
  • Design quality: Well-scoped for its use case. The gRPC boundary (DatabaseClient / DatabaseServer in database_grpc.pb.go) is generated from proto, keeping the interface cleanly serializable.

Interface patterns#

Size distribution#

  • 1–4 methods: logical.Storage (4), physical.Backend (4), physical.HABackend (2), physical.Transactional (1), physical.Lock (3), audit.Salter (1), serviceregistration.ServiceRegistration (6), BarrierEncryptor (2), ClearableView (2).
  • 5–10 methods: logical.Backend (10), audit.Backend (~10 including embedded), physical.FencingHABackend (3), vault.Seal (~20), serviceregistration (6).
  • 15+ methods: logical.SystemView (~25), vault.SecurityBarrier (~25), vault.Seal (~20), seal.Access (~13).

The small-interface philosophy is strong in the SDK (physical.Backend, logical.Storage). Core-internal interfaces (SecurityBarrier, Seal, SystemView) are deliberately large because they represent complete subsystem contracts rather than narrow capabilities.

Embedding#

Vault uses interface embedding extensively to compose capabilities:

  • physical.HABackendFencingHABackend, RemovableNodeHABackend
  • physical.Backend + TransactionalTransactionalBackendTransactionalLimits
  • logical.Storage + BarrierEncryptor → embedded in SecurityBarrier
  • audit.Salter + event.PipelineReader → embedded in audit.Backend
  • logical.WellKnownSystemView → embedded in ExtendedSystemView
  • wrapping.InitFinalizer → embedded in seal.Access

This is idiomatic Go interface composition used consistently for capability extension without inheritance.

Implicit satisfaction#

Vault uses both consumer-defined and provider-defined interfaces:

  • Consumer-defined (preferred for SDK boundaries): logical.Backend, logical.Storage, physical.Backend — Core defines what it needs from plugins; plugins satisfy them without knowing Core.
  • Provider-defined (used internally): SecurityBarrier, Seal — Core defines both the interface and its only implementation; the interface exists for testability and to document the contract.

The compile-time assertion pattern (var _ InterfaceName = (*StructName)(nil)) is used throughout audit and physical backends to catch mismatches early.

Stdlib interfaces used#

  • io.Reader — appears in SecurityBarrier.Initialize, SecurityBarrier.GenerateKey, SecurityBarrier.Rotate (for randomness sources, following io.Reader as an entropy provider)
  • io.Writer and io.Closer — in physical/raft/io.go for snapshot I/O (Reader, Writer, ReadCloser, WriteCloser)
  • context.Context — pervasive, every interface method that does I/O or could block takes a context
  • fmt.Stringer — not explicitly tracked but present in some helper types
  • No sort.Interface usage in public-facing interfaces
  • No http.Handler in core interfaces (HTTP is a separate layer, not embedded in domain interfaces)

Key abstractions#

1. logical.Backend — the plugin boundary#

The single most architecturally significant interface. It is the reason Vault’s plugin ecosystem exists — 150+ built-in and community backends all satisfy exactly this contract. The gRPC proxy transparently satisfies the same interface, making in-process and out-of-process plugins indistinguishable to Core. Without this interface, Vault would be a monolith incapable of extension.

2. physical.Backend — the untrusted storage contract#

The security model’s foundation. By keeping this interface to 4 methods and explicitly documenting it as “completely untrusted,” Vault’s designers force all security properties upward into the barrier layer. Any key-value store can become a Vault backend in ~200 lines of Go; the simplicity is a feature.

3. vault.SecurityBarrier — the encryption boundary#

The barrier is where physical storage meets cryptography. Its interface documents every lifecycle operation needed to maintain the encryption guarantee: init, unseal, seal, rotate, rekey, key upgrade paths, encryption count tracking. The embedding of logical.Storage is elegant — the barrier is simultaneously the encryption gateway and the storage layer for internal Core data.

4. logical.SystemView — the capability injection point#

Plugins receive a SystemView instead of a *Core reference. This is classic “tell, don’t ask” inverted: rather than plugins reaching into Core for capabilities, Core injects a curated interface. The large method count reflects the genuine surface area needed by real plugins (TTLs, identity lookups, password policies, rotation jobs). Enterprise features are addable via ExtendedSystemView without breaking existing plugins.

5. vault.Seal — the KMS abstraction#

Vault’s seal interface hides the entire variety of key management systems (Shamir threshold, AWS KMS, GCP CKMS, Azure Key Vault, HSMs) behind a single contract. This enables operators to change unseal mechanisms without modifying Vault core code and enables multi-seal configurations where multiple KMS providers protect the same root key.


Interface-driven extensibility#

Plugin system (logical.Backend + physical.Backend)#

Both plugin contracts are defined in sdk/ — an independently versioned Go module with minimal dependencies. Third parties implement logical.Backend to create new auth methods or secret engines, and physical.Backend to create new storage backends. The logical.Factory and physical.Factory function types are registered in a map at startup; Core never sees the implementing types.

External plugin RPC (gRPC)#

sdk/plugin/grpc_backend_client.go implements logical.Backend as a gRPC client; sdk/plugin/pb/backend_grpc.pb.go provides the gRPC server interface (BackendServer, StorageServer, SystemViewServer). This enables a plugin to run in a separate process (with its own security boundary, crash isolation, and independent Go version) while Core sees only the familiar logical.Backend interface. The database plugin system uses the same pattern with DatabaseClient/DatabaseServer.

Audit pipeline (audit.Backend + eventlogger)#

New audit sinks can be added by implementing audit.Backend and registering with audit.Factory. The PipelineReader embedding integrates custom sinks into the eventlogger pipeline for composable filtering, formatting, and delivery — enabling filtered audit (Enterprise) without changing the Backend contract.

Service discovery (ServiceRegistration)#

New service registries (beyond Consul and Kubernetes) are added by implementing serviceregistration.ServiceRegistration and registering a Factory. The interface is small and event-driven, making it easy to integrate with any health-check or service-mesh system.

Optional capability extension (type assertions)#

Physical backends communicate optional capabilities (HA, transactions, mount-table limits, redirect detection) by implementing optional interfaces. Core uses type assertions (if ha, ok := backend.(physical.HABackend); ok { ... }) to discover and use these capabilities. This is the preferred Go pattern for optional interfaces and is used consistently throughout Vault’s storage layer.