2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
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"
|
2022-08-30 02:42:26 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
"github.com/hashicorp/vault/sdk/plugin/pb"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BackendPluginClientV5 is a wrapper around backendPluginClient
|
|
|
|
// that also contains its plugin.Client instance. It's primarily
|
|
|
|
// used to cleanly kill the client on Cleanup()
|
|
|
|
type BackendPluginClientV5 struct {
|
|
|
|
client pluginutil.PluginClient
|
|
|
|
|
|
|
|
logical.Backend
|
|
|
|
}
|
|
|
|
|
|
|
|
type ContextKey string
|
|
|
|
|
|
|
|
func (c ContextKey) String() string {
|
|
|
|
return "plugin" + string(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
const ContextKeyPluginReload = ContextKey("plugin-reload")
|
|
|
|
|
|
|
|
// Cleanup cleans up the go-plugin client and the plugin catalog
|
|
|
|
func (b *BackendPluginClientV5) Cleanup(ctx context.Context) {
|
|
|
|
_, ok := ctx.Value(ContextKeyPluginReload).(string)
|
|
|
|
if !ok {
|
|
|
|
b.Backend.Cleanup(ctx)
|
|
|
|
b.client.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b.Backend.Cleanup(ctx)
|
|
|
|
b.client.Reload()
|
|
|
|
}
|
|
|
|
|
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 *BackendPluginClientV5) IsExternal() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BackendPluginClientV5) PluginVersion() logical.PluginVersion {
|
|
|
|
if versioner, ok := b.Backend.(logical.PluginVersioner); ok {
|
|
|
|
return versioner.PluginVersion()
|
|
|
|
}
|
|
|
|
return logical.EmptyPluginVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ logical.PluginVersioner = (*BackendPluginClientV5)(nil)
|
|
|
|
_ logical.Externaler = (*BackendPluginClientV5)(nil)
|
|
|
|
)
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
// NewBackendV5 will return an instance of an RPC-based client implementation of
|
|
|
|
// the backend for external plugins, or a concrete implementation of the
|
|
|
|
// backend if it is a builtin backend. The backend is returned as a
|
|
|
|
// logical.Backend interface.
|
2022-09-09 17:14:26 +00:00
|
|
|
func NewBackendV5(ctx context.Context, pluginName string, pluginType consts.PluginType, pluginVersion string, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig) (logical.Backend, error) {
|
2022-08-30 02:42:26 +00:00
|
|
|
// Look for plugin in the plugin catalog
|
2022-09-09 17:14:26 +00:00
|
|
|
pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, pluginType, pluginVersion)
|
2022-08-30 02:42:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var backend logical.Backend
|
|
|
|
if pluginRunner.Builtin {
|
|
|
|
// Plugin is builtin so we can retrieve an instance of the interface
|
|
|
|
// from the pluginRunner. Then cast it to logical.Factory.
|
|
|
|
rawFactory, err := pluginRunner.BuiltinFactory()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error getting plugin type: %q", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if factory, ok := rawFactory.(logical.Factory); !ok {
|
|
|
|
return nil, fmt.Errorf("unsupported backend type: %q", pluginName)
|
|
|
|
} else {
|
|
|
|
if backend, err = factory(ctx, conf); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create a backendPluginClient instance
|
|
|
|
config := pluginutil.PluginClientConfig{
|
|
|
|
Name: pluginName,
|
|
|
|
PluginSets: PluginSet,
|
|
|
|
PluginType: pluginType,
|
2022-09-09 17:14:26 +00:00
|
|
|
Version: pluginVersion,
|
2022-08-30 02:42:26 +00:00
|
|
|
HandshakeConfig: HandshakeConfig,
|
|
|
|
Logger: conf.Logger.Named(pluginName),
|
|
|
|
AutoMTLS: true,
|
|
|
|
Wrapper: sys,
|
|
|
|
}
|
|
|
|
backend, err = NewPluginClientV5(ctx, sys, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return backend, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PluginSet is the map of plugins we can dispense.
|
|
|
|
var PluginSet = map[int]plugin.PluginSet{
|
|
|
|
5: {
|
|
|
|
"backend": &GRPCBackendPlugin{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func Dispense(rpcClient plugin.ClientProtocol, pluginClient pluginutil.PluginClient) (logical.Backend, error) {
|
|
|
|
// Request the plugin
|
|
|
|
raw, err := rpcClient.Dispense("backend")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var backend logical.Backend
|
|
|
|
// We should have a logical backend type now. This feels like a normal interface
|
|
|
|
// implementation but is in fact over an RPC connection.
|
|
|
|
switch c := raw.(type) {
|
|
|
|
case *backendGRPCPluginClient:
|
|
|
|
// This is an abstraction leak from go-plugin but it is necessary in
|
|
|
|
// order to enable multiplexing on multiplexed plugins
|
|
|
|
c.client = pb.NewBackendClient(pluginClient.Conn())
|
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
|
|
|
c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn())
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
backend = c
|
|
|
|
default:
|
|
|
|
return nil, errors.New("unsupported plugin client type")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &BackendPluginClientV5{
|
|
|
|
client: pluginClient,
|
|
|
|
Backend: backend,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPluginClientV5(ctx context.Context, sys pluginutil.RunnerUtil, config pluginutil.PluginClientConfig) (logical.Backend, error) {
|
|
|
|
pluginClient, err := sys.NewPluginClient(ctx, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request the plugin
|
|
|
|
raw, err := pluginClient.Dispense("backend")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var backend logical.Backend
|
|
|
|
var transport string
|
|
|
|
// We should have a logical backend type now. This feels like a normal interface
|
|
|
|
// implementation but is in fact over an RPC connection.
|
|
|
|
switch c := raw.(type) {
|
|
|
|
case *backendGRPCPluginClient:
|
|
|
|
// This is an abstraction leak from go-plugin but it is necessary in
|
|
|
|
// order to enable multiplexing on multiplexed plugins
|
|
|
|
c.client = pb.NewBackendClient(pluginClient.Conn())
|
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
|
|
|
c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn())
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
backend = c
|
|
|
|
transport = "gRPC"
|
|
|
|
default:
|
|
|
|
return nil, errors.New("unsupported plugin client type")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap the backend in a tracing middleware
|
|
|
|
if config.Logger.IsTrace() {
|
|
|
|
backend = &BackendTracingMiddleware{
|
|
|
|
logger: config.Logger.With("transport", transport),
|
|
|
|
next: backend,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &BackendPluginClientV5{
|
|
|
|
client: pluginClient,
|
|
|
|
Backend: backend,
|
|
|
|
}, nil
|
|
|
|
}
|