2018-01-18 21:49:20 +00:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-02-12 17:31:03 +00:00
|
|
|
"errors"
|
2022-08-30 02:42:26 +00:00
|
|
|
"fmt"
|
|
|
|
"sync"
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2018-04-03 00:46:59 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
"github.com/hashicorp/go-plugin"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
"github.com/hashicorp/vault/sdk/plugin/pb"
|
2018-01-18 21:49:20 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
)
|
|
|
|
|
2019-02-12 17:31:03 +00:00
|
|
|
var ErrServerInMetadataMode = errors.New("plugin server can not perform action while in metadata mode")
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
// singleImplementationID is the string used to define the instance ID of a
|
|
|
|
// non-multiplexed plugin
|
|
|
|
const singleImplementationID string = "single"
|
|
|
|
|
|
|
|
type backendInstance struct {
|
|
|
|
brokeredClient *grpc.ClientConn
|
|
|
|
backend logical.Backend
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
type backendGRPCPluginServer struct {
|
2021-09-30 01:25:15 +00:00
|
|
|
pb.UnimplementedBackendServer
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
logical.UnimplementedPluginVersionServer
|
2021-09-30 01:25:15 +00:00
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
broker *plugin.GRPCBroker
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
instances map[string]backendInstance
|
|
|
|
instancesLock sync.RWMutex
|
|
|
|
multiplexingSupport bool
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
factory logical.Factory
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
logger log.Logger
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
// getBackendAndBrokeredClientInternal returns the backend and client
|
|
|
|
// connection but does not hold a lock
|
|
|
|
func (b *backendGRPCPluginServer) getBackendAndBrokeredClientInternal(ctx context.Context) (logical.Backend, *grpc.ClientConn, error) {
|
|
|
|
if b.multiplexingSupport {
|
|
|
|
id, err := pluginutil.GetMultiplexIDFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if inst, ok := b.instances[id]; ok {
|
|
|
|
return inst.backend, inst.brokeredClient, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if singleImpl, ok := b.instances[singleImplementationID]; ok {
|
|
|
|
return singleImpl.backend, singleImpl.brokeredClient, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil, fmt.Errorf("no backend instance found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// getBackendAndBrokeredClient holds a read lock and returns the backend and
|
|
|
|
// client connection
|
|
|
|
func (b *backendGRPCPluginServer) getBackendAndBrokeredClient(ctx context.Context) (logical.Backend, *grpc.ClientConn, error) {
|
|
|
|
b.instancesLock.RLock()
|
|
|
|
defer b.instancesLock.RUnlock()
|
|
|
|
return b.getBackendAndBrokeredClientInternal(ctx)
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
// Setup dials into the plugin's broker to get a shimmed storage, logger, and
|
|
|
|
// system view of the backend. This method also instantiates the underlying
|
|
|
|
// backend through its factory func for the server side of the plugin.
|
|
|
|
func (b *backendGRPCPluginServer) Setup(ctx context.Context, args *pb.SetupArgs) (*pb.SetupReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
var err error
|
|
|
|
id := singleImplementationID
|
|
|
|
|
|
|
|
if b.multiplexingSupport {
|
|
|
|
id, err = pluginutil.GetMultiplexIDFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.SetupReply{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
// Dial for storage
|
|
|
|
brokeredClient, err := b.broker.Dial(args.BrokerID)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.SetupReply{}, err
|
|
|
|
}
|
2022-08-30 02:42:26 +00:00
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
storage := newGRPCStorageClient(brokeredClient)
|
|
|
|
sysView := newGRPCSystemView(brokeredClient)
|
|
|
|
|
|
|
|
config := &logical.BackendConfig{
|
|
|
|
StorageView: storage,
|
|
|
|
Logger: b.logger,
|
|
|
|
System: sysView,
|
|
|
|
Config: args.Config,
|
2018-03-21 19:04:27 +00:00
|
|
|
BackendUUID: args.BackendUUID,
|
2018-01-18 21:49:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Call the underlying backend factory after shims have been created
|
|
|
|
// to set b.backend
|
2018-01-19 06:44:44 +00:00
|
|
|
backend, err := b.factory(ctx, config)
|
2018-01-18 21:49:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return &pb.SetupReply{
|
|
|
|
Err: pb.ErrToString(err),
|
|
|
|
}, nil
|
|
|
|
}
|
2022-09-21 18:04:20 +00:00
|
|
|
|
|
|
|
b.instancesLock.Lock()
|
|
|
|
defer b.instancesLock.Unlock()
|
2022-08-30 02:42:26 +00:00
|
|
|
b.instances[id] = backendInstance{
|
|
|
|
brokeredClient: brokeredClient,
|
|
|
|
backend: backend,
|
|
|
|
}
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
return &pb.SetupReply{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) HandleRequest(ctx context.Context, args *pb.HandleRequestArgs) (*pb.HandleRequestReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.HandleRequestReply{}, err
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:36:14 +00:00
|
|
|
if pluginutil.InMetadataMode() {
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.HandleRequestReply{}, ErrServerInMetadataMode
|
|
|
|
}
|
|
|
|
|
|
|
|
logicalReq, err := pb.ProtoRequestToLogicalRequest(args.Request)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.HandleRequestReply{}, err
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
logicalReq.Storage = newGRPCStorageClient(brokeredClient)
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
resp, respErr := backend.HandleRequest(ctx, logicalReq)
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
pbResp, err := pb.LogicalResponseToProtoResponse(resp)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.HandleRequestReply{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.HandleRequestReply{
|
|
|
|
Response: pbResp,
|
|
|
|
Err: pb.ErrToProtoErr(respErr),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-07-05 23:55:40 +00:00
|
|
|
func (b *backendGRPCPluginServer) Initialize(ctx context.Context, _ *pb.InitializeArgs) (*pb.InitializeReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.InitializeReply{}, err
|
|
|
|
}
|
|
|
|
|
2019-07-05 23:55:40 +00:00
|
|
|
if pluginutil.InMetadataMode() {
|
|
|
|
return &pb.InitializeReply{}, ErrServerInMetadataMode
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &logical.InitializationRequest{
|
2022-08-30 02:42:26 +00:00
|
|
|
Storage: newGRPCStorageClient(brokeredClient),
|
2019-07-05 23:55:40 +00:00
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
respErr := backend.Initialize(ctx, req)
|
2019-07-05 23:55:40 +00:00
|
|
|
|
|
|
|
return &pb.InitializeReply{
|
|
|
|
Err: pb.ErrToProtoErr(respErr),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
func (b *backendGRPCPluginServer) SpecialPaths(ctx context.Context, args *pb.Empty) (*pb.SpecialPathsReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, _, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.SpecialPathsReply{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
paths := backend.SpecialPaths()
|
2018-02-01 22:30:17 +00:00
|
|
|
if paths == nil {
|
|
|
|
return &pb.SpecialPathsReply{
|
|
|
|
Paths: nil,
|
|
|
|
}, nil
|
|
|
|
}
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
return &pb.SpecialPathsReply{
|
|
|
|
Paths: &pb.Paths{
|
|
|
|
Root: paths.Root,
|
|
|
|
Unauthenticated: paths.Unauthenticated,
|
|
|
|
LocalStorage: paths.LocalStorage,
|
|
|
|
SealWrapStorage: paths.SealWrapStorage,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) HandleExistenceCheck(ctx context.Context, args *pb.HandleExistenceCheckArgs) (*pb.HandleExistenceCheckReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.HandleExistenceCheckReply{}, err
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:36:14 +00:00
|
|
|
if pluginutil.InMetadataMode() {
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.HandleExistenceCheckReply{}, ErrServerInMetadataMode
|
|
|
|
}
|
|
|
|
|
|
|
|
logicalReq, err := pb.ProtoRequestToLogicalRequest(args.Request)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.HandleExistenceCheckReply{}, err
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
logicalReq.Storage = newGRPCStorageClient(brokeredClient)
|
|
|
|
|
|
|
|
checkFound, exists, err := backend.HandleExistenceCheck(ctx, logicalReq)
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.HandleExistenceCheckReply{
|
|
|
|
CheckFound: checkFound,
|
|
|
|
Exists: exists,
|
|
|
|
Err: pb.ErrToProtoErr(err),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) Cleanup(ctx context.Context, _ *pb.Empty) (*pb.Empty, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
b.instancesLock.Lock()
|
|
|
|
defer b.instancesLock.Unlock()
|
|
|
|
|
|
|
|
backend, brokeredClient, err := b.getBackendAndBrokeredClientInternal(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.Empty{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
backend.Cleanup(ctx)
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
// Close rpc clients
|
2022-08-30 02:42:26 +00:00
|
|
|
brokeredClient.Close()
|
|
|
|
|
|
|
|
if b.multiplexingSupport {
|
|
|
|
id, err := pluginutil.GetMultiplexIDFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
delete(b.instances, id)
|
|
|
|
} else if _, ok := b.instances[singleImplementationID]; ok {
|
|
|
|
delete(b.instances, singleImplementationID)
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.Empty{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) InvalidateKey(ctx context.Context, args *pb.InvalidateKeyArgs) (*pb.Empty, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, _, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.Empty{}, err
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:36:14 +00:00
|
|
|
if pluginutil.InMetadataMode() {
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.Empty{}, ErrServerInMetadataMode
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
backend.InvalidateKey(ctx, args.Key)
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.Empty{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) Type(ctx context.Context, _ *pb.Empty) (*pb.TypeReply, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
backend, _, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &pb.TypeReply{}, err
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
return &pb.TypeReply{
|
2022-08-30 02:42:26 +00:00
|
|
|
Type: uint32(backend.Type()),
|
2018-01-18 21:49:20 +00:00
|
|
|
}, nil
|
|
|
|
}
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
|
|
|
|
func (b *backendGRPCPluginServer) Version(ctx context.Context, _ *logical.Empty) (*logical.VersionReply, error) {
|
|
|
|
backend, _, err := b.getBackendAndBrokeredClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &logical.VersionReply{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if versioner, ok := backend.(logical.PluginVersioner); ok {
|
|
|
|
return &logical.VersionReply{
|
|
|
|
PluginVersion: versioner.PluginVersion().Version,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return &logical.VersionReply{
|
|
|
|
PluginVersion: "",
|
|
|
|
}, nil
|
|
|
|
}
|