github.com/hashicorp/vault/sdk@v0.13.0/plugin/grpc_backend_client.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"math"
    10  	"sync/atomic"
    11  
    12  	log "github.com/hashicorp/go-hclog"
    13  	"github.com/hashicorp/go-plugin"
    14  	"github.com/hashicorp/vault/sdk/helper/pluginutil"
    15  	"github.com/hashicorp/vault/sdk/logical"
    16  	"github.com/hashicorp/vault/sdk/plugin/pb"
    17  	"google.golang.org/grpc"
    18  	"google.golang.org/grpc/codes"
    19  	"google.golang.org/grpc/status"
    20  )
    21  
    22  var (
    23  	ErrPluginShutdown       = errors.New("plugin is shut down")
    24  	ErrClientInMetadataMode = errors.New("plugin client can not perform action while in metadata mode")
    25  )
    26  
    27  // Validate backendGRPCPluginClient satisfies the logical.Backend interface
    28  var _ logical.Backend = (*backendGRPCPluginClient)(nil)
    29  
    30  // backendPluginClient implements logical.Backend and is the
    31  // go-plugin client.
    32  type backendGRPCPluginClient struct {
    33  	broker        *plugin.GRPCBroker
    34  	client        pb.BackendClient
    35  	versionClient logical.PluginVersionClient
    36  	metadataMode  bool
    37  
    38  	system logical.SystemView
    39  	logger log.Logger
    40  
    41  	// This is used to signal to the Cleanup function that it can proceed
    42  	// because we have a defined server
    43  	cleanupCh chan struct{}
    44  
    45  	// server is the grpc server used for serving storage and sysview requests.
    46  	server *atomic.Value
    47  
    48  	doneCtx context.Context
    49  }
    50  
    51  func (b *backendGRPCPluginClient) Initialize(ctx context.Context, _ *logical.InitializationRequest) error {
    52  	if b.metadataMode {
    53  		return nil
    54  	}
    55  
    56  	ctx, cancel := context.WithCancel(ctx)
    57  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
    58  	defer close(quitCh)
    59  	defer cancel()
    60  
    61  	reply, err := b.client.Initialize(ctx, &pb.InitializeArgs{}, largeMsgGRPCCallOpts...)
    62  	if err != nil {
    63  		if b.doneCtx.Err() != nil {
    64  			return ErrPluginShutdown
    65  		}
    66  
    67  		// If the plugin doesn't have Initialize implemented we should not fail
    68  		// the initialize call; otherwise this could halt startup of vault.
    69  		grpcStatus, ok := status.FromError(err)
    70  		if ok && grpcStatus.Code() == codes.Unimplemented {
    71  			return nil
    72  		}
    73  
    74  		return err
    75  	}
    76  	if reply.Err != nil {
    77  		return pb.ProtoErrToErr(reply.Err)
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  func (b *backendGRPCPluginClient) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
    84  	if b.metadataMode {
    85  		return nil, ErrClientInMetadataMode
    86  	}
    87  
    88  	ctx, cancel := context.WithCancel(ctx)
    89  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
    90  	defer close(quitCh)
    91  	defer cancel()
    92  
    93  	protoReq, err := pb.LogicalRequestToProtoRequest(req)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	reply, err := b.client.HandleRequest(ctx, &pb.HandleRequestArgs{
    99  		Request: protoReq,
   100  	}, largeMsgGRPCCallOpts...)
   101  	if err != nil {
   102  		if b.doneCtx.Err() != nil {
   103  			return nil, ErrPluginShutdown
   104  		}
   105  
   106  		return nil, err
   107  	}
   108  	resp, err := pb.ProtoResponseToLogicalResponse(reply.Response)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	if reply.Err != nil {
   113  		return resp, pb.ProtoErrToErr(reply.Err)
   114  	}
   115  
   116  	return resp, nil
   117  }
   118  
   119  func (b *backendGRPCPluginClient) SpecialPaths() *logical.Paths {
   120  	reply, err := b.client.SpecialPaths(b.doneCtx, &pb.Empty{})
   121  	if err != nil {
   122  		return nil
   123  	}
   124  
   125  	if reply.Paths == nil {
   126  		return nil
   127  	}
   128  
   129  	return &logical.Paths{
   130  		Root:                  reply.Paths.Root,
   131  		Unauthenticated:       reply.Paths.Unauthenticated,
   132  		LocalStorage:          reply.Paths.LocalStorage,
   133  		SealWrapStorage:       reply.Paths.SealWrapStorage,
   134  		WriteForwardedStorage: reply.Paths.WriteForwardedStorage,
   135  	}
   136  }
   137  
   138  // System returns vault's system view. The backend client stores the view during
   139  // Setup, so there is no need to shim the system just to get it back.
   140  func (b *backendGRPCPluginClient) System() logical.SystemView {
   141  	return b.system
   142  }
   143  
   144  // Logger returns vault's logger. The backend client stores the logger during
   145  // Setup, so there is no need to shim the logger just to get it back.
   146  func (b *backendGRPCPluginClient) Logger() log.Logger {
   147  	return b.logger
   148  }
   149  
   150  func (b *backendGRPCPluginClient) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) {
   151  	if b.metadataMode {
   152  		return false, false, ErrClientInMetadataMode
   153  	}
   154  
   155  	protoReq, err := pb.LogicalRequestToProtoRequest(req)
   156  	if err != nil {
   157  		return false, false, err
   158  	}
   159  
   160  	ctx, cancel := context.WithCancel(ctx)
   161  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
   162  	defer close(quitCh)
   163  	defer cancel()
   164  	reply, err := b.client.HandleExistenceCheck(ctx, &pb.HandleExistenceCheckArgs{
   165  		Request: protoReq,
   166  	}, largeMsgGRPCCallOpts...)
   167  	if err != nil {
   168  		if b.doneCtx.Err() != nil {
   169  			return false, false, ErrPluginShutdown
   170  		}
   171  		return false, false, err
   172  	}
   173  	if reply.Err != nil {
   174  		return false, false, pb.ProtoErrToErr(reply.Err)
   175  	}
   176  
   177  	return reply.CheckFound, reply.Exists, nil
   178  }
   179  
   180  func (b *backendGRPCPluginClient) Cleanup(ctx context.Context) {
   181  	ctx, cancel := context.WithCancel(ctx)
   182  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
   183  	defer close(quitCh)
   184  	defer cancel()
   185  
   186  	// Only wait on graceful cleanup if we can establish communication with the
   187  	// plugin, otherwise b.cleanupCh may never get closed.
   188  	if _, err := b.client.Cleanup(ctx, &pb.Empty{}); status.Code(err) != codes.Unavailable {
   189  		// This will block until Setup has run the function to create a new server
   190  		// in b.server. If we stop here before it has a chance to actually start
   191  		// listening, when it starts listening it will immediately error out and
   192  		// exit, which is fine. Overall this ensures that we do not miss stopping
   193  		// the server if it ends up being created after Cleanup is called.
   194  		select {
   195  		case <-b.cleanupCh:
   196  		}
   197  	}
   198  	server := b.server.Load()
   199  	if grpcServer, ok := server.(*grpc.Server); ok && grpcServer != nil {
   200  		grpcServer.GracefulStop()
   201  	}
   202  }
   203  
   204  func (b *backendGRPCPluginClient) InvalidateKey(ctx context.Context, key string) {
   205  	if b.metadataMode {
   206  		return
   207  	}
   208  
   209  	ctx, cancel := context.WithCancel(ctx)
   210  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
   211  	defer close(quitCh)
   212  	defer cancel()
   213  
   214  	b.client.InvalidateKey(ctx, &pb.InvalidateKeyArgs{
   215  		Key: key,
   216  	})
   217  }
   218  
   219  func (b *backendGRPCPluginClient) Setup(ctx context.Context, config *logical.BackendConfig) error {
   220  	// Shim logical.Storage
   221  	storageImpl := config.StorageView
   222  	if b.metadataMode {
   223  		storageImpl = &NOOPStorage{}
   224  	}
   225  	storage := &GRPCStorageServer{
   226  		impl: storageImpl,
   227  	}
   228  
   229  	// Shim logical.SystemView
   230  	sysViewImpl := config.System
   231  	if b.metadataMode {
   232  		sysViewImpl = &logical.StaticSystemView{}
   233  	}
   234  	sysView := &gRPCSystemViewServer{
   235  		impl: sysViewImpl,
   236  	}
   237  
   238  	events := &GRPCEventsServer{
   239  		impl: config.EventsSender,
   240  	}
   241  
   242  	// Register the server in this closure.
   243  	serverFunc := func(opts []grpc.ServerOption) *grpc.Server {
   244  		opts = append(opts, grpc.MaxRecvMsgSize(math.MaxInt32))
   245  		opts = append(opts, grpc.MaxSendMsgSize(math.MaxInt32))
   246  
   247  		s := grpc.NewServer(opts...)
   248  		pb.RegisterSystemViewServer(s, sysView)
   249  		pb.RegisterStorageServer(s, storage)
   250  		pb.RegisterEventsServer(s, events)
   251  		b.server.Store(s)
   252  		close(b.cleanupCh)
   253  		return s
   254  	}
   255  	brokerID := b.broker.NextId()
   256  	go b.broker.AcceptAndServe(brokerID, serverFunc)
   257  
   258  	args := &pb.SetupArgs{
   259  		BrokerID:    brokerID,
   260  		Config:      config.Config,
   261  		BackendUUID: config.BackendUUID,
   262  	}
   263  
   264  	ctx, cancel := context.WithCancel(ctx)
   265  	quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
   266  	defer close(quitCh)
   267  	defer cancel()
   268  
   269  	reply, err := b.client.Setup(ctx, args)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	if reply.Err != "" {
   274  		return errors.New(reply.Err)
   275  	}
   276  
   277  	// Set system and logger for getter methods
   278  	b.system = config.System
   279  	b.logger = config.Logger
   280  
   281  	return nil
   282  }
   283  
   284  func (b *backendGRPCPluginClient) Type() logical.BackendType {
   285  	reply, err := b.client.Type(b.doneCtx, &pb.Empty{})
   286  	if err != nil {
   287  		return logical.TypeUnknown
   288  	}
   289  
   290  	return logical.BackendType(reply.Type)
   291  }
   292  
   293  func (b *backendGRPCPluginClient) PluginVersion() logical.PluginVersion {
   294  	reply, err := b.versionClient.Version(b.doneCtx, &logical.Empty{})
   295  	if err != nil {
   296  		if stErr, ok := status.FromError(err); ok {
   297  			if stErr.Code() == codes.Unimplemented {
   298  				return logical.EmptyPluginVersion
   299  			}
   300  		}
   301  		b.Logger().Warn("Unknown error getting plugin version", "err", err)
   302  		return logical.EmptyPluginVersion
   303  	}
   304  	return logical.PluginVersion{
   305  		Version: reply.GetPluginVersion(),
   306  	}
   307  }
   308  
   309  func (b *backendGRPCPluginClient) IsExternal() bool {
   310  	return true
   311  }