1074 lines
28 KiB
Go
1074 lines
28 KiB
Go
|
package plugin_test
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/hashicorp/vault/api"
|
||
|
"github.com/hashicorp/vault/builtin/plugin"
|
||
|
"github.com/hashicorp/vault/helper/namespace"
|
||
|
vaulthttp "github.com/hashicorp/vault/http"
|
||
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
||
|
"github.com/hashicorp/vault/sdk/logical"
|
||
|
lplugin "github.com/hashicorp/vault/sdk/plugin"
|
||
|
"github.com/hashicorp/vault/sdk/plugin/mock"
|
||
|
"github.com/hashicorp/vault/vault"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
expectedEnvKey = "FOO"
|
||
|
expectedEnvValue = "BAR"
|
||
|
)
|
||
|
|
||
|
// logicalVersionMap is a map of version to test plugin
|
||
|
var logicalVersionMap = map[string]string{
|
||
|
"v4": "TestBackend_PluginMain_V4_Logical",
|
||
|
"v5": "TestBackend_PluginMainLogical",
|
||
|
"v5_multiplexed": "TestBackend_PluginMain_Multiplexed_Logical",
|
||
|
}
|
||
|
|
||
|
// credentialVersionMap is a map of version to test plugin
|
||
|
var credentialVersionMap = map[string]string{
|
||
|
"v4": "TestBackend_PluginMain_V4_Credentials",
|
||
|
"v5": "TestBackend_PluginMainCredentials",
|
||
|
"v5_multiplexed": "TestBackend_PluginMain_Multiplexed_Credentials",
|
||
|
}
|
||
|
|
||
|
var testCtx = context.TODO()
|
||
|
|
||
|
func TestSystemBackend_Plugin_secret(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Make a request to lazy load the plugin
|
||
|
req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Unseal the cluster
|
||
|
barrierKeys := cluster.BarrierKeys
|
||
|
for _, core := range cluster.Cores {
|
||
|
for _, key := range barrierKeys {
|
||
|
_, err := core.Unseal(vault.TestKeyCopy(key))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if core.Sealed() {
|
||
|
t.Fatal("should not be sealed")
|
||
|
}
|
||
|
// Wait for active so post-unseal takes place
|
||
|
// If it fails, it means unseal process failed
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_auth(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeCredential, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Make a request to lazy load the plugin
|
||
|
req := logical.TestRequest(t, logical.ReadOperation, "auth/mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Unseal the cluster
|
||
|
barrierKeys := cluster.BarrierKeys
|
||
|
for _, core := range cluster.Cores {
|
||
|
for _, key := range barrierKeys {
|
||
|
_, err := core.Unseal(vault.TestKeyCopy(key))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if core.Sealed() {
|
||
|
t.Fatal("should not be sealed")
|
||
|
}
|
||
|
// Wait for active so post-unseal takes place
|
||
|
// If it fails, it means unseal process failed
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_MissingBinary(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Make a request to lazy load the plugin
|
||
|
req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Simulate removal of the plugin binary. Use os.Args to determine file name
|
||
|
// since that's how we create the file for catalog registration in the test
|
||
|
// helper.
|
||
|
pluginFileName := filepath.Base(os.Args[0])
|
||
|
err = os.Remove(filepath.Join(cluster.TempDir, pluginFileName))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
// Unseal the cluster
|
||
|
cluster.UnsealCores(t)
|
||
|
|
||
|
// Make a request against on tune after it is removed
|
||
|
req = logical.TestRequest(t, logical.ReadOperation, "sys/mounts/mock-0/tune")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
_, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err == nil {
|
||
|
t.Fatalf("expected error")
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_MismatchType(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Add a credential backend with the same name
|
||
|
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
|
||
|
req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
_, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("adding a same-named plugin of a different type should be no problem: %s", err)
|
||
|
}
|
||
|
|
||
|
// Sleep a bit before cleanup is called
|
||
|
time.Sleep(1 * time.Second)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_CatalogRemoved(t *testing.T) {
|
||
|
t.Run("secret", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_CatalogRemoved(t, logical.TypeLogical, false, logicalVersionMap)
|
||
|
})
|
||
|
|
||
|
t.Run("auth", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_CatalogRemoved(t, logical.TypeCredential, false, credentialVersionMap)
|
||
|
})
|
||
|
|
||
|
t.Run("secret-mount-existing", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_CatalogRemoved(t, logical.TypeLogical, true, logicalVersionMap)
|
||
|
})
|
||
|
|
||
|
t.Run("auth-mount-existing", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_CatalogRemoved(t, logical.TypeCredential, true, credentialVersionMap)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMount bool, versionMap map[string]string) {
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Remove the plugin from the catalog
|
||
|
req := logical.TestRequest(t, logical.DeleteOperation, "sys/plugins/catalog/database/mock-plugin")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||
|
}
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Unseal the cluster
|
||
|
barrierKeys := cluster.BarrierKeys
|
||
|
for _, core := range cluster.Cores {
|
||
|
for _, key := range barrierKeys {
|
||
|
_, err := core.Unseal(vault.TestKeyCopy(key))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if core.Sealed() {
|
||
|
t.Fatal("should not be sealed")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Wait for active so post-unseal takes place
|
||
|
// If it fails, it means unseal process failed
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
|
||
|
if testMount {
|
||
|
// Mount the plugin at the same path after plugin is re-added to the catalog
|
||
|
// and expect an error due to existing path.
|
||
|
var err error
|
||
|
switch btype {
|
||
|
case logical.TypeLogical:
|
||
|
// Add plugin back to the catalog
|
||
|
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{}, "")
|
||
|
_, err = core.Client.Logical().Write("sys/auth/mock-0", map[string]interface{}{
|
||
|
"type": "test",
|
||
|
})
|
||
|
}
|
||
|
if err == nil {
|
||
|
t.Fatal("expected error when mounting on existing path")
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_continueOnError(t *testing.T) {
|
||
|
t.Run("secret", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
t.Run("sha256_mismatch", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeLogical, true, "mock-plugin", consts.PluginTypeSecrets)
|
||
|
})
|
||
|
|
||
|
t.Run("missing_plugin", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeLogical, false, "mock-plugin", consts.PluginTypeSecrets)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.Run("auth", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
t.Run("sha256_mismatch", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeCredential, true, "mock-plugin", consts.PluginTypeCredential)
|
||
|
})
|
||
|
|
||
|
t.Run("missing_plugin", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeCredential, false, "mock-plugin", consts.PluginTypeCredential)
|
||
|
})
|
||
|
|
||
|
t.Run("sha256_mismatch", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeCredential, true, "oidc", consts.PluginTypeCredential)
|
||
|
})
|
||
|
|
||
|
t.Run("missing_plugin", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testPlugin_continueOnError(t, logical.TypeCredential, false, "oidc", consts.PluginTypeCredential)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatch bool, mountPoint string, pluginType consts.PluginType) {
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, btype, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Get the registered plugin
|
||
|
req := logical.TestRequest(t, logical.ReadOperation, fmt.Sprintf("sys/plugins/catalog/%s/mock-plugin", pluginType))
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil || resp == nil || (resp != nil && resp.IsError()) {
|
||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||
|
}
|
||
|
|
||
|
command, ok := resp.Data["command"].(string)
|
||
|
if !ok || command == "" {
|
||
|
t.Fatal("invalid command")
|
||
|
}
|
||
|
|
||
|
// Trigger a sha256 mismatch or missing plugin error
|
||
|
if mismatch {
|
||
|
req = logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("sys/plugins/catalog/%s/mock-plugin", pluginType))
|
||
|
req.Data = map[string]interface{}{
|
||
|
"sha256": "d17bd7334758e53e6fbab15745d2520765c06e296f2ce8e25b7919effa0ac216",
|
||
|
"command": filepath.Base(command),
|
||
|
}
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||
|
}
|
||
|
} else {
|
||
|
err := os.Remove(filepath.Join(cluster.TempDir, filepath.Base(command)))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Unseal the cluster
|
||
|
barrierKeys := cluster.BarrierKeys
|
||
|
for _, core := range cluster.Cores {
|
||
|
for _, key := range barrierKeys {
|
||
|
_, err := core.Unseal(vault.TestKeyCopy(key))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if core.Sealed() {
|
||
|
t.Fatal("should not be sealed")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Wait for active so post-unseal takes place
|
||
|
// If it fails, it means unseal process failed
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
|
||
|
env := []string{pluginutil.PluginCACertPEMEnv + "=" + cluster.CACertPEMFile}
|
||
|
// Re-add the plugin to the catalog
|
||
|
switch btype {
|
||
|
case logical.TypeLogical:
|
||
|
plugin := logicalVersionMap[tc.pluginVersion]
|
||
|
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", plugin, env, cluster.TempDir)
|
||
|
case logical.TypeCredential:
|
||
|
plugin := credentialVersionMap[tc.pluginVersion]
|
||
|
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", plugin, env, cluster.TempDir)
|
||
|
}
|
||
|
|
||
|
// Reload the plugin
|
||
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/plugins/reload/backend")
|
||
|
req.Data = map[string]interface{}{
|
||
|
"plugin": "mock-plugin",
|
||
|
}
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||
|
}
|
||
|
|
||
|
// Make a request to lazy load the plugin
|
||
|
var reqPath string
|
||
|
switch btype {
|
||
|
case logical.TypeLogical:
|
||
|
reqPath = "mock-0/internal"
|
||
|
case logical.TypeCredential:
|
||
|
reqPath = "auth/mock-0/internal"
|
||
|
}
|
||
|
|
||
|
req = logical.TestRequest(t, logical.ReadOperation, reqPath)
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_autoReload(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
|
||
|
// Update internal value
|
||
|
req := logical.TestRequest(t, logical.UpdateOperation, "mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
req.Data["value"] = "baz"
|
||
|
resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp != nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
|
||
|
// Call errors/rpc endpoint to trigger reload
|
||
|
req = logical.TestRequest(t, logical.ReadOperation, "mock-0/errors/rpc")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
_, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err == nil {
|
||
|
t.Fatalf("expected error from error/rpc request")
|
||
|
}
|
||
|
|
||
|
// Check internal value to make sure it's reset
|
||
|
req = logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
|
||
|
req.ClientToken = core.Client.Token()
|
||
|
resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
if resp.Data["value"].(string) == "baz" {
|
||
|
t.Fatal("did not expect backend internal value to be 'baz'")
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_SealUnseal(t *testing.T) {
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
// Seal the cluster
|
||
|
cluster.EnsureCoresSealed(t)
|
||
|
|
||
|
// Unseal the cluster
|
||
|
barrierKeys := cluster.BarrierKeys
|
||
|
for _, core := range cluster.Cores {
|
||
|
for _, key := range barrierKeys {
|
||
|
_, err := core.Unseal(vault.TestKeyCopy(key))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if core.Sealed() {
|
||
|
t.Fatal("should not be sealed")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Wait for active so post-unseal takes place
|
||
|
// If it fails, it means unseal process failed
|
||
|
vault.TestWaitActive(t, cluster.Cores[0].Core)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_reload(t *testing.T) {
|
||
|
testCases := []struct {
|
||
|
name string
|
||
|
backendType logical.BackendType
|
||
|
data map[string]interface{}
|
||
|
}{
|
||
|
{
|
||
|
name: "test plugin reload for type credential",
|
||
|
backendType: logical.TypeCredential,
|
||
|
data: map[string]interface{}{
|
||
|
"plugin": "mock-plugin",
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "test mount reload for type credential",
|
||
|
backendType: logical.TypeCredential,
|
||
|
data: map[string]interface{}{
|
||
|
"mounts": "sys/auth/mock-0/,auth/mock-1/",
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "test plugin reload for type secret",
|
||
|
backendType: logical.TypeLogical,
|
||
|
data: map[string]interface{}{
|
||
|
"plugin": "mock-plugin",
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "test mount reload for type secret",
|
||
|
backendType: logical.TypeLogical,
|
||
|
data: map[string]interface{}{
|
||
|
"mounts": "mock-0/,mock-1",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testSystemBackend_PluginReload(t, tc.data, tc.backendType)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper func to test different reload methods on plugin reload endpoint
|
||
|
func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}, backendType logical.BackendType) {
|
||
|
testCases := []struct {
|
||
|
pluginVersion string
|
||
|
}{
|
||
|
{
|
||
|
pluginVersion: "v5_multiplexed",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v5",
|
||
|
},
|
||
|
{
|
||
|
pluginVersion: "v4",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.pluginVersion, func(t *testing.T) {
|
||
|
cluster := testSystemBackendMock(t, 1, 2, backendType, tc.pluginVersion)
|
||
|
defer cluster.Cleanup()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
client := core.Client
|
||
|
|
||
|
pathPrefix := "mock-"
|
||
|
if backendType == logical.TypeCredential {
|
||
|
pathPrefix = "auth/" + pathPrefix
|
||
|
}
|
||
|
for i := 0; i < 2; i++ {
|
||
|
// Update internal value in the backend
|
||
|
resp, err := client.Logical().Write(fmt.Sprintf("%s%d/internal", pathPrefix, i), map[string]interface{}{
|
||
|
"value": "baz",
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp != nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Perform plugin reload
|
||
|
resp, err := client.Logical().Write("sys/plugins/reload/backend", reqData)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
if resp.Data["reload_id"] == nil {
|
||
|
t.Fatal("no reload_id in response")
|
||
|
}
|
||
|
|
||
|
for i := 0; i < 2; i++ {
|
||
|
// Ensure internal backed value is reset
|
||
|
resp, err := client.Logical().Read(fmt.Sprintf("%s%d/internal", pathPrefix, i))
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp == nil {
|
||
|
t.Fatalf("bad: response should not be nil")
|
||
|
}
|
||
|
if resp.Data["value"].(string) == "baz" {
|
||
|
t.Fatal("did not expect backend internal value to be 'baz'")
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// testSystemBackendMock returns a systemBackend with the desired number
|
||
|
// of mounted mock plugin backends. numMounts alternates between different
|
||
|
// ways of providing the plugin_name.
|
||
|
//
|
||
|
// The mounts are mounted at sys/mounts/mock-[numMounts] or sys/auth/mock-[numMounts]
|
||
|
func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType logical.BackendType, pluginVersion string) *vault.TestCluster {
|
||
|
coreConfig := &vault.CoreConfig{
|
||
|
LogicalBackends: map[string]logical.Factory{
|
||
|
"plugin": plugin.Factory,
|
||
|
},
|
||
|
CredentialBackends: map[string]logical.Factory{
|
||
|
"plugin": plugin.Factory,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Create a tempdir, cluster.Cleanup will clean up this directory
|
||
|
tempDir, err := ioutil.TempDir("", "vault-test-cluster")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||
|
HandlerFunc: vaulthttp.Handler,
|
||
|
KeepStandbysSealed: true,
|
||
|
NumCores: numCores,
|
||
|
TempDir: tempDir,
|
||
|
})
|
||
|
cluster.Start()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
client := core.Client
|
||
|
|
||
|
env := []string{pluginutil.PluginCACertPEMEnv + "=" + cluster.CACertPEMFile}
|
||
|
|
||
|
switch backendType {
|
||
|
case logical.TypeLogical:
|
||
|
plugin := logicalVersionMap[pluginVersion]
|
||
|
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", plugin, env, tempDir)
|
||
|
for i := 0; i < numMounts; i++ {
|
||
|
// Alternate input styles for plugin_name on every other mount
|
||
|
options := map[string]interface{}{
|
||
|
"type": "mock-plugin",
|
||
|
}
|
||
|
resp, err := client.Logical().Write(fmt.Sprintf("sys/mounts/mock-%d", i), options)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp != nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
}
|
||
|
case logical.TypeCredential:
|
||
|
plugin := credentialVersionMap[pluginVersion]
|
||
|
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", plugin, env, tempDir)
|
||
|
for i := 0; i < numMounts; i++ {
|
||
|
// Alternate input styles for plugin_name on every other mount
|
||
|
options := map[string]interface{}{
|
||
|
"type": "mock-plugin",
|
||
|
}
|
||
|
resp, err := client.Logical().Write(fmt.Sprintf("sys/auth/mock-%d", i), options)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp != nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
t.Fatal("unknown backend type provided")
|
||
|
}
|
||
|
|
||
|
return cluster
|
||
|
}
|
||
|
|
||
|
func TestSystemBackend_Plugin_Env(t *testing.T) {
|
||
|
kvPair := fmt.Sprintf("%s=%s", expectedEnvKey, expectedEnvValue)
|
||
|
cluster := testSystemBackend_SingleCluster_Env(t, []string{kvPair})
|
||
|
defer cluster.Cleanup()
|
||
|
}
|
||
|
|
||
|
// testSystemBackend_SingleCluster_Env is a helper func that returns a single
|
||
|
// cluster and a single mounted plugin logical backend.
|
||
|
func testSystemBackend_SingleCluster_Env(t *testing.T, env []string) *vault.TestCluster {
|
||
|
coreConfig := &vault.CoreConfig{
|
||
|
LogicalBackends: map[string]logical.Factory{
|
||
|
"test": plugin.Factory,
|
||
|
},
|
||
|
}
|
||
|
// Create a tempdir, cluster.Cleanup will clean up this directory
|
||
|
tempDir, err := ioutil.TempDir("", "vault-test-cluster")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||
|
HandlerFunc: vaulthttp.Handler,
|
||
|
KeepStandbysSealed: true,
|
||
|
NumCores: 1,
|
||
|
TempDir: tempDir,
|
||
|
})
|
||
|
cluster.Start()
|
||
|
|
||
|
core := cluster.Cores[0]
|
||
|
vault.TestWaitActive(t, core.Core)
|
||
|
client := core.Client
|
||
|
|
||
|
env = append([]string{pluginutil.PluginCACertPEMEnv + "=" + cluster.CACertPEMFile}, env...)
|
||
|
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestBackend_PluginMainEnv", env, tempDir)
|
||
|
options := map[string]interface{}{
|
||
|
"type": "mock-plugin",
|
||
|
}
|
||
|
|
||
|
resp, err := client.Logical().Write("sys/mounts/mock", options)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
if resp != nil {
|
||
|
t.Fatalf("bad: %v", resp)
|
||
|
}
|
||
|
|
||
|
return cluster
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMain_V4_Logical(t *testing.T) {
|
||
|
args := []string{}
|
||
|
// don't run as a standalone unit test
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// don't run as a V5 plugin
|
||
|
if os.Getenv(pluginutil.PluginAutoMTLSEnv) == "true" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
// V4 does not support AutoMTLS so we set a TLSConfig via TLSProviderFunc
|
||
|
tlsConfig := apiClientMeta.GetTLSConfig()
|
||
|
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeLogical)
|
||
|
|
||
|
err := lplugin.Serve(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
TLSProviderFunc: tlsProviderFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMain_Multiplexed_Logical(t *testing.T) {
|
||
|
args := []string{}
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeLogical)
|
||
|
|
||
|
err := lplugin.ServeMultiplex(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMainLogical(t *testing.T) {
|
||
|
args := []string{}
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeLogical)
|
||
|
|
||
|
err := lplugin.Serve(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMain_V4_Credentials(t *testing.T) {
|
||
|
args := []string{}
|
||
|
// don't run as a standalone unit test
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// don't run as a V5 plugin
|
||
|
if os.Getenv(pluginutil.PluginAutoMTLSEnv) == "true" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
// V4 does not support AutoMTLS so we set a TLSConfig via TLSProviderFunc
|
||
|
tlsConfig := apiClientMeta.GetTLSConfig()
|
||
|
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeCredential)
|
||
|
|
||
|
err := lplugin.Serve(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
TLSProviderFunc: tlsProviderFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMain_Multiplexed_Credentials(t *testing.T) {
|
||
|
args := []string{}
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeCredential)
|
||
|
|
||
|
err := lplugin.ServeMultiplex(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBackend_PluginMainCredentials(t *testing.T) {
|
||
|
args := []string{}
|
||
|
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeCredential)
|
||
|
|
||
|
err := lplugin.Serve(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TestBackend_PluginMainEnv is a mock plugin that simply checks for the existence of FOO env var.
|
||
|
func TestBackend_PluginMainEnv(t *testing.T) {
|
||
|
args := []string{}
|
||
|
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" && os.Getenv(pluginutil.PluginMetadataModeEnv) != "true" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Check on actual vs expected env var
|
||
|
actual := os.Getenv(expectedEnvKey)
|
||
|
if actual != expectedEnvValue {
|
||
|
t.Fatalf("expected: %q, got: %q", expectedEnvValue, actual)
|
||
|
}
|
||
|
|
||
|
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||
|
if caPEM == "" {
|
||
|
t.Fatal("CA cert not passed in")
|
||
|
}
|
||
|
args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
|
||
|
|
||
|
apiClientMeta := &api.PluginAPIClientMeta{}
|
||
|
flags := apiClientMeta.FlagSet()
|
||
|
flags.Parse(args)
|
||
|
|
||
|
factoryFunc := mock.FactoryType(logical.TypeLogical)
|
||
|
|
||
|
err := lplugin.Serve(&lplugin.ServeOpts{
|
||
|
BackendFactoryFunc: factoryFunc,
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|