| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package plugin |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "sync" |
| |
| log "github.com/hashicorp/go-hclog" |
| "github.com/hashicorp/go-plugin" |
| "github.com/hashicorp/vault/sdk/helper/pluginutil" |
| "github.com/hashicorp/vault/sdk/logical" |
| "github.com/hashicorp/vault/sdk/plugin/pb" |
| "google.golang.org/grpc" |
| ) |
| |
| var ErrServerInMetadataMode = errors.New("plugin server can not perform action while in metadata mode") |
| |
| // 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 |
| } |
| |
| type backendGRPCPluginServer struct { |
| pb.UnimplementedBackendServer |
| logical.UnimplementedPluginVersionServer |
| |
| broker *plugin.GRPCBroker |
| |
| instances map[string]backendInstance |
| instancesLock sync.RWMutex |
| multiplexingSupport bool |
| |
| factory logical.Factory |
| |
| logger log.Logger |
| } |
| |
| // 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) |
| } |
| |
| // 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) { |
| var err error |
| id := singleImplementationID |
| |
| if b.multiplexingSupport { |
| id, err = pluginutil.GetMultiplexIDFromContext(ctx) |
| if err != nil { |
| return &pb.SetupReply{}, err |
| } |
| } |
| |
| // Dial for storage |
| brokeredClient, err := b.broker.Dial(args.BrokerID) |
| if err != nil { |
| return &pb.SetupReply{}, err |
| } |
| |
| storage := newGRPCStorageClient(brokeredClient) |
| sysView := newGRPCSystemView(brokeredClient) |
| events := newGRPCEventsClient(brokeredClient) |
| |
| config := &logical.BackendConfig{ |
| StorageView: storage, |
| Logger: b.logger, |
| System: sysView, |
| Config: args.Config, |
| BackendUUID: args.BackendUUID, |
| EventsSender: events, |
| } |
| |
| // Call the underlying backend factory after shims have been created |
| // to set b.backend |
| backend, err := b.factory(ctx, config) |
| if err != nil { |
| return &pb.SetupReply{ |
| Err: pb.ErrToString(err), |
| }, nil |
| } |
| |
| b.instancesLock.Lock() |
| defer b.instancesLock.Unlock() |
| b.instances[id] = backendInstance{ |
| brokeredClient: brokeredClient, |
| backend: backend, |
| } |
| |
| return &pb.SetupReply{}, nil |
| } |
| |
| func (b *backendGRPCPluginServer) HandleRequest(ctx context.Context, args *pb.HandleRequestArgs) (*pb.HandleRequestReply, error) { |
| backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.HandleRequestReply{}, err |
| } |
| |
| if pluginutil.InMetadataMode() { |
| return &pb.HandleRequestReply{}, ErrServerInMetadataMode |
| } |
| |
| logicalReq, err := pb.ProtoRequestToLogicalRequest(args.Request) |
| if err != nil { |
| return &pb.HandleRequestReply{}, err |
| } |
| |
| logicalReq.Storage = newGRPCStorageClient(brokeredClient) |
| |
| resp, respErr := backend.HandleRequest(ctx, logicalReq) |
| |
| pbResp, err := pb.LogicalResponseToProtoResponse(resp) |
| if err != nil { |
| return &pb.HandleRequestReply{}, err |
| } |
| |
| return &pb.HandleRequestReply{ |
| Response: pbResp, |
| Err: pb.ErrToProtoErr(respErr), |
| }, nil |
| } |
| |
| func (b *backendGRPCPluginServer) Initialize(ctx context.Context, _ *pb.InitializeArgs) (*pb.InitializeReply, error) { |
| backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.InitializeReply{}, err |
| } |
| |
| if pluginutil.InMetadataMode() { |
| return &pb.InitializeReply{}, ErrServerInMetadataMode |
| } |
| |
| req := &logical.InitializationRequest{ |
| Storage: newGRPCStorageClient(brokeredClient), |
| } |
| |
| respErr := backend.Initialize(ctx, req) |
| |
| return &pb.InitializeReply{ |
| Err: pb.ErrToProtoErr(respErr), |
| }, nil |
| } |
| |
| func (b *backendGRPCPluginServer) SpecialPaths(ctx context.Context, args *pb.Empty) (*pb.SpecialPathsReply, error) { |
| backend, _, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.SpecialPathsReply{}, err |
| } |
| |
| paths := backend.SpecialPaths() |
| if paths == nil { |
| return &pb.SpecialPathsReply{ |
| Paths: nil, |
| }, nil |
| } |
| |
| return &pb.SpecialPathsReply{ |
| Paths: &pb.Paths{ |
| Root: paths.Root, |
| Unauthenticated: paths.Unauthenticated, |
| LocalStorage: paths.LocalStorage, |
| SealWrapStorage: paths.SealWrapStorage, |
| WriteForwardedStorage: paths.WriteForwardedStorage, |
| }, |
| }, nil |
| } |
| |
| func (b *backendGRPCPluginServer) HandleExistenceCheck(ctx context.Context, args *pb.HandleExistenceCheckArgs) (*pb.HandleExistenceCheckReply, error) { |
| backend, brokeredClient, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.HandleExistenceCheckReply{}, err |
| } |
| |
| if pluginutil.InMetadataMode() { |
| return &pb.HandleExistenceCheckReply{}, ErrServerInMetadataMode |
| } |
| |
| logicalReq, err := pb.ProtoRequestToLogicalRequest(args.Request) |
| if err != nil { |
| return &pb.HandleExistenceCheckReply{}, err |
| } |
| |
| logicalReq.Storage = newGRPCStorageClient(brokeredClient) |
| |
| checkFound, exists, err := backend.HandleExistenceCheck(ctx, logicalReq) |
| return &pb.HandleExistenceCheckReply{ |
| CheckFound: checkFound, |
| Exists: exists, |
| Err: pb.ErrToProtoErr(err), |
| }, nil |
| } |
| |
| func (b *backendGRPCPluginServer) Cleanup(ctx context.Context, _ *pb.Empty) (*pb.Empty, error) { |
| b.instancesLock.Lock() |
| defer b.instancesLock.Unlock() |
| |
| backend, brokeredClient, err := b.getBackendAndBrokeredClientInternal(ctx) |
| if err != nil { |
| return &pb.Empty{}, err |
| } |
| |
| backend.Cleanup(ctx) |
| |
| // Close rpc clients |
| 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) |
| } |
| |
| return &pb.Empty{}, nil |
| } |
| |
| func (b *backendGRPCPluginServer) InvalidateKey(ctx context.Context, args *pb.InvalidateKeyArgs) (*pb.Empty, error) { |
| backend, _, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.Empty{}, err |
| } |
| |
| if pluginutil.InMetadataMode() { |
| return &pb.Empty{}, ErrServerInMetadataMode |
| } |
| |
| backend.InvalidateKey(ctx, args.Key) |
| return &pb.Empty{}, nil |
| } |
| |
| func (b *backendGRPCPluginServer) Type(ctx context.Context, _ *pb.Empty) (*pb.TypeReply, error) { |
| backend, _, err := b.getBackendAndBrokeredClient(ctx) |
| if err != nil { |
| return &pb.TypeReply{}, err |
| } |
| |
| return &pb.TypeReply{ |
| Type: uint32(backend.Type()), |
| }, nil |
| } |
| |
| 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 |
| } |