// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package plugin import ( "context" "errors" "fmt" "github.com/hashicorp/go-plugin" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/pluginutil" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/plugin/pb" ) // BackendPluginClientV5 is a wrapper around backendPluginClient // that also contains its plugin.Client instance. It's primarily // used to cleanly kill the client on Cleanup() type BackendPluginClientV5 struct { client pluginutil.PluginClient logical.Backend } type ContextKey string func (c ContextKey) String() string { return "plugin" + string(c) } const ContextKeyPluginReload = ContextKey("plugin-reload") // Cleanup cleans up the go-plugin client and the plugin catalog func (b *BackendPluginClientV5) Cleanup(ctx context.Context) { _, ok := ctx.Value(ContextKeyPluginReload).(string) if !ok { b.Backend.Cleanup(ctx) b.client.Close() return } b.Backend.Cleanup(ctx) b.client.Reload() } func (b *BackendPluginClientV5) IsExternal() bool { return true } func (b *BackendPluginClientV5) PluginVersion() logical.PluginVersion { if versioner, ok := b.Backend.(logical.PluginVersioner); ok { return versioner.PluginVersion() } return logical.EmptyPluginVersion } var _ logical.PluginVersioner = (*BackendPluginClientV5)(nil) // NewBackendV5 will return an instance of an RPC-based client implementation of // the backend for external plugins, or a concrete implementation of the // backend if it is a builtin backend. The backend is returned as a // logical.Backend interface. func NewBackendV5(ctx context.Context, pluginName string, pluginType consts.PluginType, pluginVersion string, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig) (logical.Backend, error) { // Look for plugin in the plugin catalog pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, pluginType, pluginVersion) if err != nil { return nil, err } var backend logical.Backend if pluginRunner.Builtin { // Plugin is builtin so we can retrieve an instance of the interface // from the pluginRunner. Then cast it to logical.Factory. rawFactory, err := pluginRunner.BuiltinFactory() if err != nil { return nil, fmt.Errorf("error getting plugin type: %q", err) } if factory, ok := rawFactory.(logical.Factory); !ok { return nil, fmt.Errorf("unsupported backend type: %q", pluginName) } else { if backend, err = factory(ctx, conf); err != nil { return nil, err } } } else { // create a backendPluginClient instance config := pluginutil.PluginClientConfig{ Name: pluginName, PluginSets: PluginSet, PluginType: pluginType, Version: pluginVersion, HandshakeConfig: HandshakeConfig, Logger: conf.Logger.Named(pluginName), AutoMTLS: true, Wrapper: sys, } backend, err = NewPluginClientV5(ctx, sys, config) if err != nil { return nil, err } } return backend, nil } // PluginSet is the map of plugins we can dispense. var PluginSet = map[int]plugin.PluginSet{ 5: { "backend": &GRPCBackendPlugin{}, }, } func Dispense(rpcClient plugin.ClientProtocol, pluginClient pluginutil.PluginClient) (logical.Backend, error) { // Request the plugin raw, err := rpcClient.Dispense("backend") if err != nil { return nil, err } var backend logical.Backend // We should have a logical backend type now. This feels like a normal interface // implementation but is in fact over an RPC connection. switch c := raw.(type) { case *backendGRPCPluginClient: // This is an abstraction leak from go-plugin but it is necessary in // order to enable multiplexing on multiplexed plugins c.client = pb.NewBackendClient(pluginClient.Conn()) c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn()) backend = c default: return nil, errors.New("unsupported plugin client type") } return &BackendPluginClientV5{ client: pluginClient, Backend: backend, }, nil } func NewPluginClientV5(ctx context.Context, sys pluginutil.RunnerUtil, config pluginutil.PluginClientConfig) (logical.Backend, error) { pluginClient, err := sys.NewPluginClient(ctx, config) if err != nil { return nil, err } // Request the plugin raw, err := pluginClient.Dispense("backend") if err != nil { return nil, err } var backend logical.Backend var transport string // We should have a logical backend type now. This feels like a normal interface // implementation but is in fact over an RPC connection. switch c := raw.(type) { case *backendGRPCPluginClient: // This is an abstraction leak from go-plugin but it is necessary in // order to enable multiplexing on multiplexed plugins c.client = pb.NewBackendClient(pluginClient.Conn()) c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn()) backend = c transport = "gRPC" default: return nil, errors.New("unsupported plugin client type") } // Wrap the backend in a tracing middleware if config.Logger.IsTrace() { backend = &BackendTracingMiddleware{ logger: config.Logger.With("transport", transport), next: backend, } } return &BackendPluginClientV5{ client: pluginClient, Backend: backend, }, nil }