177 lines
4.9 KiB
Go
177 lines
4.9 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/rsa"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"sync"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
"github.com/hashicorp/vault/helper/pluginutil"
|
|
"github.com/hashicorp/vault/logical"
|
|
)
|
|
|
|
// init registers basic structs with gob which will be used to transport complex
|
|
// types through the plugin server and client.
|
|
func init() {
|
|
// Common basic structs
|
|
gob.Register([]interface{}{})
|
|
gob.Register(map[string]interface{}{})
|
|
gob.Register(map[string]string{})
|
|
gob.Register(map[string]int{})
|
|
|
|
// Register these types since we have to serialize and de-serialize
|
|
// tls.ConnectionState over the wire as part of logical.Request.Connection.
|
|
gob.Register(rsa.PublicKey{})
|
|
gob.Register(ecdsa.PublicKey{})
|
|
gob.Register(time.Duration(0))
|
|
|
|
// Custom common error types for requests. If you add something here, you must
|
|
// also add it to the switch statement in `wrapError`!
|
|
gob.Register(&plugin.BasicError{})
|
|
gob.Register(logical.CodedError(0, ""))
|
|
gob.Register(&logical.StatusBadRequest{})
|
|
}
|
|
|
|
// BackendPluginClient is a wrapper around backendPluginClient
|
|
// that also contains its plugin.Client instance. It's primarily
|
|
// used to cleanly kill the client on Cleanup()
|
|
type BackendPluginClient struct {
|
|
client *plugin.Client
|
|
sync.Mutex
|
|
|
|
logical.Backend
|
|
}
|
|
|
|
// Cleanup calls the RPC client's Cleanup() func and also calls
|
|
// the go-plugin's client Kill() func
|
|
func (b *BackendPluginClient) Cleanup(ctx context.Context) {
|
|
b.Backend.Cleanup(ctx)
|
|
b.client.Kill()
|
|
}
|
|
|
|
// NewBackend 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. The isMetadataMode param determines whether
|
|
// the plugin should run in metadata mode.
|
|
func NewBackend(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
|
|
// Look for plugin in the plugin catalog
|
|
pluginRunner, err := sys.LookupPlugin(ctx, pluginName)
|
|
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.Backend.
|
|
backendRaw, err := pluginRunner.BuiltinFactory()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("error getting plugin type: {{err}}", err)
|
|
}
|
|
|
|
var ok bool
|
|
backend, ok = backendRaw.(logical.Backend)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unsupported backend type: %q", pluginName)
|
|
}
|
|
|
|
} else {
|
|
// create a backendPluginClient instance
|
|
backend, err = newPluginClient(ctx, sys, pluginRunner, logger, isMetadataMode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return backend, nil
|
|
}
|
|
|
|
func newPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
|
|
// pluginMap is the map of plugins we can dispense.
|
|
pluginMap := map[string]plugin.Plugin{
|
|
"backend": &BackendPlugin{
|
|
metadataMode: isMetadataMode,
|
|
},
|
|
}
|
|
|
|
namedLogger := logger.Named(pluginRunner.Name)
|
|
|
|
var client *plugin.Client
|
|
var err error
|
|
if isMetadataMode {
|
|
client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginMap, handshakeConfig, []string{}, namedLogger)
|
|
} else {
|
|
client, err = pluginRunner.Run(ctx, sys, pluginMap, handshakeConfig, []string{}, namedLogger)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Connect via RPC
|
|
rpcClient, err := client.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Request the plugin
|
|
raw, err := rpcClient.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 raw.(type) {
|
|
case *backendPluginClient:
|
|
backend = raw.(*backendPluginClient)
|
|
transport = "netRPC"
|
|
case *backendGRPCPluginClient:
|
|
backend = raw.(*backendGRPCPluginClient)
|
|
transport = "gRPC"
|
|
default:
|
|
return nil, errors.New("unsupported plugin client type")
|
|
}
|
|
|
|
// Wrap the backend in a tracing middleware
|
|
if namedLogger.IsTrace() {
|
|
backend = &backendTracingMiddleware{
|
|
logger: namedLogger.With("transport", transport),
|
|
next: backend,
|
|
}
|
|
}
|
|
|
|
return &BackendPluginClient{
|
|
client: client,
|
|
Backend: backend,
|
|
}, nil
|
|
}
|
|
|
|
// wrapError takes a generic error type and makes it usable with the plugin
|
|
// interface. Only errors which have exported fields and have been registered
|
|
// with gob can be unwrapped and transported. This checks error types and, if
|
|
// none match, wrap the error in a plugin.BasicError.
|
|
func wrapError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
switch err.(type) {
|
|
case *plugin.BasicError,
|
|
logical.HTTPCodedError,
|
|
*logical.StatusBadRequest:
|
|
return err
|
|
}
|
|
|
|
return plugin.NewBasicError(err)
|
|
}
|