blob: cc2d1383775d4e701f300174a080675fc09674a8 [file] [log] [blame]
// 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
}