188 lines
5.2 KiB
Go
188 lines
5.2 KiB
Go
// 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)
|
|
_ logical.Externaler = (*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
|
|
}
|