2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-09-09 17:14:26 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-09-21 19:25:04 +00:00
|
|
|
"encoding/hex"
|
2022-12-14 18:06:33 +00:00
|
|
|
"errors"
|
2022-09-09 17:14:26 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
2022-09-09 17:14:26 +00:00
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
2023-02-01 13:33:16 +00:00
|
|
|
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
2023-02-03 22:27:11 +00:00
|
|
|
"github.com/hashicorp/vault/helper/testhelpers/pluginhelpers"
|
2022-09-09 17:14:26 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
2022-09-21 19:25:04 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2022-10-05 08:29:29 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/plugin"
|
|
|
|
"github.com/hashicorp/vault/sdk/plugin/mock"
|
2022-12-14 18:06:33 +00:00
|
|
|
"github.com/hashicorp/vault/version"
|
2022-09-09 17:14:26 +00:00
|
|
|
)
|
|
|
|
|
2022-10-05 08:29:29 +00:00
|
|
|
const vaultTestingMockPluginEnv = "VAULT_TESTING_MOCK_PLUGIN"
|
|
|
|
|
2022-09-21 19:25:04 +00:00
|
|
|
// version is used to override the plugin's self-reported version
|
2023-02-03 22:27:11 +00:00
|
|
|
func testCoreWithPlugins(t *testing.T, typ consts.PluginType, versions ...string) (*Core, []pluginhelpers.TestPlugin) {
|
2022-09-09 17:14:26 +00:00
|
|
|
t.Helper()
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-10-05 08:29:29 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
var plugins []pluginhelpers.TestPlugin
|
2022-10-05 08:29:29 +00:00
|
|
|
for _, version := range versions {
|
2023-02-03 22:27:11 +00:00
|
|
|
plugins = append(plugins, pluginhelpers.CompilePlugin(t, typ, version, pluginDir))
|
2022-10-05 08:29:29 +00:00
|
|
|
}
|
2022-09-09 17:14:26 +00:00
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-09-09 17:14:26 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
core := TestCoreWithSealAndUI(t, conf)
|
|
|
|
core, _, _ = testCoreUnsealed(t, core)
|
2022-10-05 08:29:29 +00:00
|
|
|
return core, plugins
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalPlugin(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
routerPath string
|
|
|
|
expectedMatch string
|
|
|
|
}{
|
|
|
|
"enable external credential plugin": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2023-02-03 22:27:11 +00:00
|
|
|
coreConfig := &CoreConfig{
|
|
|
|
DisableMlock: true,
|
|
|
|
DisableCache: true,
|
|
|
|
Logger: log.NewNullLogger(),
|
|
|
|
CredentialBackends: map[string]logical.Factory{},
|
|
|
|
}
|
|
|
|
|
|
|
|
cluster := NewTestCluster(t, coreConfig, &TestClusterOptions{
|
|
|
|
Plugins: &TestPluginConfig{
|
|
|
|
Typ: tc.pluginType,
|
|
|
|
Versions: []string{""},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
cluster.Start()
|
|
|
|
defer cluster.Cleanup()
|
|
|
|
|
|
|
|
c := cluster.Cores[0].Core
|
|
|
|
TestWaitActive(t, c)
|
|
|
|
|
|
|
|
plugins := cluster.Plugins
|
|
|
|
|
|
|
|
registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), "1.0.0", plugins[0].Sha256, plugins[0].FileName)
|
2022-09-09 17:14:26 +00:00
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, "v1.0.0", "")
|
2022-09-09 17:14:26 +00:00
|
|
|
|
|
|
|
match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
|
|
|
|
if match != tc.expectedMatch {
|
|
|
|
t.Fatalf("missing mount, match: %q", match)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalPlugin_MultipleVersions(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
registerVersions []string
|
|
|
|
mountVersion string
|
2022-09-22 12:53:52 +00:00
|
|
|
expectedVersion string
|
2022-09-09 17:14:26 +00:00
|
|
|
routerPath string
|
|
|
|
expectedMatch string
|
|
|
|
}{
|
|
|
|
"enable external credential plugin, multiple versions available": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "v1.0.0",
|
2022-09-22 12:53:52 +00:00
|
|
|
expectedVersion: "v1.0.0",
|
2022-09-09 17:14:26 +00:00
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin, multiple versions available": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "v1.0.0",
|
2022-09-22 12:53:52 +00:00
|
|
|
expectedVersion: "v1.0.0",
|
2022-09-09 17:14:26 +00:00
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
"enable external credential plugin, multiple versions available, select other version": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "v1.0.1",
|
2022-09-22 12:53:52 +00:00
|
|
|
expectedVersion: "v1.0.1",
|
2022-09-09 17:14:26 +00:00
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin, multiple versions available, select other version": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "v1.0.1",
|
2022-09-22 12:53:52 +00:00
|
|
|
expectedVersion: "v1.0.1",
|
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
"enable external credential plugin, selects latest when version not specified": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "",
|
|
|
|
expectedVersion: "v1.0.1",
|
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin, selects latest when version not specified": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
registerVersions: []string{"v1.0.0", "v1.0.1"},
|
|
|
|
mountVersion: "",
|
|
|
|
expectedVersion: "v1.0.1",
|
2022-09-09 17:14:26 +00:00
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
|
2022-09-09 17:14:26 +00:00
|
|
|
for _, version := range tc.registerVersions {
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), version, plugins[0].Sha256, plugins[0].FileName)
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, tc.mountVersion, "")
|
2022-09-09 17:14:26 +00:00
|
|
|
|
|
|
|
match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
|
|
|
|
if match != tc.expectedMatch {
|
|
|
|
t.Fatalf("missing mount, match: %q", match)
|
|
|
|
}
|
|
|
|
|
|
|
|
raw, _ := c.router.root.Get(match)
|
2022-09-22 12:53:52 +00:00
|
|
|
if raw.(*routeEntry).mountEntry.Version != tc.expectedVersion {
|
|
|
|
t.Errorf("Expected mount to be version %s but got %s", tc.expectedVersion, raw.(*routeEntry).mountEntry.Version)
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
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
|
|
|
|
2022-09-23 10:19:38 +00:00
|
|
|
if raw.(*routeEntry).mountEntry.RunningVersion != tc.expectedVersion {
|
|
|
|
t.Errorf("Expected mount running version to be %s but got %s", tc.expectedVersion, raw.(*routeEntry).mountEntry.RunningVersion)
|
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
|
|
|
}
|
2022-09-21 19:25:04 +00:00
|
|
|
|
2022-09-21 20:32:00 +00:00
|
|
|
if raw.(*routeEntry).mountEntry.RunningSha256 == "" {
|
|
|
|
t.Errorf("Expected RunningSha256 to be present: %+v", raw.(*routeEntry).mountEntry.RunningSha256)
|
2022-09-21 19:25:04 +00:00
|
|
|
}
|
2022-09-09 17:14:26 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 18:16:31 +00:00
|
|
|
func TestCore_EnableExternalPlugin_Deregister_SealUnseal(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-12-02 18:16:31 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
|
|
|
// create an external plugin to shadow the builtin "pending-removal-test-plugin"
|
|
|
|
pluginName := "therug"
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
|
2022-12-02 18:16:31 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-12-02 18:16:31 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, keys, root := testCoreUnsealed(t, c)
|
|
|
|
|
|
|
|
// Register a plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
|
2022-12-02 18:16:31 +00:00
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
plugct := len(c.pluginCatalog.externalPlugins)
|
|
|
|
if plugct != 1 {
|
|
|
|
t.Fatalf("expected a single external plugin entry after registering, got: %d", plugct)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now pull the rug out from underneath us
|
|
|
|
deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", "", "")
|
|
|
|
|
|
|
|
if err := c.Seal(root); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
for i, key := range keys {
|
|
|
|
unseal, err := TestCoreUnseal(c, key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if i+1 == len(keys) && !unseal {
|
|
|
|
t.Fatalf("err: should be unsealed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plugct = len(c.pluginCatalog.externalPlugins)
|
|
|
|
if plugct != 0 {
|
|
|
|
t.Fatalf("expected no plugin entries after unseal, got: %d", plugct)
|
|
|
|
}
|
|
|
|
|
|
|
|
found := false
|
|
|
|
mounts, err := c.ListAuths()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
for _, mount := range mounts {
|
|
|
|
if mount.Type == pluginName {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
t.Fatalf("expected to find %s mount, but got none", pluginName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 18:06:33 +00:00
|
|
|
// TestCore_Unseal_isMajorVersionFirstMount_PendingRemoval_Plugin tests the
|
|
|
|
// behavior of deprecated builtins when attempting to unseal Vault after a major
|
|
|
|
// version upgrade. It simulates this behavior by instantiating a Vault cluster,
|
|
|
|
// registering a shadow plugin to mount a builtin, and deregistering the shadow
|
|
|
|
// plugin. The first unseal should work. Before sealing and unsealing again, the
|
|
|
|
// version store is cleared. Vault sees the next unseal as a major upgrade and
|
|
|
|
// should immediately shut down.
|
|
|
|
func TestCore_Unseal_isMajorVersionFirstMount_PendingRemoval_Plugin(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-12-14 18:06:33 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
|
|
|
// create an external plugin to shadow the builtin "pending-removal-test-plugin"
|
|
|
|
pluginName := "pending-removal-test-plugin"
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
|
2022-12-14 18:06:33 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-12-14 18:06:33 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, keys, root := testCoreUnsealed(t, c)
|
|
|
|
|
|
|
|
// Register a shadow plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
|
2022-12-14 18:06:33 +00:00
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
|
|
|
|
// Deregister shadow plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
|
2022-12-14 18:06:33 +00:00
|
|
|
|
|
|
|
// Make sure this isn't the first mount for the current major version.
|
|
|
|
if c.isMajorVersionFirstMount(context.Background()) {
|
|
|
|
t.Fatalf("expected major version to register as mounted")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Seal(root); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
for i, key := range keys {
|
|
|
|
unseal, err := TestCoreUnseal(c, key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if i+1 == len(keys) && !unseal {
|
|
|
|
t.Fatalf("err: should be unsealed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now remove version history and try again
|
|
|
|
vaultVersionPath := "core/versions/"
|
|
|
|
key := vaultVersionPath + version.Version
|
|
|
|
if err := c.barrier.Delete(context.Background(), key); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadVersionHistory doesn't care about invalidating old entries, since
|
|
|
|
// they shouldn't really be deleted from the version store. It just updates
|
|
|
|
// the map, so we need to manually delete the current entry.
|
|
|
|
delete(c.versionHistory, version.Version)
|
|
|
|
|
|
|
|
// Make sure this appears to be the first mount for the current major
|
|
|
|
// version.
|
|
|
|
if !c.isMajorVersionFirstMount(context.Background()) {
|
|
|
|
t.Fatalf("expected major version first mount")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seal again and check for unseal failure.
|
|
|
|
if err := c.Seal(root); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
for i, key := range keys {
|
|
|
|
unseal, err := TestCoreUnseal(c, key)
|
|
|
|
if i+1 == len(keys) {
|
|
|
|
if !errors.Is(err, errLoadAuthFailed) {
|
|
|
|
t.Fatalf("expected error: %q, got: %q", errLoadAuthFailed, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if unseal {
|
|
|
|
t.Fatalf("err: should not be unsealed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalPlugin_PendingRemoval(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-12-14 18:06:33 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
|
|
|
// create an external plugin to shadow the builtin "pending-removal-test-plugin"
|
|
|
|
pluginName := "pending-removal-test-plugin"
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
|
2022-12-14 18:06:33 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-12-14 18:06:33 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, _, _ = testCoreUnsealed(t, c)
|
|
|
|
|
|
|
|
pendingRemovalString := "pending removal"
|
|
|
|
|
|
|
|
// Create a new auth method with builtin pending-removal-test-plugin
|
|
|
|
resp, err := mountPluginWithResponse(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expected error when mounting deprecated backend")
|
|
|
|
}
|
|
|
|
if resp == nil || resp.Data == nil || !strings.Contains(resp.Data["error"].(string), pendingRemovalString) {
|
|
|
|
t.Fatalf("expected error response to contain %q but got %+v", pendingRemovalString, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register a shadow plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
|
2022-12-14 18:06:33 +00:00
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
}
|
|
|
|
|
2022-11-11 19:51:37 +00:00
|
|
|
func TestCore_EnableExternalPlugin_ShadowBuiltin(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-11-11 19:51:37 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
|
|
|
// create an external plugin to shadow the builtin "approle"
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "approle"))
|
2022-11-11 19:51:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
pluginName := "approle"
|
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-11-11 19:51:37 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, _, _ = testCoreUnsealed(t, c)
|
|
|
|
|
|
|
|
verifyAuthListDeprecationStatus := func(authName string, checkExists bool) error {
|
|
|
|
req := logical.TestRequest(t, logical.ReadOperation, mountTable(consts.PluginTypeCredential))
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": authName,
|
|
|
|
}
|
|
|
|
resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
status := resp.Data["deprecation_status"]
|
|
|
|
if checkExists && status == nil {
|
|
|
|
return fmt.Errorf("expected deprecation status but found none")
|
|
|
|
} else if !checkExists && status != nil {
|
|
|
|
return fmt.Errorf("expected nil deprecation status but found %q", status)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new auth method with builtin approle
|
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
|
|
|
|
// Read the auth table to verify deprecation status
|
|
|
|
if err := verifyAuthListDeprecationStatus(pluginName, true); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register a shadow plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
|
2022-11-11 19:51:37 +00:00
|
|
|
|
|
|
|
// Verify auth table hasn't changed
|
|
|
|
if err := verifyAuthListDeprecationStatus(pluginName, true); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remount auth method using registered shadow plugin
|
|
|
|
unmountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
|
|
|
|
// Verify auth table has changed
|
|
|
|
if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deregister shadow plugin
|
2023-02-03 22:27:11 +00:00
|
|
|
deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeSecrets.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
|
2022-11-11 19:51:37 +00:00
|
|
|
|
|
|
|
// Verify auth table hasn't changed
|
|
|
|
if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remount auth method
|
|
|
|
unmountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
|
|
|
|
|
|
|
// Verify auth table has changed
|
|
|
|
if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 20:14:37 +00:00
|
|
|
func TestCore_EnableExternalKv_MultipleVersions(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-10-05 08:29:29 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
2022-09-23 20:14:37 +00:00
|
|
|
// new kv plugin can be registered but not mounted
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeSecrets, "v1.2.3", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "kv"))
|
2022-09-23 20:14:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2022-10-05 08:29:29 +00:00
|
|
|
pluginName := "kv"
|
2022-09-23 20:14:37 +00:00
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-09-23 20:14:37 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, _, _ = testCoreUnsealed(t, c)
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeSecrets.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
|
2022-09-23 20:14:37 +00:00
|
|
|
req := logical.TestRequest(t, logical.ReadOperation, "plugins/catalog")
|
|
|
|
resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
|
|
|
t.Fatalf("%#v", resp)
|
|
|
|
}
|
|
|
|
found := false
|
2022-12-01 10:44:44 +00:00
|
|
|
for _, plugin := range resp.Data["detailed"].([]map[string]any) {
|
|
|
|
if plugin["name"] == pluginName && plugin["version"] == "v1.2.3" {
|
2022-09-23 20:14:37 +00:00
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Fatal("Expected to find v1.2.3 kv plugin but did not")
|
|
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, mountTable(consts.PluginTypeSecrets))
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": pluginName,
|
|
|
|
}
|
|
|
|
req.Data["config"] = map[string]interface{}{
|
|
|
|
"plugin_version": "v1.2.3",
|
|
|
|
}
|
|
|
|
resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() == nil {
|
|
|
|
t.Fatal("Expected resp error but got successful response")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalNoop_MultipleVersions(t *testing.T) {
|
2023-02-01 13:33:16 +00:00
|
|
|
pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
|
2022-10-05 08:29:29 +00:00
|
|
|
t.Cleanup(func() { cleanup(t) })
|
|
|
|
|
2022-09-23 20:14:37 +00:00
|
|
|
// new noop plugin can be registered but not mounted
|
2023-02-03 22:27:11 +00:00
|
|
|
plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
|
|
|
|
err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "noop"))
|
2022-09-23 20:14:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2022-10-05 08:29:29 +00:00
|
|
|
pluginName := "noop"
|
2022-09-23 20:14:37 +00:00
|
|
|
conf := &CoreConfig{
|
2023-02-01 13:33:16 +00:00
|
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
2022-09-23 20:14:37 +00:00
|
|
|
PluginDirectory: pluginDir,
|
|
|
|
}
|
|
|
|
c := TestCoreWithSealAndUI(t, conf)
|
|
|
|
c, _, _ = testCoreUnsealed(t, c)
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
|
2022-09-23 20:14:37 +00:00
|
|
|
req := logical.TestRequest(t, logical.ReadOperation, "plugins/catalog")
|
|
|
|
resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
|
|
|
t.Fatalf("%#v", resp)
|
|
|
|
}
|
|
|
|
found := false
|
2022-12-01 10:44:44 +00:00
|
|
|
for _, plugin := range resp.Data["detailed"].([]map[string]any) {
|
|
|
|
if plugin["name"] == "noop" && plugin["version"] == "v1.2.3" {
|
2022-09-23 20:14:37 +00:00
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Fatal("Expected to find v1.2.3 noop plugin but did not")
|
|
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, mountTable(consts.PluginTypeCredential))
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": pluginName,
|
|
|
|
}
|
|
|
|
req.Data["config"] = map[string]interface{}{
|
|
|
|
"plugin_version": "v1.2.3",
|
|
|
|
}
|
|
|
|
resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() == nil {
|
|
|
|
t.Fatal("Expected resp error but got successful response")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-09 17:14:26 +00:00
|
|
|
func TestCore_EnableExternalPlugin_NoVersionsOkay(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
routerPath string
|
|
|
|
expectedMatch string
|
|
|
|
}{
|
|
|
|
"enable external credential plugin with no version": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin with no version": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
|
2022-09-22 12:53:52 +00:00
|
|
|
// When an unversioned plugin is registered, mounting a plugin with no
|
|
|
|
// version specified should mount the unversioned plugin even if there
|
|
|
|
// are versioned plugins available.
|
|
|
|
for _, version := range []string{"", "v1.0.0"} {
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), version, plugins[0].Sha256, plugins[0].FileName)
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, "", "")
|
2022-09-09 17:14:26 +00:00
|
|
|
|
|
|
|
match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
|
|
|
|
if match != tc.expectedMatch {
|
|
|
|
t.Fatalf("missing mount, match: %q", match)
|
|
|
|
}
|
|
|
|
|
|
|
|
raw, _ := c.router.root.Get(match)
|
|
|
|
if raw.(*routeEntry).mountEntry.Version != "" {
|
|
|
|
t.Errorf("Expected mount to be empty version but got %s", raw.(*routeEntry).mountEntry.Version)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalCredentialPlugin_NoVersionOnRegister(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
routerPath string
|
|
|
|
expectedMatch string
|
|
|
|
}{
|
|
|
|
"enable external credential plugin with version, but no version was provided on registration": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
routerPath: "auth/foo/bar",
|
|
|
|
expectedMatch: "auth/foo/",
|
|
|
|
},
|
|
|
|
"enable external secrets plugin with version, but no version was provided on registration": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
routerPath: "foo/bar",
|
|
|
|
expectedMatch: "foo/",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), "", plugins[0].Sha256, plugins[0].FileName)
|
2022-09-09 17:14:26 +00:00
|
|
|
|
2022-09-22 12:53:52 +00:00
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, mountTable(tc.pluginType))
|
|
|
|
req.Data = map[string]interface{}{
|
2023-02-03 22:27:11 +00:00
|
|
|
"type": plugins[0].Name,
|
2022-09-22 19:55:46 +00:00
|
|
|
"config": map[string]interface{}{
|
|
|
|
"plugin_version": "v1.0.0",
|
|
|
|
},
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
2022-09-22 12:53:52 +00:00
|
|
|
resp, _ := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if resp == nil || !resp.IsError() || !strings.Contains(resp.Error().Error(), ErrPluginNotFound.Error()) {
|
|
|
|
t.Fatalf("Expected to get plugin not found but got: %v", resp.Error())
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCore_EnableExternalCredentialPlugin_InvalidName(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
}{
|
|
|
|
"enable external credential plugin with the wrong name": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
},
|
|
|
|
"enable external secrets plugin with the wrong name": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
|
2022-09-09 17:14:26 +00:00
|
|
|
d := &framework.FieldData{
|
|
|
|
Raw: map[string]interface{}{
|
2023-02-03 22:27:11 +00:00
|
|
|
"name": plugins[0].Name,
|
|
|
|
"sha256": plugins[0].Sha256,
|
2022-09-09 17:14:26 +00:00
|
|
|
"version": "v1.0.0",
|
2023-02-03 22:27:11 +00:00
|
|
|
"command": plugins[0].Name + "xyz",
|
2022-09-09 17:14:26 +00:00
|
|
|
},
|
|
|
|
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
|
|
|
|
}
|
|
|
|
_, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "no such file or directory") {
|
|
|
|
t.Fatalf("should have gotten a no such file or directory error inserting the plugin: %v", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:25:04 +00:00
|
|
|
func TestExternalPlugin_getBackendTypeVersion(t *testing.T) {
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginType consts.PluginType
|
|
|
|
setRunningVersion string
|
|
|
|
}{
|
|
|
|
"external credential plugin": {
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
setRunningVersion: "v1.2.3",
|
|
|
|
},
|
|
|
|
"external secrets plugin": {
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
setRunningVersion: "v1.2.3",
|
|
|
|
},
|
|
|
|
"external database plugin": {
|
|
|
|
pluginType: consts.PluginTypeDatabase,
|
|
|
|
setRunningVersion: "v1.2.3",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, tc.setRunningVersion)
|
2023-02-03 22:27:11 +00:00
|
|
|
registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), tc.setRunningVersion, plugins[0].Sha256, plugins[0].FileName)
|
2022-09-21 19:25:04 +00:00
|
|
|
|
2023-02-03 22:27:11 +00:00
|
|
|
shaBytes, _ := hex.DecodeString(plugins[0].Sha256)
|
|
|
|
commandFull := filepath.Join(c.pluginCatalog.directory, plugins[0].FileName)
|
2022-09-21 19:25:04 +00:00
|
|
|
entry := &pluginutil.PluginRunner{
|
2023-02-03 22:27:11 +00:00
|
|
|
Name: plugins[0].Name,
|
2022-09-21 19:25:04 +00:00
|
|
|
Command: commandFull,
|
|
|
|
Args: nil,
|
|
|
|
Sha256: shaBytes,
|
|
|
|
Builtin: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
var version logical.PluginVersion
|
2022-09-22 19:55:46 +00:00
|
|
|
var err error
|
2022-09-21 19:25:04 +00:00
|
|
|
if tc.pluginType == consts.PluginTypeDatabase {
|
|
|
|
version, err = c.pluginCatalog.getDatabaseRunningVersion(context.Background(), entry)
|
|
|
|
} else {
|
|
|
|
version, err = c.pluginCatalog.getBackendRunningVersion(context.Background(), entry)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if version.Version != tc.setRunningVersion {
|
|
|
|
t.Errorf("Expected to get version %v but got %v", tc.setRunningVersion, version.Version)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 09:33:31 +00:00
|
|
|
func TestExternalPlugin_CheckFilePermissions(t *testing.T) {
|
|
|
|
// Turn on the check.
|
|
|
|
if err := os.Setenv(consts.VaultEnableFilePermissionsCheckEnv, "true"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := os.Unsetenv(consts.VaultEnableFilePermissionsCheckEnv); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
pluginNameFmt string
|
|
|
|
pluginType consts.PluginType
|
|
|
|
pluginVersion string
|
|
|
|
}{
|
|
|
|
"plugin name and file name match": {
|
|
|
|
pluginNameFmt: "%s",
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
},
|
|
|
|
"plugin name and file name mismatch": {
|
|
|
|
pluginNameFmt: "%s-foo",
|
|
|
|
pluginType: consts.PluginTypeSecrets,
|
|
|
|
},
|
|
|
|
"plugin name has slash": {
|
|
|
|
pluginNameFmt: "%s/foo",
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
},
|
|
|
|
"plugin with version": {
|
|
|
|
pluginNameFmt: "%s/foo",
|
|
|
|
pluginType: consts.PluginTypeCredential,
|
|
|
|
pluginVersion: "v1.2.3",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2022-10-05 08:29:29 +00:00
|
|
|
c, plugins := testCoreWithPlugins(t, tc.pluginType, tc.pluginVersion)
|
2023-02-03 22:27:11 +00:00
|
|
|
registeredPluginName := fmt.Sprintf(tc.pluginNameFmt, plugins[0].Name)
|
2022-09-30 09:33:31 +00:00
|
|
|
|
|
|
|
// Permissions will be checked once during registration.
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("plugins/catalog/%s/%s", tc.pluginType.String(), registeredPluginName))
|
|
|
|
req.Data = map[string]interface{}{
|
2023-02-03 22:27:11 +00:00
|
|
|
"command": plugins[0].FileName,
|
|
|
|
"sha256": plugins[0].Sha256,
|
2022-09-30 09:33:31 +00:00
|
|
|
"version": tc.pluginVersion,
|
|
|
|
}
|
|
|
|
resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
|
|
|
t.Fatal(resp.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now attempt to mount the plugin, which should trigger checking the permissions again.
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, mountTable(tc.pluginType))
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": registeredPluginName,
|
|
|
|
}
|
|
|
|
if tc.pluginVersion != "" {
|
|
|
|
req.Data["config"] = map[string]interface{}{
|
|
|
|
"plugin_version": tc.pluginVersion,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
|
|
|
t.Fatal(resp.Error())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-05 08:29:29 +00:00
|
|
|
func TestExternalPlugin_DifferentVersionsAndArgs_AreNotMultiplexed(t *testing.T) {
|
|
|
|
env := []string{fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv)}
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
|
|
|
|
for i, tc := range []struct {
|
|
|
|
version string
|
|
|
|
testName string
|
|
|
|
}{
|
|
|
|
{"v1.2.3", "TestBackend_PluginMain_Multiplexed_Logical_v123"},
|
|
|
|
{"v1.2.4", "TestBackend_PluginMain_Multiplexed_Logical_v124"},
|
|
|
|
} {
|
|
|
|
// Register and mount plugins.
|
|
|
|
TestAddTestPlugin(t, core, "mux-secret", consts.PluginTypeSecrets, tc.version, tc.testName, env, "")
|
|
|
|
mountPlugin(t, core.systemBackend, "mux-secret", consts.PluginTypeSecrets, tc.version, fmt.Sprintf("foo%d", i))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(core.pluginCatalog.externalPlugins) != 2 {
|
|
|
|
t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExternalPlugin_DifferentTypes_AreNotMultiplexed(t *testing.T) {
|
|
|
|
const version = "v1.2.3"
|
|
|
|
env := []string{fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv)}
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
|
|
|
|
// Register and mount plugins.
|
|
|
|
TestAddTestPlugin(t, core, "mux-aws", consts.PluginTypeSecrets, version, "TestBackend_PluginMain_Multiplexed_Logical_v123", env, "")
|
|
|
|
TestAddTestPlugin(t, core, "mux-aws", consts.PluginTypeCredential, version, "TestBackend_PluginMain_Multiplexed_Credential_v123", env, "")
|
|
|
|
|
|
|
|
mountPlugin(t, core.systemBackend, "mux-aws", consts.PluginTypeSecrets, version, "")
|
|
|
|
mountPlugin(t, core.systemBackend, "mux-aws", consts.PluginTypeCredential, version, "")
|
|
|
|
|
|
|
|
if len(core.pluginCatalog.externalPlugins) != 2 {
|
|
|
|
t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExternalPlugin_DifferentEnv_AreNotMultiplexed(t *testing.T) {
|
|
|
|
const version = "v1.2.3"
|
|
|
|
baseEnv := []string{
|
|
|
|
fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv),
|
|
|
|
}
|
|
|
|
alteredEnv := []string{
|
|
|
|
fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv),
|
|
|
|
"FOO=BAR",
|
|
|
|
}
|
|
|
|
|
|
|
|
core, _, _ := TestCoreUnsealed(t)
|
|
|
|
|
|
|
|
// Register and mount plugins.
|
|
|
|
for i, env := range [][]string{baseEnv, alteredEnv} {
|
|
|
|
TestAddTestPlugin(t, core, "mux-secret", consts.PluginTypeSecrets, version, "TestBackend_PluginMain_Multiplexed_Logical_v123", env, "")
|
|
|
|
mountPlugin(t, core.systemBackend, "mux-secret", consts.PluginTypeSecrets, version, fmt.Sprintf("foo%d", i))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(core.pluginCatalog.externalPlugins) != 2 {
|
|
|
|
t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to run a mock multiplexed secrets plugin
|
|
|
|
func TestBackend_PluginMain_Multiplexed_Logical_v123(t *testing.T) {
|
|
|
|
if os.Getenv(vaultTestingMockPluginEnv) == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Setenv(mock.MockPluginVersionEnv, "v1.2.3")
|
|
|
|
|
|
|
|
err := plugin.ServeMultiplex(&plugin.ServeOpts{
|
|
|
|
BackendFactoryFunc: mock.FactoryType(logical.TypeLogical),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to run a mock multiplexed secrets plugin
|
|
|
|
func TestBackend_PluginMain_Multiplexed_Logical_v124(t *testing.T) {
|
|
|
|
if os.Getenv(vaultTestingMockPluginEnv) == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Setenv(mock.MockPluginVersionEnv, "v1.2.4")
|
|
|
|
|
|
|
|
err := plugin.ServeMultiplex(&plugin.ServeOpts{
|
|
|
|
BackendFactoryFunc: mock.FactoryType(logical.TypeLogical),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to run a mock multiplexed auth plugin
|
|
|
|
func TestBackend_PluginMain_Multiplexed_Credential_v123(t *testing.T) {
|
|
|
|
if os.Getenv(vaultTestingMockPluginEnv) == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Setenv(mock.MockPluginVersionEnv, "v1.2.3")
|
|
|
|
|
|
|
|
err := plugin.ServeMultiplex(&plugin.ServeOpts{
|
|
|
|
BackendFactoryFunc: mock.FactoryType(logical.TypeCredential),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, command string) {
|
2022-09-22 12:53:52 +00:00
|
|
|
t.Helper()
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("plugins/catalog/%s/%s", pluginType, pluginName))
|
|
|
|
req.Data = map[string]interface{}{
|
2022-10-05 08:29:29 +00:00
|
|
|
"command": command,
|
2022-09-22 12:53:52 +00:00
|
|
|
"sha256": sha,
|
|
|
|
"version": version,
|
|
|
|
}
|
|
|
|
resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
|
2022-10-05 08:29:29 +00:00
|
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 18:06:33 +00:00
|
|
|
func mountPluginWithResponse(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) (*logical.Response, error) {
|
2022-09-22 12:53:52 +00:00
|
|
|
t.Helper()
|
2022-10-05 08:29:29 +00:00
|
|
|
var mountPath string
|
|
|
|
if path == "" {
|
|
|
|
mountPath = mountTable(pluginType)
|
|
|
|
} else {
|
|
|
|
mountPath = mountTableWithPath(consts.PluginTypeSecrets, path)
|
|
|
|
}
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, mountPath)
|
2022-09-22 12:53:52 +00:00
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": pluginName,
|
|
|
|
}
|
|
|
|
if version != "" {
|
2022-09-22 19:55:46 +00:00
|
|
|
req.Data["config"] = map[string]interface{}{
|
|
|
|
"plugin_version": version,
|
|
|
|
}
|
2022-09-22 12:53:52 +00:00
|
|
|
}
|
2022-12-14 18:06:33 +00:00
|
|
|
return sys.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mountPlugin(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) {
|
|
|
|
t.Helper()
|
|
|
|
resp, err := mountPluginWithResponse(t, sys, pluginName, pluginType, version, path)
|
2022-10-05 08:29:29 +00:00
|
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
2022-09-22 12:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-11 19:51:37 +00:00
|
|
|
func unmountPlugin(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) {
|
|
|
|
t.Helper()
|
|
|
|
var mountPath string
|
|
|
|
if path == "" {
|
|
|
|
mountPath = mountTable(pluginType)
|
|
|
|
} else {
|
|
|
|
mountPath = mountTableWithPath(consts.PluginTypeSecrets, path)
|
|
|
|
}
|
|
|
|
req := logical.TestRequest(t, logical.DeleteOperation, mountPath)
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"type": pluginName,
|
|
|
|
}
|
|
|
|
if version != "" {
|
|
|
|
req.Data["config"] = map[string]interface{}{
|
|
|
|
"plugin_version": version,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func deregisterPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, command string) {
|
|
|
|
t.Helper()
|
|
|
|
req := logical.TestRequest(t, logical.DeleteOperation, fmt.Sprintf("plugins/catalog/%s/%s", pluginType, pluginName))
|
|
|
|
req.Data = map[string]interface{}{
|
|
|
|
"command": command,
|
|
|
|
"sha256": sha,
|
|
|
|
"version": version,
|
|
|
|
}
|
|
|
|
resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
|
|
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 12:53:52 +00:00
|
|
|
func mountTable(pluginType consts.PluginType) string {
|
2022-10-05 08:29:29 +00:00
|
|
|
return mountTableWithPath(pluginType, "foo")
|
|
|
|
}
|
|
|
|
|
|
|
|
func mountTableWithPath(pluginType consts.PluginType, path string) string {
|
2022-09-09 17:14:26 +00:00
|
|
|
switch pluginType {
|
|
|
|
case consts.PluginTypeCredential:
|
2022-10-05 08:29:29 +00:00
|
|
|
return "auth/" + path
|
2022-09-09 17:14:26 +00:00
|
|
|
case consts.PluginTypeSecrets:
|
2022-10-05 08:29:29 +00:00
|
|
|
return "mounts/" + path
|
2022-09-09 17:14:26 +00:00
|
|
|
default:
|
2022-09-30 09:33:31 +00:00
|
|
|
panic("test does not support mounting plugin type yet: " + pluginType.String())
|
2022-09-09 17:14:26 +00:00
|
|
|
}
|
|
|
|
}
|