// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package plugin import ( "context" "net/rpc" "sync" "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/plugin" bplugin "github.com/hashicorp/vault/sdk/plugin" ) // Backend returns an instance of the backend, either as a plugin if external // or as a concrete implementation if builtin, casted as logical.Backend. func Backend(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { var b backend name := conf.Config["plugin_name"] pluginType, err := consts.ParsePluginType(conf.Config["plugin_type"]) if err != nil { return nil, err } pluginVersion := conf.Config["plugin_version"] sys := conf.System raw, err := plugin.NewBackendV5(ctx, name, pluginType, pluginVersion, sys, conf) if err != nil { return nil, err } b.Backend = raw b.config = conf return &b, nil } // backend is a thin wrapper around a builtin plugin or a plugin.BackendPluginClientV5 type backend struct { logical.Backend mu sync.RWMutex config *logical.BackendConfig // Used to detect if we already reloaded canary string } func (b *backend) reloadBackend(ctx context.Context, storage logical.Storage) error { pluginName := b.config.Config["plugin_name"] pluginType, err := consts.ParsePluginType(b.config.Config["plugin_type"]) if err != nil { return err } pluginVersion := b.config.Config["plugin_version"] b.Logger().Debug("plugin: reloading plugin backend", "plugin", pluginName) // Ensure proper cleanup of the backend // Pass a context value so that the plugin client will call the appropriate // cleanup method for reloading reloadCtx := context.WithValue(ctx, plugin.ContextKeyPluginReload, "reload") b.Backend.Cleanup(reloadCtx) nb, err := plugin.NewBackendV5(ctx, pluginName, pluginType, pluginVersion, b.config.System, b.config) if err != nil { return err } err = nb.Setup(ctx, b.config) if err != nil { return err } b.Backend = nb // Re-initialize the backend in case plugin was reloaded // after it crashed err = b.Backend.Initialize(ctx, &logical.InitializationRequest{ Storage: storage, }) if err != nil { return err } return nil } // HandleRequest is a thin wrapper implementation of HandleRequest that includes automatic plugin reload. func (b *backend) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) { b.mu.RLock() canary := b.canary resp, err := b.Backend.HandleRequest(ctx, req) b.mu.RUnlock() // Need to compare string value for case were err comes from plugin RPC // and is returned as plugin.BasicError type. if err != nil && (err.Error() == rpc.ErrShutdown.Error() || err == bplugin.ErrPluginShutdown) { // Reload plugin if it's an rpc.ErrShutdown b.mu.Lock() if b.canary == canary { err := b.reloadBackend(ctx, req.Storage) if err != nil { b.mu.Unlock() return nil, err } b.canary, err = uuid.GenerateUUID() if err != nil { b.mu.Unlock() return nil, err } } b.mu.Unlock() // Try request once more b.mu.RLock() defer b.mu.RUnlock() return b.Backend.HandleRequest(ctx, req) } return resp, err } // HandleExistenceCheck is a thin wrapper implementation of HandleRequest that includes automatic plugin reload. func (b *backend) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) { b.mu.RLock() canary := b.canary checkFound, exists, err := b.Backend.HandleExistenceCheck(ctx, req) b.mu.RUnlock() if err != nil && (err.Error() == rpc.ErrShutdown.Error() || err == bplugin.ErrPluginShutdown) { // Reload plugin if it's an rpc.ErrShutdown b.mu.Lock() if b.canary == canary { err := b.reloadBackend(ctx, req.Storage) if err != nil { b.mu.Unlock() return false, false, err } b.canary, err = uuid.GenerateUUID() if err != nil { b.mu.Unlock() return false, false, err } } b.mu.Unlock() // Try request once more b.mu.RLock() defer b.mu.RUnlock() return b.Backend.HandleExistenceCheck(ctx, req) } return checkFound, exists, err } // InvalidateKey is a thin wrapper used to ensure we grab the lock for race purposes func (b *backend) InvalidateKey(ctx context.Context, key string) { b.mu.RLock() defer b.mu.RUnlock() b.Backend.InvalidateKey(ctx, key) } func (b *backend) IsExternal() bool { switch b.Backend.(type) { case *plugin.BackendPluginClientV5: return true } return false }