Support version selection for database plugins (#16982)

* Support version selection for database plugins
* Don't consider unversioned plugins for version selection algorithm
* Added version to 'plugin not found' error
* Add PluginFactoryVersion function to avoid changing sdk/ API
This commit is contained in:
Tom Proctor 2022-09-09 17:32:28 +01:00 committed by GitHub
parent 3075c5bd65
commit aa50e42fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 370 additions and 49 deletions

View File

@ -336,7 +336,7 @@ func (b *databaseBackend) GetConnectionWithConfig(ctx context.Context, name stri
return nil, err
}
dbw, err := newDatabaseWrapper(ctx, config.PluginName, b.System(), b.logger)
dbw, err := newDatabaseWrapper(ctx, config.PluginName, config.PluginVersion, b.System(), b.logger)
if err != nil {
return nil, fmt.Errorf("unable to create database instance: %w", err)
}

View File

@ -48,12 +48,12 @@ func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)
sys := vault.TestDynamicSystemView(cores[0].Core, nil)
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Postgres", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin-muxed", consts.PluginTypeDatabase, "TestBackend_PluginMain_PostgresMultiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodb-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Mongo", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodb-database-plugin-muxed", consts.PluginTypeDatabase, "TestBackend_PluginMain_MongoMultiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodbatlas-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_MongoAtlas", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodbatlas-database-plugin-muxed", consts.PluginTypeDatabase, "TestBackend_PluginMain_MongoAtlasMultiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_Postgres", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_PostgresMultiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodb-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_Mongo", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodb-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MongoMultiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodbatlas-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MongoAtlas", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "mongodbatlas-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MongoAtlasMultiplexed", []string{}, "")
return cluster, sys
}
@ -236,6 +236,7 @@ func TestBackend_config_connection(t *testing.T) {
"allowed_roles": []string{"*"},
"root_credentials_rotate_statements": []string{},
"password_policy": "",
"plugin_version": "",
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
@ -289,6 +290,7 @@ func TestBackend_config_connection(t *testing.T) {
"allowed_roles": []string{"*"},
"root_credentials_rotate_statements": []string{},
"password_policy": "",
"plugin_version": "",
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
@ -331,6 +333,7 @@ func TestBackend_config_connection(t *testing.T) {
"allowed_roles": []string{"flu", "barre"},
"root_credentials_rotate_statements": []string{},
"password_policy": "",
"plugin_version": "",
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
@ -728,6 +731,7 @@ func TestBackend_connectionCrud(t *testing.T) {
"allowed_roles": []string{"plugin-role-test"},
"root_credentials_rotate_statements": []string(nil),
"password_policy": "",
"plugin_version": "",
}
req.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
@ -1503,7 +1507,7 @@ func TestBackend_AsyncClose(t *testing.T) {
// Test that having a plugin that takes a LONG time to close will not cause the cleanup function to take
// longer than 750ms.
cluster, sys := getCluster(t)
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "hanging-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Hanging", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "hanging-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_Hanging", []string{}, "")
t.Cleanup(cluster.Cleanup)
config := logical.TestBackendConfig()

View File

@ -110,7 +110,7 @@ func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
cores := cluster.Cores
sys := vault.TestDynamicSystemView(cores[0].Core, nil)
vault.TestAddTestPlugin(t, cores[0].Core, "test-plugin", consts.PluginTypeDatabase, "TestPlugin_GRPC_Main", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "test-plugin", consts.PluginTypeDatabase, "", "TestPlugin_GRPC_Main", []string{}, "")
return cluster, sys
}
@ -139,7 +139,7 @@ func TestPlugin_Init(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
dbRaw, err := dbplugin.PluginFactory(namespace.RootContext(nil), "test-plugin", sys, log.NewNullLogger())
dbRaw, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
@ -163,7 +163,7 @@ func TestPlugin_CreateUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactory(namespace.RootContext(nil), "test-plugin", sys, log.NewNullLogger())
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
@ -203,7 +203,7 @@ func TestPlugin_RenewUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactory(namespace.RootContext(nil), "test-plugin", sys, log.NewNullLogger())
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
@ -237,7 +237,7 @@ func TestPlugin_RevokeUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactory(namespace.RootContext(nil), "test-plugin", sys, log.NewNullLogger())
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}

View File

@ -5,12 +5,16 @@ import (
"errors"
"fmt"
"net/url"
"sort"
"github.com/fatih/structs"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-version"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
)
@ -22,7 +26,8 @@ var (
// DatabaseConfig is used by the Factory function to configure a Database
// object.
type DatabaseConfig struct {
PluginName string `json:"plugin_name" structs:"plugin_name" mapstructure:"plugin_name"`
PluginName string `json:"plugin_name" structs:"plugin_name" mapstructure:"plugin_name"`
PluginVersion string `json:"plugin_version" structs:"plugin_version" mapstructure:"plugin_version"`
// ConnectionDetails stores the database specific connection settings needed
// by each database type.
ConnectionDetails map[string]interface{} `json:"connection_details" structs:"connection_details" mapstructure:"connection_details"`
@ -110,6 +115,11 @@ func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
that plugin type.`,
},
"plugin_version": {
Type: framework.TypeString,
Description: `The version of the plugin to use.`,
},
"verify_connection": {
Type: framework.TypeBool,
Default: true,
@ -281,6 +291,48 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
return logical.ErrorResponse(respErrEmptyPluginName), nil
}
if pluginVersionRaw, ok := data.GetOk("plugin_version"); ok {
config.PluginVersion = pluginVersionRaw.(string)
}
unversionedPlugin, err := b.System().LookupPlugin(ctx, config.PluginName, consts.PluginTypeDatabase)
switch {
case config.PluginVersion != "":
semanticVersion, err := version.NewVersion(config.PluginVersion)
if err != nil {
return logical.ErrorResponse("version %q is not a valid semantic version: %s", config.PluginVersion, err), nil
}
// Canonicalize the version.
config.PluginVersion = "v" + semanticVersion.String()
case err == nil && !unversionedPlugin.Builtin:
// We'll select the unversioned plugin that's been registered.
case req.Operation == logical.CreateOperation:
// No version provided and no unversioned plugin of that name available.
// Pin to the current latest version if any versioned plugins are registered.
plugins, err := b.System().ListVersionedPlugins(ctx, consts.PluginTypeDatabase)
if err != nil {
return nil, err
}
var versionedCandidates []pluginutil.VersionedPlugin
for _, plugin := range plugins {
if !plugin.Builtin && plugin.Name == config.PluginName && plugin.Version != "" {
versionedCandidates = append(versionedCandidates, plugin)
}
}
if len(versionedCandidates) != 0 {
// Sort in reverse order.
sort.SliceStable(versionedCandidates, func(i, j int) bool {
return versionedCandidates[i].SemanticVersion.GreaterThan(versionedCandidates[j].SemanticVersion)
})
config.PluginVersion = "v" + versionedCandidates[0].SemanticVersion.String()
b.logger.Debug(fmt.Sprintf("pinning %q database plugin version %q from candidates %v", config.PluginName, config.PluginVersion, versionedCandidates))
}
}
if allowedRolesRaw, ok := data.GetOk("allowed_roles"); ok {
config.AllowedRoles = allowedRolesRaw.([]string)
} else if req.Operation == logical.CreateOperation {
@ -301,6 +353,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
// ConnectionDetails.
delete(data.Raw, "name")
delete(data.Raw, "plugin_name")
delete(data.Raw, "plugin_version")
delete(data.Raw, "allowed_roles")
delete(data.Raw, "verify_connection")
delete(data.Raw, "root_rotation_statements")
@ -326,7 +379,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
}
// Create a database plugin and initialize it.
dbw, err := newDatabaseWrapper(ctx, config.PluginName, b.System(), b.logger)
dbw, err := newDatabaseWrapper(ctx, config.PluginName, config.PluginVersion, b.System(), b.logger)
if err != nil {
return logical.ErrorResponse("error creating database object: %s", err), nil
}

View File

@ -20,8 +20,8 @@ type databaseVersionWrapper struct {
// newDatabaseWrapper figures out which version of the database the pluginName is referring to and returns a wrapper object
// that can be used to make operations on the underlying database plugin.
func newDatabaseWrapper(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (dbw databaseVersionWrapper, err error) {
newDB, err := v5.PluginFactory(ctx, pluginName, sys, logger)
func newDatabaseWrapper(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (dbw databaseVersionWrapper, err error) {
newDB, err := v5.PluginFactoryVersion(ctx, pluginName, pluginVersion, sys, logger)
if err == nil {
dbw = databaseVersionWrapper{
v5: newDB,
@ -32,7 +32,7 @@ func newDatabaseWrapper(ctx context.Context, pluginName string, sys pluginutil.L
merr := &multierror.Error{}
merr = multierror.Append(merr, err)
legacyDB, err := v4.PluginFactory(ctx, pluginName, sys, logger)
legacyDB, err := v4.PluginFactoryVersion(ctx, pluginName, pluginVersion, sys, logger)
if err == nil {
dbw = databaseVersionWrapper{
v4: legacyDB,

View File

@ -22,9 +22,9 @@ func TestPlugin_lifecycle(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v4-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_MockV4", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_MockV5", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v6-database-plugin-muxed", consts.PluginTypeDatabase, "TestBackend_PluginMain_MockV6Multiplexed", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v4-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV4", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v6-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV6Multiplexed", []string{}, "")
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
@ -218,6 +218,216 @@ func TestPlugin_lifecycle(t *testing.T) {
}
}
func TestPlugin_VersionSelection(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
for _, version := range []string{"v11.0.0", "v11.0.1-rc1", "v2.0.0"} {
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, version, "TestBackend_PluginMain_MockV5", []string{}, "")
}
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to database backend")
}
defer b.Cleanup(context.Background())
test := func(t *testing.T, selectVersion, expectedVersion string) func(t *testing.T) {
return func(t *testing.T) {
req := &logical.Request{
Operation: logical.CreateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: map[string]interface{}{
"connection_url": "sample_connection_url",
"plugin_name": "mock-v5-database-plugin",
"plugin_version": selectVersion,
"verify_connection": true,
"allowed_roles": []string{"*"},
"name": "mockv5",
"username": "mockv5-user",
"password": "mysecurepassword",
},
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
defer func() {
_, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "config/db",
Storage: config.StorageView,
})
if err != nil {
t.Fatal(err)
}
}()
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "config/db",
Storage: config.StorageView,
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
if resp.Data["plugin_version"].(string) != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, resp.Data["plugin_version"].(string))
}
}
}
for name, tc := range map[string]struct {
selectVersion string
expectedVersion string
}{
"no version specified, selects latest in the absence of unversioned plugins": {
selectVersion: "",
expectedVersion: "v11.0.1-rc1",
},
"specific version selected": {
selectVersion: "11.0.0",
expectedVersion: "v11.0.0",
},
} {
t.Run(name, test(t, tc.selectVersion, tc.expectedVersion))
}
// Register a newer version of the plugin, and ensure that's the new default version selected.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "v11.0.1", "TestBackend_PluginMain_MockV5", []string{}, "")
t.Run("no version specified, new latest version selected", test(t, "", "v11.0.1"))
// Register an unversioned plugin and ensure that is now selected when no version is specified.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", []string{}, "")
for name, tc := range map[string]struct {
selectVersion string
expectedVersion string
}{
"no version specified, selects unversioned": {
selectVersion: "",
expectedVersion: "",
},
"specific version selected": {
selectVersion: "v2.0.0",
expectedVersion: "v2.0.0",
},
} {
t.Run(name, test(t, tc.selectVersion, tc.expectedVersion))
}
}
func TestPlugin_VersionMustBeExplicitlyUpgraded(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to database backend")
}
defer b.Cleanup(context.Background())
configData := func(extraData ...string) map[string]interface{} {
data := map[string]interface{}{
"connection_url": "sample_connection_url",
"plugin_name": "mysql-database-plugin",
"verify_connection": false,
"allowed_roles": []string{"*"},
"username": "mockv5-user",
"password": "mysecurepassword",
}
if len(extraData)%2 != 0 {
t.Fatal("Expected an even number of args in extraData")
}
for i := 0; i < len(extraData); i += 2 {
data[extraData[i]] = extraData[i+1]
}
return data
}
readVersion := func() string {
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "config/db",
Storage: config.StorageView,
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
return resp.Data["plugin_version"].(string)
}
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.CreateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData(),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version := readVersion()
expectedVersion := ""
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
// Register versioned plugin, and check that a new write to existing config doesn't upgrade the plugin implicitly.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mysql-database-plugin", consts.PluginTypeDatabase, "v1.0.0", "TestBackend_PluginMain_MockV5", []string{}, "")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData(),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version = readVersion()
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
// Now explicitly upgrade.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData("plugin_version", "1.0.0"),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version = readVersion()
expectedVersion = "v1.0.0"
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
}
func cleanup(t *testing.T, b *databaseBackend, reqs []*logical.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

View File

@ -2,6 +2,7 @@ package plugin
import (
"context"
"errors"
"testing"
"github.com/hashicorp/vault/sdk/helper/logging"
@ -193,3 +194,7 @@ func (v testSystemView) LookupPluginVersion(context.Context, string, consts.Plug
},
}, nil
}
func (v testSystemView) ListVersionedPlugins(_ context.Context, _ consts.PluginType) ([]pluginutil.VersionedPlugin, error) {
return nil, errors.New("ListVersionedPlugins not implemented for testSystemView")
}

View File

@ -133,7 +133,7 @@ func testConfig(t *testing.T, pluginCmd string) (*logical.BackendConfig, func())
os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, pluginCmd, []string{}, "")
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", pluginCmd, []string{}, "")
return config, func() {
cluster.Cleanup()

View File

@ -52,7 +52,7 @@ func getPluginClusterAndCore(t testing.TB, logger log.Logger) (*vault.TestCluste
os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)
vault.TestWaitActive(benchhelpers.TBtoT(t), core.Core)
vault.TestAddTestPlugin(benchhelpers.TBtoT(t), core.Core, "mock-plugin", consts.PluginTypeSecrets, "TestPlugin_PluginMain", []string{}, "")
vault.TestAddTestPlugin(benchhelpers.TBtoT(t), core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestPlugin_PluginMain", []string{}, "")
// Mount the mock plugin
err = core.Client.Sys().Mount("mock", &api.MountInput{

View File

@ -71,9 +71,15 @@ type Database interface {
// PluginFactory is used to build plugin database types. It wraps the database
// object in a logging and metrics middleware.
func PluginFactory(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
func PluginFactory(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
return PluginFactoryVersion(ctx, pluginName, "", sys, logger)
}
// PluginFactory is used to build plugin database types with a version specified.
// It wraps the database object in a logging and metrics middleware.
func PluginFactoryVersion(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
// Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, consts.PluginTypeDatabase)
pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, consts.PluginTypeDatabase, pluginVersion)
if err != nil {
return nil, err
}

View File

@ -13,8 +13,14 @@ import (
// PluginFactory is used to build plugin database types. It wraps the database
// object in a logging and metrics middleware.
func PluginFactory(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
return PluginFactoryVersion(ctx, pluginName, "", sys, logger)
}
// PluginFactoryVersion is used to build plugin database types with a version specified.
// It wraps the database object in a logging and metrics middleware.
func PluginFactoryVersion(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
// Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, consts.PluginTypeDatabase)
pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, consts.PluginTypeDatabase, pluginVersion)
if err != nil {
return nil, err
}
@ -43,6 +49,7 @@ func PluginFactory(ctx context.Context, pluginName string, sys pluginutil.LookRu
config := pluginutil.PluginClientConfig{
Name: pluginName,
PluginType: consts.PluginTypeDatabase,
Version: pluginVersion,
PluginSets: PluginSets,
HandshakeConfig: HandshakeConfig,
Logger: namedLogger,

View File

@ -60,6 +60,10 @@ type SystemView interface {
// name and version. Returns a PluginRunner or an error if a plugin can not be found.
LookupPluginVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, version string) (*pluginutil.PluginRunner, error)
// ListVersionedPlugins returns information about all plugins of a certain
// type in the catalog, including any versioning information stored for them.
ListVersionedPlugins(ctx context.Context, pluginType consts.PluginType) ([]pluginutil.VersionedPlugin, error)
// NewPluginClient returns a client for managing the lifecycle of plugin
// processes
NewPluginClient(ctx context.Context, config pluginutil.PluginClientConfig) (pluginutil.PluginClient, error)
@ -176,6 +180,10 @@ func (d StaticSystemView) LookupPluginVersion(_ context.Context, _ string, _ con
return nil, errors.New("LookupPluginVersion is not implemented in StaticSystemView")
}
func (d StaticSystemView) ListVersionedPlugins(_ context.Context, _ consts.PluginType) ([]pluginutil.VersionedPlugin, error) {
return nil, errors.New("ListVersionedPlugins is not implemented in StaticSystemView")
}
func (d StaticSystemView) MlockEnabled() bool {
return d.EnableMlock
}

View File

@ -111,6 +111,10 @@ func (s *gRPCSystemViewClient) LookupPluginVersion(_ context.Context, _ string,
return nil, fmt.Errorf("cannot call LookupPluginVersion from a plugin backend")
}
func (s *gRPCSystemViewClient) ListVersionedPlugins(_ context.Context, _ consts.PluginType) ([]pluginutil.VersionedPlugin, error) {
return nil, fmt.Errorf("cannot call ListVersionedPlugins from a plugin backend")
}
func (s *gRPCSystemViewClient) MlockEnabled() bool {
reply, err := s.client.MlockEnabled(context.Background(), &pb.Empty{})
if err != nil {

View File

@ -916,7 +916,11 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
return nil, err
}
if plug == nil {
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, t)
errContext := t
if entry.Version != "" {
errContext += fmt.Sprintf(", version=%s", entry.Version)
}
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, errContext)
}
f = plugin.Factory

View File

@ -251,12 +251,28 @@ func (d dynamicSystemView) LookupPluginVersion(ctx context.Context, name string,
return nil, err
}
if r == nil {
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, name)
errContext := name
if version != "" {
errContext += fmt.Sprintf(", version=%s", version)
}
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, errContext)
}
return r, nil
}
// ListVersionedPlugins returns information about all plugins of a certain
// typein the catalog, including any versioning information stored for them.
func (d dynamicSystemView) ListVersionedPlugins(ctx context.Context, pluginType consts.PluginType) ([]pluginutil.VersionedPlugin, error) {
if d.core == nil {
return nil, fmt.Errorf("system view core is nil")
}
if d.core.pluginCatalog == nil {
return nil, fmt.Errorf("system view core plugin catalog is nil")
}
return d.core.pluginCatalog.ListVersionedPlugins(ctx, pluginType)
}
// MlockEnabled returns the configuration setting for enabling mlock on plugins.
func (d dynamicSystemView) MlockEnabled() bool {
return d.core.enableMlock

View File

@ -242,7 +242,7 @@ func TestSystemBackend_Plugin_MismatchType(t *testing.T) {
core := cluster.Cores[0]
// Add a credential backend with the same name
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "TestBackend_PluginMainCredentials", []string{}, "")
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", "TestBackend_PluginMainCredentials", []string{}, "")
// Make a request to lazy load the now-credential plugin
// and expect an error
@ -335,13 +335,13 @@ func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMoun
switch btype {
case logical.TypeLogical:
// Add plugin back to the catalog
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, logicalVersionMap[tc.pluginVersion], []string{}, "")
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", logicalVersionMap[tc.pluginVersion], []string{}, "")
_, err = core.Client.Logical().Write("sys/mounts/mock-0", map[string]interface{}{
"type": "test",
})
case logical.TypeCredential:
// Add plugin back to the catalog
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, credentialVersionMap[tc.pluginVersion], []string{}, "")
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", credentialVersionMap[tc.pluginVersion], []string{}, "")
_, err = core.Client.Logical().Write("sys/auth/mock-0", map[string]interface{}{
"type": "test",
})
@ -463,10 +463,10 @@ func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatc
switch btype {
case logical.TypeLogical:
plugin := logicalVersionMap[tc.pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, plugin, []string{}, cluster.TempDir)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", plugin, []string{}, cluster.TempDir)
case logical.TypeCredential:
plugin := credentialVersionMap[tc.pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, plugin, []string{}, cluster.TempDir)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", plugin, []string{}, cluster.TempDir)
}
// Reload the plugin
@ -755,7 +755,7 @@ func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType lo
switch backendType {
case logical.TypeLogical:
plugin := logicalVersionMap[pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, plugin, []string{}, tempDir)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", plugin, []string{}, tempDir)
for i := 0; i < numMounts; i++ {
// Alternate input styles for plugin_name on every other mount
options := map[string]interface{}{
@ -771,7 +771,7 @@ func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType lo
}
case logical.TypeCredential:
plugin := credentialVersionMap[pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, plugin, []string{}, tempDir)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", plugin, []string{}, tempDir)
for i := 0; i < numMounts; i++ {
// Alternate input styles for plugin_name on every other mount
options := map[string]interface{}{
@ -826,7 +826,7 @@ func testSystemBackend_SingleCluster_Env(t *testing.T, env []string) *vault.Test
os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "TestBackend_PluginMainEnv", env, tempDir)
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestBackend_PluginMainEnv", env, tempDir)
options := map[string]interface{}{
"type": "mock-plugin",
}

View File

@ -306,7 +306,7 @@ const mountStateUnmounting = "unmounting"
type MountEntry struct {
Table string `json:"table"` // The table it belongs to
Path string `json:"path"` // Mount Path
Type string `json:"type"` // Logical backend Type
Type string `json:"type"` // Logical backend Type. NB: This is the plugin name, e.g. my-vault-plugin, NOT plugin type (e.g. auth).
Description string `json:"description"` // User-provided description
UUID string `json:"uuid"` // Barrier view UUID
BackendAwareUUID string `json:"backend_aware_uuid"` // UUID that can be used by the backend as a helper when a consistent value is needed outside of storage.
@ -330,9 +330,9 @@ type MountEntry struct {
synthesizedConfigCache sync.Map
// version info
Version string `json:"version,omitempty"`
Sha string `json:"sha,omitempty"`
RunningVersion string `json:"running_version,omitempty"`
Version string `json:"version,omitempty"` // The semantic version of the mounted plugin, e.g. v1.2.3.
Sha string `json:"sha,omitempty"` // The SHA256 sum of the plugin binary.
RunningVersion string `json:"running_version,omitempty"` // The semantic version of the mounted plugin as reported by the plugin.
RunningSha string `json:"running_sha,omitempty"`
}
@ -1526,7 +1526,11 @@ func (c *Core) newLogicalBackend(ctx context.Context, entry *MountEntry, sysView
return nil, err
}
if plug == nil {
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, t)
errContext := t
if entry.Version != "" {
errContext += fmt.Sprintf(", version=%s", entry.Version)
}
return nil, fmt.Errorf("%w: %s", ErrPluginNotFound, errContext)
}
f = plugin.Factory
@ -1548,6 +1552,7 @@ func (c *Core) newLogicalBackend(ctx context.Context, entry *MountEntry, sysView
}
conf["plugin_type"] = consts.PluginTypeSecrets.String()
conf["plugin_version"] = entry.Version
backendLogger := c.baseLogger.Named(fmt.Sprintf("secrets.%s.%s", t, entry.Accessor))
c.AddLogger(backendLogger)

View File

@ -373,13 +373,13 @@ func TestPluginCatalog_NewPluginClient(t *testing.T) {
}
// register plugins
TestAddTestPlugin(t, core, "mux-postgres", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_PostgresMultiplexed", []string{}, "")
TestAddTestPlugin(t, core, "single-postgres-1", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
TestAddTestPlugin(t, core, "single-postgres-2", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
TestAddTestPlugin(t, core, "mux-postgres", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_PostgresMultiplexed", []string{}, "")
TestAddTestPlugin(t, core, "single-postgres-1", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
TestAddTestPlugin(t, core, "single-postgres-2", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
TestAddTestPlugin(t, core, "mux-userpass", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_UserpassMultiplexed", []string{}, "")
TestAddTestPlugin(t, core, "single-userpass-1", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
TestAddTestPlugin(t, core, "single-userpass-2", consts.PluginTypeUnknown, "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
TestAddTestPlugin(t, core, "mux-userpass", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_UserpassMultiplexed", []string{}, "")
TestAddTestPlugin(t, core, "single-userpass-1", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
TestAddTestPlugin(t, core, "single-userpass-2", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
var pluginClients []*pluginClient
// run plugins

View File

@ -513,7 +513,7 @@ func TestDynamicSystemView(c *Core, ns *namespace.Namespace) *dynamicSystemView
// TestAddTestPlugin registers the testFunc as part of the plugin command to the
// plugin catalog. If provided, uses tmpDir as the plugin directory.
func TestAddTestPlugin(t testing.T, c *Core, name string, pluginType consts.PluginType, testFunc string, env []string, tempDir string) {
func TestAddTestPlugin(t testing.T, c *Core, name string, pluginType consts.PluginType, version string, testFunc string, env []string, tempDir string) {
file, err := os.Open(os.Args[0])
if err != nil {
t.Fatal(err)
@ -575,7 +575,6 @@ func TestAddTestPlugin(t testing.T, c *Core, name string, pluginType consts.Plug
c.pluginCatalog.directory = fullPath
args := []string{fmt.Sprintf("--test.run=%s", testFunc)}
version := ""
err = c.pluginCatalog.Set(context.Background(), name, pluginType, version, fileName, args, env, sum)
if err != nil {
t.Fatal(err)