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 }