github.com/hashicorp/vault/sdk@v0.13.0/plugin/plugin.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 "fmt" 10 11 log "github.com/hashicorp/go-hclog" 12 "github.com/hashicorp/go-plugin" 13 "github.com/hashicorp/vault/sdk/helper/consts" 14 "github.com/hashicorp/vault/sdk/helper/pluginutil" 15 "github.com/hashicorp/vault/sdk/logical" 16 ) 17 18 // BackendPluginClient is a wrapper around backendPluginClient 19 // that also contains its plugin.Client instance. It's primarily 20 // used to cleanly kill the client on Cleanup() 21 type BackendPluginClient struct { 22 client *plugin.Client 23 24 logical.Backend 25 } 26 27 // Cleanup calls the RPC client's Cleanup() func and also calls 28 // the go-plugin's client Kill() func 29 func (b *BackendPluginClient) Cleanup(ctx context.Context) { 30 b.Backend.Cleanup(ctx) 31 b.client.Kill() 32 } 33 34 // NewBackendWithVersion will return an instance of an RPC-based client implementation of the backend for 35 // external plugins, or a concrete implementation of the backend if it is a builtin backend. 36 // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether 37 // the plugin should run in metadata mode. 38 func NewBackendWithVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool, version string) (logical.Backend, error) { 39 // Look for plugin in the plugin catalog 40 pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, pluginType, version) 41 if err != nil { 42 return nil, err 43 } 44 45 var backend logical.Backend 46 if pluginRunner.Builtin { 47 // Plugin is builtin so we can retrieve an instance of the interface 48 // from the pluginRunner. Then cast it to logical.Factory. 49 rawFactory, err := pluginRunner.BuiltinFactory() 50 if err != nil { 51 return nil, fmt.Errorf("error getting plugin type: %q", err) 52 } 53 54 if factory, ok := rawFactory.(logical.Factory); !ok { 55 return nil, fmt.Errorf("unsupported backend type: %q", pluginName) 56 } else { 57 if backend, err = factory(ctx, conf); err != nil { 58 return nil, err 59 } 60 } 61 } else { 62 // create a backendPluginClient instance 63 backend, err = NewPluginClient(ctx, sys, pluginRunner, conf.Logger, isMetadataMode) 64 if err != nil { 65 return nil, err 66 } 67 } 68 69 return backend, nil 70 } 71 72 // NewBackend will return an instance of an RPC-based client implementation of the backend for 73 // external plugins, or a concrete implementation of the backend if it is a builtin backend. 74 // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether 75 // the plugin should run in metadata mode. 76 func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool) (logical.Backend, error) { 77 return NewBackendWithVersion(ctx, pluginName, pluginType, sys, conf, isMetadataMode, "") 78 } 79 80 func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) { 81 // pluginMap is the map of plugins we can dispense. 82 pluginSet := map[int]plugin.PluginSet{ 83 // Version 3 used to supports both protocols. We want to keep it around 84 // since it's possible old plugins built against this version will still 85 // work with gRPC. There is currently no difference between version 3 86 // and version 4. 87 3: { 88 "backend": &GRPCBackendPlugin{ 89 MetadataMode: isMetadataMode, 90 }, 91 }, 92 4: { 93 "backend": &GRPCBackendPlugin{ 94 MetadataMode: isMetadataMode, 95 }, 96 }, 97 } 98 99 namedLogger := logger.Named(pluginRunner.Name) 100 101 var client *plugin.Client 102 var err error 103 if isMetadataMode { 104 client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginSet, HandshakeConfig, []string{}, namedLogger) 105 } else { 106 client, err = pluginRunner.Run(ctx, sys, pluginSet, HandshakeConfig, []string{}, namedLogger) 107 } 108 if err != nil { 109 return nil, err 110 } 111 112 // Connect via RPC 113 rpcClient, err := client.Client() 114 if err != nil { 115 return nil, err 116 } 117 118 // Request the plugin 119 raw, err := rpcClient.Dispense("backend") 120 if err != nil { 121 return nil, err 122 } 123 124 var backend logical.Backend 125 var transport string 126 // We should have a logical backend type now. This feels like a normal interface 127 // implementation but is in fact over an RPC connection. 128 switch b := raw.(type) { 129 case *backendGRPCPluginClient: 130 backend = b 131 transport = "gRPC" 132 default: 133 return nil, errors.New("unsupported plugin client type") 134 } 135 136 // Wrap the backend in a tracing middleware 137 if namedLogger.IsTrace() { 138 backend = &BackendTracingMiddleware{ 139 logger: namedLogger.With("transport", transport), 140 next: backend, 141 } 142 } 143 144 return &BackendPluginClient{ 145 client: client, 146 Backend: backend, 147 }, nil 148 } 149 150 func (b *BackendPluginClient) PluginVersion() logical.PluginVersion { 151 if versioner, ok := b.Backend.(logical.PluginVersioner); ok { 152 return versioner.PluginVersion() 153 } 154 return logical.EmptyPluginVersion 155 } 156 157 var _ logical.PluginVersioner = (*BackendPluginClient)(nil)