// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 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_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) } }