2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2017-04-12 16:40:54 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2018-01-19 06:44:44 +00:00
|
|
|
"context"
|
2022-10-05 08:29:29 +00:00
|
|
|
"crypto/sha256"
|
2022-09-22 22:15:46 +00:00
|
|
|
"encoding/json"
|
2017-04-12 16:40:54 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
"github.com/hashicorp/vault/api"
|
|
|
|
"github.com/hashicorp/vault/builtin/credential/userpass"
|
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/vault/helper/versions"
|
2022-03-08 16:33:24 +00:00
|
|
|
"github.com/hashicorp/vault/plugins/database/postgresql"
|
|
|
|
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
2021-04-08 16:43:39 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
2022-08-30 02:42:26 +00:00
|
|
|
backendplugin "github.com/hashicorp/vault/sdk/plugin"
|
2021-04-08 16:43:39 +00:00
|
|
|
|
2017-04-12 16:40:54 +00:00
|
|
|
"github.com/hashicorp/vault/helper/builtinplugins"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPluginCatalog_CRUD(t *testing.T) {
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
2022-08-25 20:31:42 +00:00
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
2022-08-25 20:31:42 +00:00
|
|
|
t.Fatal(err)
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
core.pluginCatalog.directory = tempDir
|
|
|
|
|
|
|
|
const pluginName = "mysql-database-plugin"
|
2017-04-12 16:40:54 +00:00
|
|
|
|
|
|
|
// Get builtin plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
p, err := core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
2022-11-23 18:36:25 +00:00
|
|
|
// Get it again, explicitly specifying builtin version
|
|
|
|
builtinVersion := versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName)
|
|
|
|
p2, err := core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, builtinVersion)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
2017-04-12 16:40:54 +00:00
|
|
|
expectedBuiltin := &pluginutil.PluginRunner{
|
2022-08-25 20:31:42 +00:00
|
|
|
Name: pluginName,
|
2018-11-07 01:21:24 +00:00
|
|
|
Type: consts.PluginTypeDatabase,
|
2017-04-12 16:40:54 +00:00
|
|
|
Builtin: true,
|
2022-11-23 18:36:25 +00:00
|
|
|
Version: builtinVersion,
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)
|
2017-04-12 16:40:54 +00:00
|
|
|
|
2017-04-21 17:24:34 +00:00
|
|
|
if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) {
|
|
|
|
t.Fatal("expected BuiltinFactory did not match actual")
|
|
|
|
}
|
|
|
|
expectedBuiltin.BuiltinFactory = nil
|
|
|
|
p.BuiltinFactory = nil
|
2022-11-23 18:36:25 +00:00
|
|
|
p2.BuiltinFactory = nil
|
2017-04-12 16:40:54 +00:00
|
|
|
if !reflect.DeepEqual(p, expectedBuiltin) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expectedBuiltin)
|
|
|
|
}
|
2022-11-23 18:36:25 +00:00
|
|
|
if !reflect.DeepEqual(p2, expectedBuiltin) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p2, expectedBuiltin)
|
|
|
|
}
|
2017-04-12 16:40:54 +00:00
|
|
|
|
|
|
|
// Set a plugin, test overwriting a builtin plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
file, err := ioutil.TempFile(tempDir, "temp")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
command := filepath.Base(file.Name())
|
2022-08-25 20:31:42 +00:00
|
|
|
err = core.pluginCatalog.Set(context.Background(), pluginName, consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{"FOO=BAR"}, []byte{'1'})
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
p, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
2022-11-23 18:36:25 +00:00
|
|
|
// Get it again, explicitly specifying builtin version.
|
|
|
|
// This time it should fail because it was overwritten.
|
|
|
|
p2, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, builtinVersion)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
if p2 != nil {
|
|
|
|
t.Fatalf("expected no result, got: %#v", p2)
|
|
|
|
}
|
|
|
|
|
2017-04-12 16:40:54 +00:00
|
|
|
expected := &pluginutil.PluginRunner{
|
2022-08-25 20:31:42 +00:00
|
|
|
Name: pluginName,
|
2018-11-07 01:21:24 +00:00
|
|
|
Type: consts.PluginTypeDatabase,
|
2022-08-25 20:31:42 +00:00
|
|
|
Command: filepath.Join(tempDir, filepath.Base(file.Name())),
|
2017-04-12 16:40:54 +00:00
|
|
|
Args: []string{"--test"},
|
2018-09-20 17:50:29 +00:00
|
|
|
Env: []string{"FOO=BAR"},
|
2017-04-12 16:40:54 +00:00
|
|
|
Sha256: []byte{'1'},
|
|
|
|
Builtin: false,
|
2022-08-25 20:31:42 +00:00
|
|
|
Version: "",
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(p, expected) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
err = core.pluginCatalog.Delete(context.Background(), pluginName, consts.PluginTypeDatabase, "")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get builtin plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
p, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
2017-04-21 17:24:34 +00:00
|
|
|
expectedBuiltin = &pluginutil.PluginRunner{
|
2022-08-25 20:31:42 +00:00
|
|
|
Name: pluginName,
|
2018-11-07 01:21:24 +00:00
|
|
|
Type: consts.PluginTypeDatabase,
|
2017-04-21 17:24:34 +00:00
|
|
|
Builtin: true,
|
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
|
|
|
Version: versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName),
|
2017-04-21 17:24:34 +00:00
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)
|
2017-04-21 17:24:34 +00:00
|
|
|
|
|
|
|
if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) {
|
|
|
|
t.Fatal("expected BuiltinFactory did not match actual")
|
|
|
|
}
|
|
|
|
expectedBuiltin.BuiltinFactory = nil
|
|
|
|
p.BuiltinFactory = nil
|
2017-04-12 16:40:54 +00:00
|
|
|
if !reflect.DeepEqual(p, expectedBuiltin) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expectedBuiltin)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:31:42 +00:00
|
|
|
func TestPluginCatalog_VersionedCRUD(t *testing.T) {
|
2017-04-12 16:40:54 +00:00
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
2022-08-25 20:31:42 +00:00
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
core.pluginCatalog.directory = tempDir
|
|
|
|
|
|
|
|
// Set a versioned plugin.
|
|
|
|
file, err := ioutil.TempFile(tempDir, "temp")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2022-09-22 22:15:46 +00:00
|
|
|
const name = "mysql-database-plugin"
|
2022-08-25 20:31:42 +00:00
|
|
|
const version = "1.0.0"
|
|
|
|
command := fmt.Sprintf("%s", filepath.Base(file.Name()))
|
2022-09-22 22:15:46 +00:00
|
|
|
err = core.pluginCatalog.Set(context.Background(), name, consts.PluginTypeDatabase, version, command, []string{"--test"}, []string{"FOO=BAR"}, []byte{'1'})
|
2022-08-25 20:31:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the plugin
|
2022-09-22 22:15:46 +00:00
|
|
|
plugin, err := core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, version)
|
2022-08-25 20:31:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := &pluginutil.PluginRunner{
|
2022-09-22 22:15:46 +00:00
|
|
|
Name: name,
|
2022-08-25 20:31:42 +00:00
|
|
|
Type: consts.PluginTypeDatabase,
|
|
|
|
Version: version,
|
|
|
|
Command: filepath.Join(tempDir, filepath.Base(file.Name())),
|
|
|
|
Args: []string{"--test"},
|
|
|
|
Env: []string{"FOO=BAR"},
|
|
|
|
Sha256: []byte{'1'},
|
|
|
|
Builtin: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(plugin, expected) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugin, expected)
|
|
|
|
}
|
|
|
|
|
2022-09-22 22:15:46 +00:00
|
|
|
// Also get the builtin version to check we can still access that.
|
|
|
|
builtinVersion := versions.GetBuiltinVersion(consts.PluginTypeDatabase, name)
|
|
|
|
plugin, err = core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, builtinVersion)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected = &pluginutil.PluginRunner{
|
|
|
|
Name: name,
|
|
|
|
Type: consts.PluginTypeDatabase,
|
|
|
|
Version: builtinVersion,
|
|
|
|
Builtin: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check by marshalling to JSON to avoid messing with BuiltinFactory function field.
|
|
|
|
expectedBytes, err := json.Marshal(expected)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
actualBytes, err := json.Marshal(plugin)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if string(expectedBytes) != string(actualBytes) {
|
|
|
|
t.Fatalf("expected %s, got %s", string(expectedBytes), string(actualBytes))
|
|
|
|
}
|
|
|
|
if !plugin.Builtin {
|
|
|
|
t.Fatal("expected builtin true but got false")
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:31:42 +00:00
|
|
|
// Delete the plugin
|
2022-09-22 22:15:46 +00:00
|
|
|
err = core.pluginCatalog.Delete(context.Background(), name, consts.PluginTypeDatabase, version)
|
2022-08-25 20:31:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get plugin - should fail
|
2022-09-22 22:15:46 +00:00
|
|
|
plugin, err = core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, version)
|
2022-08-25 20:31:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if plugin != nil {
|
|
|
|
t.Fatalf("expected no plugin with this version to be in the catalog, but found %+v", plugin)
|
|
|
|
}
|
|
|
|
}
|
2017-04-12 16:40:54 +00:00
|
|
|
|
2022-08-25 20:31:42 +00:00
|
|
|
func TestPluginCatalog_List(t *testing.T) {
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
2022-08-25 20:31:42 +00:00
|
|
|
t.Fatal(err)
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
core.pluginCatalog.directory = tempDir
|
2017-04-12 16:40:54 +00:00
|
|
|
|
|
|
|
// Get builtin plugins and sort them
|
2018-11-07 01:21:24 +00:00
|
|
|
builtinKeys := builtinplugins.Registry.Keys(consts.PluginTypeDatabase)
|
2017-04-12 16:40:54 +00:00
|
|
|
sort.Strings(builtinKeys)
|
|
|
|
|
|
|
|
// List only builtin plugins
|
2018-11-07 01:21:24 +00:00
|
|
|
plugins, err := core.pluginCatalog.List(context.Background(), consts.PluginTypeDatabase)
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
sort.Strings(plugins)
|
2017-04-12 16:40:54 +00:00
|
|
|
|
|
|
|
if len(plugins) != len(builtinKeys) {
|
|
|
|
t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys), len(plugins))
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:31:42 +00:00
|
|
|
if !reflect.DeepEqual(plugins, builtinKeys) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins, builtinKeys)
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set a plugin, test overwriting a builtin plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
file, err := ioutil.TempFile(tempDir, "temp")
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2018-01-18 00:19:28 +00:00
|
|
|
command := filepath.Base(file.Name())
|
2022-08-25 20:31:42 +00:00
|
|
|
err = core.pluginCatalog.Set(context.Background(), "mysql-database-plugin", consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{}, []byte{'1'})
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set another plugin
|
2022-08-25 20:31:42 +00:00
|
|
|
err = core.pluginCatalog.Set(context.Background(), "aaaaaaa", consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{}, []byte{'1'})
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// List the plugins
|
2018-11-07 01:21:24 +00:00
|
|
|
plugins, err = core.pluginCatalog.List(context.Background(), consts.PluginTypeDatabase)
|
2017-04-12 16:40:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
sort.Strings(plugins)
|
2017-04-12 16:40:54 +00:00
|
|
|
|
2018-11-07 01:21:24 +00:00
|
|
|
// plugins has a test-added plugin called "aaaaaaa" that is not built in
|
2017-04-12 16:40:54 +00:00
|
|
|
if len(plugins) != len(builtinKeys)+1 {
|
|
|
|
t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys)+1, len(plugins))
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify the first plugin is the one we just created.
|
|
|
|
if !reflect.DeepEqual(plugins[0], "aaaaaaa") {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[0], "aaaaaaa")
|
|
|
|
}
|
|
|
|
|
2018-03-20 18:54:10 +00:00
|
|
|
// verify the builtin plugins are correct
|
2022-08-25 20:31:42 +00:00
|
|
|
if !reflect.DeepEqual(plugins[1:], builtinKeys) {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[1:], builtinKeys)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPluginCatalog_ListVersionedPlugins(t *testing.T) {
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
core.pluginCatalog.directory = tempDir
|
|
|
|
|
|
|
|
// Get builtin plugins and sort them
|
|
|
|
builtinKeys := builtinplugins.Registry.Keys(consts.PluginTypeDatabase)
|
|
|
|
sort.Strings(builtinKeys)
|
|
|
|
|
|
|
|
// List only builtin plugins
|
|
|
|
plugins, err := core.pluginCatalog.ListVersionedPlugins(context.Background(), consts.PluginTypeDatabase)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
sortVersionedPlugins(plugins)
|
|
|
|
|
|
|
|
if len(plugins) != len(builtinKeys) {
|
|
|
|
t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys), len(plugins))
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, plugin := range plugins {
|
|
|
|
if plugin.Name != builtinKeys[i] {
|
|
|
|
t.Fatalf("expected plugin list with names %v but got %+v", builtinKeys, plugins)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a plugin, test overwriting a builtin plugin
|
|
|
|
file, err := ioutil.TempFile(tempDir, "temp")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
command := filepath.Base(file.Name())
|
|
|
|
err = core.pluginCatalog.Set(
|
|
|
|
context.Background(),
|
|
|
|
"mysql-database-plugin",
|
|
|
|
consts.PluginTypeDatabase,
|
|
|
|
"",
|
|
|
|
command,
|
|
|
|
[]string{"--test"},
|
|
|
|
[]string{},
|
|
|
|
[]byte{'1'},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set another plugin, with version information
|
|
|
|
err = core.pluginCatalog.Set(
|
|
|
|
context.Background(),
|
|
|
|
"aaaaaaa",
|
|
|
|
consts.PluginTypeDatabase,
|
|
|
|
"1.1.0",
|
|
|
|
command,
|
|
|
|
[]string{"--test"},
|
|
|
|
[]string{},
|
|
|
|
[]byte{'1'},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// List the plugins
|
|
|
|
plugins, err = core.pluginCatalog.ListVersionedPlugins(context.Background(), consts.PluginTypeDatabase)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error %v", err)
|
|
|
|
}
|
|
|
|
sortVersionedPlugins(plugins)
|
|
|
|
|
|
|
|
// plugins has a test-added plugin called "aaaaaaa" that is not built in
|
|
|
|
if len(plugins) != len(builtinKeys)+1 {
|
|
|
|
t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys)+1, len(plugins))
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify the first plugin is the one we just created.
|
|
|
|
if !reflect.DeepEqual(plugins[0].Name, "aaaaaaa") {
|
|
|
|
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[0], "aaaaaaa")
|
|
|
|
}
|
|
|
|
if plugins[0].SemanticVersion == nil {
|
|
|
|
t.Fatalf("expected non-nil semantic version for %v", plugins[0].Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify the builtin plugins are correct
|
|
|
|
for i, plugin := range plugins[1:] {
|
|
|
|
if plugin.Name != builtinKeys[i] {
|
|
|
|
t.Fatalf("expected plugin list with names %v but got %+v", builtinKeys, plugins)
|
|
|
|
}
|
|
|
|
switch plugin.Name {
|
|
|
|
case "mysql-database-plugin":
|
|
|
|
if plugin.Builtin {
|
|
|
|
t.Fatalf("expected %v plugin to be an unversioned external plugin", plugin)
|
|
|
|
}
|
|
|
|
if plugin.Version != "" {
|
|
|
|
t.Fatalf("expected no version information for %v but got %s", plugin, plugin.Version)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if !plugin.Builtin {
|
|
|
|
t.Fatalf("expected %v plugin to be builtin", plugin)
|
|
|
|
}
|
2022-11-23 18:36:25 +00:00
|
|
|
if !versions.IsBuiltinVersion(plugin.Version) {
|
2022-08-25 20:31:42 +00:00
|
|
|
t.Fatalf("expected +builtin metadata but got %s", plugin.Version)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if plugin.SemanticVersion == nil {
|
|
|
|
t.Fatalf("expected non-nil semantic version for %v", plugin)
|
2017-04-12 16:40:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-08 16:33:24 +00:00
|
|
|
|
2022-09-23 19:00:10 +00:00
|
|
|
func TestPluginCatalog_ListHandlesPluginNamesWithSlashes(t *testing.T) {
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
core.pluginCatalog.directory = tempDir
|
|
|
|
|
|
|
|
file, err := ioutil.TempFile(tempDir, "temp")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
command := filepath.Base(file.Name())
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
pluginsToRegister := []pluginutil.PluginRunner{
|
|
|
|
{
|
|
|
|
Name: "unversioned-plugin",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "unversioned-plugin/with-slash",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "unversioned-plugin/with-two/slashes",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "versioned-plugin",
|
|
|
|
Version: "v1.0.0",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "versioned-plugin/with-slash",
|
|
|
|
Version: "v1.0.0",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "versioned-plugin/with-two/slashes",
|
|
|
|
Version: "v1.0.0",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, entry := range pluginsToRegister {
|
|
|
|
err = core.pluginCatalog.Set(ctx, entry.Name, consts.PluginTypeCredential, entry.Version, command, nil, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plugins, err := core.pluginCatalog.ListVersionedPlugins(ctx, consts.PluginTypeCredential)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, expected := range pluginsToRegister {
|
|
|
|
found := false
|
|
|
|
for _, plugin := range plugins {
|
|
|
|
if expected.Name == plugin.Name && expected.Version == plugin.Version {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
t.Errorf("Did not find %#v in %#v", expected, plugins)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-08 16:33:24 +00:00
|
|
|
func TestPluginCatalog_NewPluginClient(t *testing.T) {
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
2022-08-25 20:31:42 +00:00
|
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
2022-03-08 16:33:24 +00:00
|
|
|
if err != nil {
|
2022-08-25 20:31:42 +00:00
|
|
|
t.Fatal(err)
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
2022-08-25 20:31:42 +00:00
|
|
|
core.pluginCatalog.directory = tempDir
|
2022-03-08 16:33:24 +00:00
|
|
|
|
|
|
|
if extPlugins := len(core.pluginCatalog.externalPlugins); extPlugins != 0 {
|
|
|
|
t.Fatalf("expected externalPlugins map to be of len 0 but got %d", extPlugins)
|
|
|
|
}
|
|
|
|
|
|
|
|
// register plugins
|
2022-09-09 16:32:28 +00:00
|
|
|
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{}, "")
|
2022-03-08 16:33:24 +00:00
|
|
|
|
2022-09-09 16:32:28 +00:00
|
|
|
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{}, "")
|
2022-08-30 02:42:26 +00:00
|
|
|
|
2022-10-05 08:29:29 +00:00
|
|
|
getKey := func(pluginName string, pluginType consts.PluginType) externalPluginsKey {
|
|
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
plugin, err := core.pluginCatalog.Get(ctx, pluginName, pluginType, "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if plugin == nil {
|
|
|
|
t.Fatal("did not find " + pluginName)
|
|
|
|
}
|
|
|
|
key, err := makeExternalPluginsKey(plugin)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
var pluginClients []*pluginClient
|
2022-03-08 16:33:24 +00:00
|
|
|
// run plugins
|
2022-08-30 02:42:26 +00:00
|
|
|
// run "mux-postgres" twice which will start a single plugin for 2
|
|
|
|
// distinct connections
|
|
|
|
c := TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "mux-postgres")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "mux-postgres")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "single-postgres-1")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "single-postgres-2")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
|
|
|
|
// run "mux-userpass" twice which will start a single plugin for 2
|
|
|
|
// distinct connections
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "mux-userpass")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "mux-userpass")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "single-userpass-1")
|
|
|
|
pluginClients = append(pluginClients, c)
|
|
|
|
c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "single-userpass-2")
|
|
|
|
pluginClients = append(pluginClients, c)
|
2022-03-08 16:33:24 +00:00
|
|
|
|
|
|
|
externalPlugins := core.pluginCatalog.externalPlugins
|
2022-08-30 02:42:26 +00:00
|
|
|
if len(externalPlugins) != 6 {
|
|
|
|
t.Fatalf("expected externalPlugins map to be of len 6 but got %d", len(externalPlugins))
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check connections map
|
2022-10-05 08:29:29 +00:00
|
|
|
expectConnectionLen(t, 2, externalPlugins[getKey("mux-postgres", consts.PluginTypeDatabase)].connections)
|
|
|
|
expectConnectionLen(t, 1, externalPlugins[getKey("single-postgres-1", consts.PluginTypeDatabase)].connections)
|
|
|
|
expectConnectionLen(t, 1, externalPlugins[getKey("single-postgres-2", consts.PluginTypeDatabase)].connections)
|
|
|
|
expectConnectionLen(t, 2, externalPlugins[getKey("mux-userpass", consts.PluginTypeCredential)].connections)
|
|
|
|
expectConnectionLen(t, 1, externalPlugins[getKey("single-userpass-1", consts.PluginTypeCredential)].connections)
|
|
|
|
expectConnectionLen(t, 1, externalPlugins[getKey("single-userpass-2", consts.PluginTypeCredential)].connections)
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
// check multiplexing support
|
2022-10-05 08:29:29 +00:00
|
|
|
expectMultiplexingSupport(t, true, externalPlugins[getKey("mux-postgres", consts.PluginTypeDatabase)].multiplexingSupport)
|
|
|
|
expectMultiplexingSupport(t, false, externalPlugins[getKey("single-postgres-1", consts.PluginTypeDatabase)].multiplexingSupport)
|
|
|
|
expectMultiplexingSupport(t, false, externalPlugins[getKey("single-postgres-2", consts.PluginTypeDatabase)].multiplexingSupport)
|
|
|
|
expectMultiplexingSupport(t, true, externalPlugins[getKey("mux-userpass", consts.PluginTypeCredential)].multiplexingSupport)
|
|
|
|
expectMultiplexingSupport(t, false, externalPlugins[getKey("single-userpass-1", consts.PluginTypeCredential)].multiplexingSupport)
|
|
|
|
expectMultiplexingSupport(t, false, externalPlugins[getKey("single-userpass-2", consts.PluginTypeCredential)].multiplexingSupport)
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
// cleanup all of the external plugin processes
|
|
|
|
for _, client := range pluginClients {
|
|
|
|
client.Close()
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
// check that externalPlugins map is cleaned up
|
|
|
|
if len(externalPlugins) != 0 {
|
|
|
|
t.Fatalf("expected external plugin map to be of len 0 but got %d", len(externalPlugins))
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
2022-08-30 02:42:26 +00:00
|
|
|
}
|
|
|
|
|
2022-10-05 08:29:29 +00:00
|
|
|
func TestPluginCatalog_MakeExternalPluginsKey_Comparable(t *testing.T) {
|
|
|
|
var plugins []pluginutil.PluginRunner
|
|
|
|
hasher := sha256.New()
|
|
|
|
hasher.Write([]byte("Some random input"))
|
|
|
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
plugins = append(plugins, pluginutil.PluginRunner{
|
|
|
|
Name: "Name",
|
|
|
|
Type: consts.PluginTypeDatabase,
|
|
|
|
Version: "Version",
|
|
|
|
Command: "Command",
|
|
|
|
Args: []string{"Some", "Args"},
|
|
|
|
Env: []string{"Env=foo", "bar=", "baz=foo"},
|
|
|
|
Sha256: hasher.Sum(nil),
|
|
|
|
Builtin: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
var keys []externalPluginsKey
|
|
|
|
for _, plugin := range plugins {
|
|
|
|
key, err := makeExternalPluginsKey(&plugin)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
if keys[0] != keys[1] {
|
|
|
|
t.Fatal("expected equality")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
func TestPluginCatalog_PluginMain_Userpass(t *testing.T) {
|
|
|
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
|
|
|
return
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
|
|
|
flags := apiClientMeta.FlagSet()
|
|
|
|
flags.Parse(os.Args[1:])
|
|
|
|
|
|
|
|
tlsConfig := apiClientMeta.GetTLSConfig()
|
|
|
|
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
|
|
|
|
|
|
|
|
err := backendplugin.Serve(
|
|
|
|
&backendplugin.ServeOpts{
|
|
|
|
BackendFactoryFunc: userpass.Factory,
|
|
|
|
TLSProviderFunc: tlsProviderFunc,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to initialize userpass: %s", err)
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
2022-08-30 02:42:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestPluginCatalog_PluginMain_UserpassMultiplexed(t *testing.T) {
|
|
|
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
|
|
|
return
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
2022-08-30 02:42:26 +00:00
|
|
|
|
|
|
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
|
|
|
flags := apiClientMeta.FlagSet()
|
|
|
|
flags.Parse(os.Args[1:])
|
|
|
|
|
|
|
|
tlsConfig := apiClientMeta.GetTLSConfig()
|
|
|
|
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
|
|
|
|
|
|
|
|
err := backendplugin.ServeMultiplex(
|
|
|
|
&backendplugin.ServeOpts{
|
|
|
|
BackendFactoryFunc: userpass.Factory,
|
|
|
|
TLSProviderFunc: tlsProviderFunc,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to initialize userpass: %s", err)
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPluginCatalog_PluginMain_Postgres(t *testing.T) {
|
|
|
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
dbType, err := postgresql.New()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to initialize postgres: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
v5.Serve(dbType.(v5.Database))
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:31:42 +00:00
|
|
|
func TestPluginCatalog_PluginMain_PostgresMultiplexed(_ *testing.T) {
|
2022-03-08 16:33:24 +00:00
|
|
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
v5.ServeMultiplex(postgresql.New)
|
|
|
|
}
|
|
|
|
|
2022-08-30 02:42:26 +00:00
|
|
|
// expectConnectionLen asserts that the PluginCatalog's externalPlugin
|
|
|
|
// connections map has a length of expectedLen
|
|
|
|
func expectConnectionLen(t *testing.T, expectedLen int, connections map[string]*pluginClient) {
|
|
|
|
if len(connections) != expectedLen {
|
|
|
|
t.Fatalf("expected external plugin's connections map to be of len %d but got %d", expectedLen, len(connections))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func expectMultiplexingSupport(t *testing.T, expected, actual bool) {
|
|
|
|
if expected != actual {
|
|
|
|
t.Fatalf("expected external plugin multiplexing support to be %t", expected)
|
2022-03-08 16:33:24 +00:00
|
|
|
}
|
|
|
|
}
|