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 insdk/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/Initializelifecycle 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
BarrierViewimplementing 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.Storageseparate fromphysical.Backend(which usesphysical.Entrynotlogical.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 byLockWith) 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. TheLocksub-interface (3 methods:Lock,Unlock,Value) is clean.FencingHABackendextends 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.
GenericTransactionHandlerprovides a default implementation usingPseudoTransactional(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 —
Transactionalis separate fromBackend, combined intoTransactionalBackendvia embedding, then extended byTransactionalLimits. The fallbackGenericTransactionHandlerdemonstrates 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.Backendand 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.Storagemeans it can be used directly as aStorageby internal components. The separation ofBarrierStorageandBarrierEncryptoras 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-levelseal.Accessfor 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). TheIsUpToDatemethod enables lazy re-encryption when seal configuration changes. Embedswrapping.InitFinalizerfromgo-kms-wrappingto hook into the HashiCorp wrapping library. - Implementations:
accessstruct wrapping one or morewrapping.Wrapperinstances; ultimately delegates to AWS KMS SDK, GCP SDK, Azure SDK, or Shamir. - Design quality: The multi-seal semantics (
map[string]errorreturn 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.Brokerfans out every request/response audit event to all registeredBackendinstances. If all backends fail, the request is rejected — audit is mandatory by design. ThePipelineReaderembedding integrates with HashiCorp’seventloggerpipeline framework for composable filter/formatter/sink chains.Salterprovides HMAC salting for hashing sensitive values (tokens, secrets) in audit logs. - Implementations:
fileBackend,socketBackend,syslogBackend— all verified withvar _ Backend = (*...)(nil)compile-time assertions. - Design quality: Clean use of interface embedding to compose
Salter+PipelineReader+ audit-specific methods. ThePipelineReaderembedding 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
SystemViewimplementation 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 aCorereference),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(addsWellKnownSystemView+Auditor+ForwardGenericRequest+APILockShouldBlockRequest) via embedding, which is correct: internal Core consumers get the extended view; external plugins get only the base. TheHasFeature(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
Runmethod for lifecycle management is idiomatic Go. TheNotifyConfigurationReloadwith 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-databaseDatabaseimplementations 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/DatabaseServerindatabase_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.HABackend←FencingHABackend,RemovableNodeHABackendphysical.Backend+Transactional→TransactionalBackend→TransactionalLimitslogical.Storage+BarrierEncryptor→ embedded inSecurityBarrieraudit.Salter+event.PipelineReader→ embedded inaudit.Backendlogical.WellKnownSystemView→ embedded inExtendedSystemViewwrapping.InitFinalizer→ embedded inseal.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 inSecurityBarrier.Initialize,SecurityBarrier.GenerateKey,SecurityBarrier.Rotate(for randomness sources, following io.Reader as an entropy provider)io.Writerandio.Closer— inphysical/raft/io.gofor snapshot I/O (Reader,Writer,ReadCloser,WriteCloser)context.Context— pervasive, every interface method that does I/O or could block takes a contextfmt.Stringer— not explicitly tracked but present in some helper types- No
sort.Interfaceusage in public-facing interfaces - No
http.Handlerin 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.