AutoMTLS for secrets/auth plugins (#15671)

* use automtls for v5 secrets/auth plugins

* add automtls env guard

* start backend without metadata mode

* use PluginClientConfig for backend's NewPluginClient param

refactor

* - fix pluginutil test
- do not expect plugin to be unloaded in UT
- fix pluginutil tests --need new env var
- use require in UT
- fix lazy load test

* add changelog

* prioritize automtls; improve comments

* user multierror; refactor pluginSet for v4 unit test

* add test cases for v4 and v5 plugin versions

* remove unnecessary call to AutoMTLSSupported

* update comment on pluginSets

* use runconfig directly in sdk newpluginclient

* use automtls without metadatamode for v5 backend plugin registration

* use multierror for plugin runconfig calls

* remove some unnecessary code
This commit is contained in:
John-Michael Faircloth
2022-07-18 16:25:18 -05:00
committed by GitHub
parent ba56224a2a
commit 39bcd5c715
14 changed files with 736 additions and 435 deletions

View File

@@ -17,6 +17,10 @@ import (
) )
var ( var (
// PluginAutoMTLSEnv ensures AutoMTLS is used. This overrides setting a
// TLSProviderFunc for a plugin.
PluginAutoMTLSEnv = "VAULT_PLUGIN_AUTOMTLS"
// PluginMetadataModeEnv is an ENV name used to disable TLS communication // PluginMetadataModeEnv is an ENV name used to disable TLS communication
// to bootstrap mounting plugins. // to bootstrap mounting plugins.
PluginMetadataModeEnv = "VAULT_PLUGIN_METADATA_MODE" PluginMetadataModeEnv = "VAULT_PLUGIN_METADATA_MODE"
@@ -120,7 +124,7 @@ func VaultPluginTLSProvider(apiTLSConfig *TLSConfig) func() (*tls.Config, error)
// VaultPluginTLSProviderContext is run inside a plugin and retrieves the response // VaultPluginTLSProviderContext is run inside a plugin and retrieves the response
// wrapped TLS certificate from vault. It returns a configured TLS Config. // wrapped TLS certificate from vault. It returns a configured TLS Config.
func VaultPluginTLSProviderContext(ctx context.Context, apiTLSConfig *TLSConfig) func() (*tls.Config, error) { func VaultPluginTLSProviderContext(ctx context.Context, apiTLSConfig *TLSConfig) func() (*tls.Config, error) {
if os.Getenv(PluginMetadataModeEnv) == "true" { if os.Getenv(PluginAutoMTLSEnv) == "true" || os.Getenv(PluginMetadataModeEnv) == "true" {
return nil return nil
} }

View File

@@ -9,6 +9,7 @@ import (
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid" uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
@@ -51,17 +52,32 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*PluginBackend,
sys := conf.System sys := conf.System
// NewBackend with isMetadataMode set to true merr := &multierror.Error{}
raw, err := bplugin.NewBackend(ctx, name, pluginType, sys, conf, true) // NewBackend with isMetadataMode set to false
raw, err := bplugin.NewBackend(ctx, name, pluginType, sys, conf, false, true)
if err != nil { if err != nil {
return nil, err merr = multierror.Append(merr, err)
// NewBackend with isMetadataMode set to true
raw, err = bplugin.NewBackend(ctx, name, pluginType, sys, conf, true, false)
if err != nil {
merr = multierror.Append(merr, err)
return nil, merr
} }
} else {
b.Backend = raw
b.config = conf
b.loaded = true
b.autoMTLSSupported = true
return &b, nil
}
// Setup the backend so we can inspect the SpecialPaths and Type
err = raw.Setup(ctx, conf) err = raw.Setup(ctx, conf)
if err != nil { if err != nil {
raw.Cleanup(ctx) raw.Cleanup(ctx)
return nil, err return nil, err
} }
// Get SpecialPaths and BackendType
paths := raw.SpecialPaths() paths := raw.SpecialPaths()
btype := raw.Type() btype := raw.Type()
@@ -85,6 +101,7 @@ type PluginBackend struct {
Backend logical.Backend Backend logical.Backend
sync.RWMutex sync.RWMutex
autoMTLSSupported bool
config *logical.BackendConfig config *logical.BackendConfig
// Used to detect if we already reloaded // Used to detect if we already reloaded
@@ -105,7 +122,7 @@ func (b *PluginBackend) startBackend(ctx context.Context, storage logical.Storag
// Ensure proper cleanup of the backend (i.e. call client.Kill()) // Ensure proper cleanup of the backend (i.e. call client.Kill())
b.Backend.Cleanup(ctx) b.Backend.Cleanup(ctx)
nb, err := bplugin.NewBackend(ctx, pluginName, pluginType, b.config.System, b.config, false) nb, err := bplugin.NewBackend(ctx, pluginName, pluginType, b.config.System, b.config, false, b.autoMTLSSupported)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -59,7 +59,7 @@ func testLazyLoad(t *testing.T, methodWrapper func() error) *PluginBackend {
} }
// this is a dummy plugin that hasn't really been loaded yet // this is a dummy plugin that hasn't really been loaded yet
orig, err := plugin.NewBackend(ctx, "test-plugin", consts.PluginTypeSecrets, sysView, config, true) orig, err := plugin.NewBackend(ctx, "test-plugin", consts.PluginTypeSecrets, sysView, config, true, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

3
changelog/15671.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
plugins: Use AutoMTLS for secrets engines and auth methods run as external plugins.
```

View File

@@ -8,6 +8,10 @@ import (
) )
var ( var (
// PluginAutoMTLSEnv is used to ensure AutoMTLS is used. This will override
// setting a TLSProviderFunc for a plugin.
PluginAutoMTLSEnv = "VAULT_PLUGIN_AUTOMTLS"
// PluginMlockEnabled is the ENV name used to pass the configuration for // PluginMlockEnabled is the ENV name used to pass the configuration for
// enabling mlock // enabling mlock
PluginMlockEnabled = "VAULT_PLUGIN_MLOCK_ENABLED" PluginMlockEnabled = "VAULT_PLUGIN_MLOCK_ENABLED"

View File

@@ -9,6 +9,8 @@ import (
status "google.golang.org/grpc/status" status "google.golang.org/grpc/status"
) )
const MultiplexingCtxKey string = "multiplex_id"
type PluginMultiplexingServerImpl struct { type PluginMultiplexingServerImpl struct {
UnimplementedPluginMultiplexingServer UnimplementedPluginMultiplexingServer

View File

@@ -22,6 +22,7 @@ type PluginClientConfig struct {
IsMetadataMode bool IsMetadataMode bool
AutoMTLS bool AutoMTLS bool
MLock bool MLock bool
Wrapper RunnerUtil
} }
type runConfig struct { type runConfig struct {
@@ -33,8 +34,6 @@ type runConfig struct {
// Initialized with what's in PluginRunner.Env, but can be added to // Initialized with what's in PluginRunner.Env, but can be added to
env []string env []string
wrapper RunnerUtil
PluginClientConfig PluginClientConfig
} }
@@ -43,7 +42,7 @@ func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error
cmd.Env = append(cmd.Env, rc.env...) cmd.Env = append(cmd.Env, rc.env...)
// Add the mlock setting to the ENV of the plugin // Add the mlock setting to the ENV of the plugin
if rc.MLock || (rc.wrapper != nil && rc.wrapper.MlockEnabled()) { if rc.MLock || (rc.Wrapper != nil && rc.Wrapper.MlockEnabled()) {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginMlockEnabled, "true")) cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginMlockEnabled, "true"))
} }
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version)) cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version))
@@ -54,6 +53,9 @@ func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error
metadataEnv := fmt.Sprintf("%s=%t", PluginMetadataModeEnv, rc.IsMetadataMode) metadataEnv := fmt.Sprintf("%s=%t", PluginMetadataModeEnv, rc.IsMetadataMode)
cmd.Env = append(cmd.Env, metadataEnv) cmd.Env = append(cmd.Env, metadataEnv)
automtlsEnv := fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, rc.AutoMTLS)
cmd.Env = append(cmd.Env, automtlsEnv)
var clientTLSConfig *tls.Config var clientTLSConfig *tls.Config
if !rc.AutoMTLS && !rc.IsMetadataMode { if !rc.AutoMTLS && !rc.IsMetadataMode {
// Get a CA TLS Certificate // Get a CA TLS Certificate
@@ -70,7 +72,7 @@ func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error
// Use CA to sign a server cert and wrap the values in a response wrapped // Use CA to sign a server cert and wrap the values in a response wrapped
// token. // token.
wrapToken, err := wrapServerConfig(ctx, rc.wrapper, certBytes, key) wrapToken, err := wrapServerConfig(ctx, rc.Wrapper, certBytes, key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -120,7 +122,7 @@ func Env(env ...string) RunOpt {
func Runner(wrapper RunnerUtil) RunOpt { func Runner(wrapper RunnerUtil) RunOpt {
return func(rc *runConfig) { return func(rc *runConfig) {
rc.wrapper = wrapper rc.Wrapper = wrapper
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"os/exec" "os/exec"
"reflect"
"testing" "testing"
"time" "time"
@@ -14,6 +13,7 @@ import (
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/hashicorp/vault/sdk/helper/wrapping"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
) )
func TestMakeConfig(t *testing.T) { func TestMakeConfig(t *testing.T) {
@@ -78,6 +78,7 @@ func TestMakeConfig(t *testing.T) {
"initial=true", "initial=true",
fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version), fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version),
fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true),
fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, false),
}, },
), ),
SecureConfig: &plugin.SecureConfig{ SecureConfig: &plugin.SecureConfig{
@@ -143,6 +144,7 @@ func TestMakeConfig(t *testing.T) {
fmt.Sprintf("%s=%t", PluginMlockEnabled, true), fmt.Sprintf("%s=%t", PluginMlockEnabled, true),
fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version), fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version),
fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false),
fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, false),
fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, "testtoken"), fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, "testtoken"),
}, },
), ),
@@ -205,6 +207,7 @@ func TestMakeConfig(t *testing.T) {
"initial=true", "initial=true",
fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version), fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version),
fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true),
fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, true),
}, },
), ),
SecureConfig: &plugin.SecureConfig{ SecureConfig: &plugin.SecureConfig{
@@ -266,6 +269,7 @@ func TestMakeConfig(t *testing.T) {
"initial=true", "initial=true",
fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version), fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version),
fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false),
fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, true),
}, },
), ),
SecureConfig: &plugin.SecureConfig{ SecureConfig: &plugin.SecureConfig{
@@ -290,7 +294,7 @@ func TestMakeConfig(t *testing.T) {
Return(test.responseWrapInfo, test.responseWrapInfoErr) Return(test.responseWrapInfo, test.responseWrapInfoErr)
mockWrapper.On("MlockEnabled"). mockWrapper.On("MlockEnabled").
Return(test.mlockEnabled) Return(test.mlockEnabled)
test.rc.wrapper = mockWrapper test.rc.Wrapper = mockWrapper
defer mockWrapper.AssertNumberOfCalls(t, "ResponseWrapData", test.responseWrapInfoTimes) defer mockWrapper.AssertNumberOfCalls(t, "ResponseWrapData", test.responseWrapInfoTimes)
defer mockWrapper.AssertNumberOfCalls(t, "MlockEnabled", test.mlockEnabledTimes) defer mockWrapper.AssertNumberOfCalls(t, "MlockEnabled", test.mlockEnabledTimes)
@@ -318,9 +322,7 @@ func TestMakeConfig(t *testing.T) {
} }
config.TLSConfig = nil config.TLSConfig = nil
if !reflect.DeepEqual(config, test.expectedConfig) { require.Equal(t, config, test.expectedConfig)
t.Fatalf("Actual config: %#v\nExpected config: %#v", config, test.expectedConfig)
}
}) })
} }
} }

View File

@@ -38,8 +38,6 @@ type PluginClient interface {
plugin.ClientProtocol plugin.ClientProtocol
} }
const MultiplexingCtxKey string = "multiplex_id"
// PluginRunner defines the metadata needed to run a plugin securely with // PluginRunner defines the metadata needed to run a plugin securely with
// go-plugin. // go-plugin.
type PluginRunner struct { type PluginRunner struct {

View File

@@ -22,6 +22,7 @@ var (
type GRPCBackendPlugin struct { type GRPCBackendPlugin struct {
Factory logical.Factory Factory logical.Factory
MetadataMode bool MetadataMode bool
AutoMTLSSupported bool
Logger log.Logger Logger log.Logger
// Embeding this will disable the netRPC protocol // Embeding this will disable the netRPC protocol
@@ -46,7 +47,8 @@ func (b *GRPCBackendPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCB
broker: broker, broker: broker,
cleanupCh: make(chan struct{}), cleanupCh: make(chan struct{}),
doneCtx: ctx, doneCtx: ctx,
metadataMode: b.MetadataMode, // Only run in metadata mode if mode is true and autoMTLS is not supported
metadataMode: b.MetadataMode && !b.AutoMTLSSupported,
} }
// Create the value and set the type // Create the value and set the type

View File

@@ -7,7 +7,6 @@ import (
"sync" "sync"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil" "github.com/hashicorp/vault/sdk/helper/pluginutil"
@@ -35,7 +34,7 @@ func (b *BackendPluginClient) Cleanup(ctx context.Context) {
// external plugins, or a concrete implementation of the backend if it is a builtin backend. // external plugins, or a concrete implementation of the backend if it is a builtin backend.
// The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
// the plugin should run in metadata mode. // the plugin should run in metadata mode.
func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool) (logical.Backend, error) { func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool, autoMTLS bool) (logical.Backend, error) {
// Look for plugin in the plugin catalog // Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, pluginType) pluginRunner, err := sys.LookupPlugin(ctx, pluginName, pluginType)
if err != nil { if err != nil {
@@ -59,8 +58,16 @@ func NewBackend(ctx context.Context, pluginName string, pluginType consts.Plugin
} }
} }
} else { } else {
config := pluginutil.PluginClientConfig{
Name: pluginName,
PluginType: pluginType,
Logger: conf.Logger.Named(pluginName),
IsMetadataMode: isMetadataMode,
AutoMTLS: autoMTLS,
Wrapper: sys,
}
// create a backendPluginClient instance // create a backendPluginClient instance
backend, err = NewPluginClient(ctx, sys, pluginRunner, conf.Logger, isMetadataMode) backend, err = NewPluginClient(ctx, pluginRunner, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -69,34 +76,49 @@ func NewBackend(ctx context.Context, pluginName string, pluginType consts.Plugin
return backend, nil return backend, nil
} }
func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) { // pluginSet returns the go-plugin PluginSet that we can dispense. This ensures
// pluginMap is the map of plugins we can dispense. // that plugins that don't support AutoMTLS are run on the appropriate version.
pluginSet := map[int]plugin.PluginSet{ func pluginSet(autoMTLS, metadataMode bool) map[int]plugin.PluginSet {
if autoMTLS {
return map[int]plugin.PluginSet{
5: {
"backend": &GRPCBackendPlugin{
MetadataMode: false,
AutoMTLSSupported: true,
},
},
}
}
return map[int]plugin.PluginSet{
// Version 3 used to supports both protocols. We want to keep it around // Version 3 used to supports both protocols. We want to keep it around
// since it's possible old plugins built against this version will still // since it's possible old plugins built against this version will still
// work with gRPC. There is currently no difference between version 3 // work with gRPC. There is currently no difference between version 3
// and version 4. // and version 4.
3: { 3: {
"backend": &GRPCBackendPlugin{ "backend": &GRPCBackendPlugin{
MetadataMode: isMetadataMode, MetadataMode: metadataMode,
}, },
}, },
4: { 4: {
"backend": &GRPCBackendPlugin{ "backend": &GRPCBackendPlugin{
MetadataMode: isMetadataMode, MetadataMode: metadataMode,
}, },
}, },
} }
namedLogger := logger.Named(pluginRunner.Name)
var client *plugin.Client
var err error
if isMetadataMode {
client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginSet, handshakeConfig, []string{}, namedLogger)
} else {
client, err = pluginRunner.Run(ctx, sys, pluginSet, handshakeConfig, []string{}, namedLogger)
} }
func NewPluginClient(ctx context.Context, pluginRunner *pluginutil.PluginRunner, config pluginutil.PluginClientConfig) (logical.Backend, error) {
ps := pluginSet(config.AutoMTLS, config.IsMetadataMode)
client, err := pluginRunner.RunConfig(ctx,
pluginutil.Runner(config.Wrapper),
pluginutil.PluginSets(ps),
pluginutil.HandshakeConfig(handshakeConfig),
pluginutil.Env(),
pluginutil.Logger(config.Logger),
pluginutil.MetadataMode(config.IsMetadataMode),
pluginutil.AutoMTLS(config.AutoMTLS),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -126,9 +148,9 @@ func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunne
} }
// Wrap the backend in a tracing middleware // Wrap the backend in a tracing middleware
if namedLogger.IsTrace() { if config.Logger.IsTrace() {
backend = &backendTracingMiddleware{ backend = &backendTracingMiddleware{
logger: namedLogger.With("transport", transport), logger: config.Logger.With("transport", transport),
next: backend, next: backend,
} }
} }

View File

@@ -37,12 +37,13 @@ func Serve(opts *ServeOpts) error {
}) })
} }
// pluginMap is the map of plugins we can dispense. // pluginSets is the map of plugins we can dispense.
pluginSets := map[int]plugin.PluginSet{ pluginSets := map[int]plugin.PluginSet{
// Version 3 used to supports both protocols. We want to keep it around // Version 3 used to supports both protocols. We want to keep it around
// since it's possible old plugins built against this version will still // since it's possible old plugins built against this version will still
// work with gRPC. There is currently no difference between version 3 // work with gRPC. There is currently no difference between version 3
// and version 4. // and version 4.
// AutoMTLS is not supported by versions lower than 5.
3: { 3: {
"backend": &GRPCBackendPlugin{ "backend": &GRPCBackendPlugin{
Factory: opts.BackendFactoryFunc, Factory: opts.BackendFactoryFunc,
@@ -55,6 +56,13 @@ func Serve(opts *ServeOpts) error {
Logger: logger, Logger: logger,
}, },
}, },
5: {
"backend": &GRPCBackendPlugin{
Factory: opts.BackendFactoryFunc,
Logger: logger,
AutoMTLSSupported: true,
},
},
} }
err := pluginutil.OptionallyEnableMlock() err := pluginutil.OptionallyEnableMlock()

View File

@@ -31,8 +31,33 @@ const (
expectedEnvValue = "BAR" expectedEnvValue = "BAR"
) )
// logicalVersionMap is a map of version to test plugin
var logicalVersionMap = map[string]string{
"v4": "TestBackend_PluginMain_V4_Logical",
"v5": "TestBackend_PluginMainLogical",
}
// credentialVersionMap is a map of version to test plugin
var credentialVersionMap = map[string]string{
"v4": "TestBackend_PluginMain_V4_Credentials",
"v5": "TestBackend_PluginMainCredentials",
}
func TestSystemBackend_Plugin_secret(t *testing.T) { func TestSystemBackend_Plugin_secret(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -67,10 +92,25 @@ func TestSystemBackend_Plugin_secret(t *testing.T) {
// If it fails, it means unseal process failed // If it fails, it means unseal process failed
vault.TestWaitActive(t, core.Core) vault.TestWaitActive(t, core.Core)
} }
})
}
} }
func TestSystemBackend_Plugin_auth(t *testing.T) { func TestSystemBackend_Plugin_auth(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeCredential) testCases := []struct {
pluginVersion string
}{
{
pluginVersion: "v5",
},
{
pluginVersion: "v4",
},
}
for _, tc := range testCases {
t.Run(tc.pluginVersion, func(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeCredential, tc.pluginVersion)
defer cluster.Cleanup() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -105,10 +145,25 @@ func TestSystemBackend_Plugin_auth(t *testing.T) {
// If it fails, it means unseal process failed // If it fails, it means unseal process failed
vault.TestWaitActive(t, core.Core) vault.TestWaitActive(t, core.Core)
} }
})
}
} }
func TestSystemBackend_Plugin_MissingBinary(t *testing.T) { func TestSystemBackend_Plugin_MissingBinary(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -146,10 +201,25 @@ func TestSystemBackend_Plugin_MissingBinary(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("expected error") t.Fatalf("expected error")
} }
})
}
} }
func TestSystemBackend_Plugin_MismatchType(t *testing.T) { func TestSystemBackend_Plugin_MismatchType(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -168,28 +238,43 @@ func TestSystemBackend_Plugin_MismatchType(t *testing.T) {
// Sleep a bit before cleanup is called // Sleep a bit before cleanup is called
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
})
}
} }
func TestSystemBackend_Plugin_CatalogRemoved(t *testing.T) { func TestSystemBackend_Plugin_CatalogRemoved(t *testing.T) {
t.Run("secret", func(t *testing.T) { t.Run("secret", func(t *testing.T) {
testPlugin_CatalogRemoved(t, logical.TypeLogical, false) testPlugin_CatalogRemoved(t, logical.TypeLogical, false, logicalVersionMap)
}) })
t.Run("auth", func(t *testing.T) { t.Run("auth", func(t *testing.T) {
testPlugin_CatalogRemoved(t, logical.TypeCredential, false) testPlugin_CatalogRemoved(t, logical.TypeCredential, false, credentialVersionMap)
}) })
t.Run("secret-mount-existing", func(t *testing.T) { t.Run("secret-mount-existing", func(t *testing.T) {
testPlugin_CatalogRemoved(t, logical.TypeLogical, true) testPlugin_CatalogRemoved(t, logical.TypeLogical, true, logicalVersionMap)
}) })
t.Run("auth-mount-existing", func(t *testing.T) { t.Run("auth-mount-existing", func(t *testing.T) {
testPlugin_CatalogRemoved(t, logical.TypeCredential, true) testPlugin_CatalogRemoved(t, logical.TypeCredential, true, credentialVersionMap)
}) })
} }
func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMount bool) { func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMount bool, versionMap map[string]string) {
cluster := testSystemBackendMock(t, 1, 1, btype) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -230,13 +315,13 @@ func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMoun
switch btype { switch btype {
case logical.TypeLogical: case logical.TypeLogical:
// Add plugin back to the catalog // Add plugin back to the catalog
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "TestBackend_PluginMainLogical", []string{}, "") vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, logicalVersionMap[tc.pluginVersion], []string{}, "")
_, err = core.Client.Logical().Write("sys/mounts/mock-0", map[string]interface{}{ _, err = core.Client.Logical().Write("sys/mounts/mock-0", map[string]interface{}{
"type": "test", "type": "test",
}) })
case logical.TypeCredential: case logical.TypeCredential:
// Add plugin back to the catalog // Add plugin back to the catalog
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "TestBackend_PluginMainCredentials", []string{}, "") vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, credentialVersionMap[tc.pluginVersion], []string{}, "")
_, err = core.Client.Logical().Write("sys/auth/mock-0", map[string]interface{}{ _, err = core.Client.Logical().Write("sys/auth/mock-0", map[string]interface{}{
"type": "test", "type": "test",
}) })
@@ -245,6 +330,8 @@ func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMoun
t.Fatal("expected error when mounting on existing path") t.Fatal("expected error when mounting on existing path")
} }
} }
})
}
} }
func TestSystemBackend_Plugin_continueOnError(t *testing.T) { func TestSystemBackend_Plugin_continueOnError(t *testing.T) {
@@ -278,7 +365,20 @@ func TestSystemBackend_Plugin_continueOnError(t *testing.T) {
} }
func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatch bool, mountPoint string, pluginType consts.PluginType) { func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatch bool, mountPoint string, pluginType consts.PluginType) {
cluster := testSystemBackendMock(t, 1, 1, btype) testCases := []struct {
pluginVersion string
}{
{
pluginVersion: "v5",
},
{
pluginVersion: "v4",
},
}
for _, tc := range testCases {
t.Run(tc.pluginVersion, func(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, btype, tc.pluginVersion)
defer cluster.Cleanup() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -296,18 +396,6 @@ func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatc
t.Fatal("invalid command") t.Fatal("invalid command")
} }
// Mount credential type plugins
switch btype {
case logical.TypeCredential:
vault.TestAddTestPlugin(t, core.Core, mountPoint, consts.PluginTypeCredential, "TestBackend_PluginMainCredentials", []string{}, cluster.TempDir)
_, err = core.Client.Logical().Write(fmt.Sprintf("sys/auth/%s", mountPoint), map[string]interface{}{
"type": "mock-plugin",
})
if err != nil {
t.Fatalf("err:%v", err)
}
}
// Trigger a sha256 mismatch or missing plugin error // Trigger a sha256 mismatch or missing plugin error
if mismatch { if mismatch {
req = logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("sys/plugins/catalog/%s/mock-plugin", pluginType)) req = logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("sys/plugins/catalog/%s/mock-plugin", pluginType))
@@ -351,9 +439,11 @@ func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatc
// Re-add the plugin to the catalog // Re-add the plugin to the catalog
switch btype { switch btype {
case logical.TypeLogical: case logical.TypeLogical:
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "TestBackend_PluginMainLogical", []string{}, cluster.TempDir) plugin := logicalVersionMap[tc.pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, plugin, []string{}, cluster.TempDir)
case logical.TypeCredential: case logical.TypeCredential:
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "TestBackend_PluginMainCredentials", []string{}, cluster.TempDir) plugin := credentialVersionMap[tc.pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, plugin, []string{}, cluster.TempDir)
} }
// Reload the plugin // Reload the plugin
@@ -385,10 +475,25 @@ func testPlugin_continueOnError(t *testing.T, btype logical.BackendType, mismatc
if resp == nil { if resp == nil {
t.Fatalf("bad: response should not be nil") t.Fatalf("bad: response should not be nil")
} }
})
}
} }
func TestSystemBackend_Plugin_autoReload(t *testing.T) { func TestSystemBackend_Plugin_autoReload(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -426,10 +531,25 @@ func TestSystemBackend_Plugin_autoReload(t *testing.T) {
if resp.Data["value"].(string) == "baz" { if resp.Data["value"].(string) == "baz" {
t.Fatal("did not expect backend internal value to be 'baz'") t.Fatal("did not expect backend internal value to be 'baz'")
} }
})
}
} }
func TestSystemBackend_Plugin_SealUnseal(t *testing.T) { func TestSystemBackend_Plugin_SealUnseal(t *testing.T) {
cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
// Seal the cluster // Seal the cluster
@@ -452,6 +572,8 @@ func TestSystemBackend_Plugin_SealUnseal(t *testing.T) {
// Wait for active so post-unseal takes place // Wait for active so post-unseal takes place
// If it fails, it means unseal process failed // If it fails, it means unseal process failed
vault.TestWaitActive(t, cluster.Cores[0].Core) vault.TestWaitActive(t, cluster.Cores[0].Core)
})
}
} }
func TestSystemBackend_Plugin_reload(t *testing.T) { func TestSystemBackend_Plugin_reload(t *testing.T) {
@@ -498,7 +620,20 @@ func TestSystemBackend_Plugin_reload(t *testing.T) {
// Helper func to test different reload methods on plugin reload endpoint // Helper func to test different reload methods on plugin reload endpoint
func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}, backendType logical.BackendType) { func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}, backendType logical.BackendType) {
cluster := testSystemBackendMock(t, 1, 2, backendType) testCases := []struct {
pluginVersion string
}{
{
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() defer cluster.Cleanup()
core := cluster.Cores[0] core := cluster.Cores[0]
@@ -546,6 +681,8 @@ func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}
t.Fatal("did not expect backend internal value to be 'baz'") t.Fatal("did not expect backend internal value to be 'baz'")
} }
} }
})
}
} }
// testSystemBackendMock returns a systemBackend with the desired number // testSystemBackendMock returns a systemBackend with the desired number
@@ -553,7 +690,7 @@ func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}
// ways of providing the plugin_name. // ways of providing the plugin_name.
// //
// The mounts are mounted at sys/mounts/mock-[numMounts] or sys/auth/mock-[numMounts] // 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) *vault.TestCluster { func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType logical.BackendType, pluginVersion string) *vault.TestCluster {
coreConfig := &vault.CoreConfig{ coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{ LogicalBackends: map[string]logical.Factory{
"plugin": plugin.Factory, "plugin": plugin.Factory,
@@ -585,7 +722,8 @@ func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType lo
switch backendType { switch backendType {
case logical.TypeLogical: case logical.TypeLogical:
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "TestBackend_PluginMainLogical", []string{}, tempDir) plugin := logicalVersionMap[pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, plugin, []string{}, tempDir)
for i := 0; i < numMounts; i++ { for i := 0; i < numMounts; i++ {
// Alternate input styles for plugin_name on every other mount // Alternate input styles for plugin_name on every other mount
options := map[string]interface{}{ options := map[string]interface{}{
@@ -600,7 +738,8 @@ func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType lo
} }
} }
case logical.TypeCredential: case logical.TypeCredential:
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "TestBackend_PluginMainCredentials", []string{}, tempDir) plugin := credentialVersionMap[pluginVersion]
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, plugin, []string{}, tempDir)
for i := 0; i < numMounts; i++ { for i := 0; i < numMounts; i++ {
// Alternate input styles for plugin_name on every other mount // Alternate input styles for plugin_name on every other mount
options := map[string]interface{}{ options := map[string]interface{}{
@@ -671,9 +810,15 @@ func testSystemBackend_SingleCluster_Env(t *testing.T, env []string) *vault.Test
return cluster return cluster
} }
func TestBackend_PluginMainLogical(t *testing.T) { func TestBackend_PluginMain_V4_Logical(t *testing.T) {
args := []string{} args := []string{}
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" && os.Getenv(pluginutil.PluginMetadataModeEnv) != "true" { // 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 return
} }
@@ -686,6 +831,8 @@ func TestBackend_PluginMainLogical(t *testing.T) {
apiClientMeta := &api.PluginAPIClientMeta{} apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet() flags := apiClientMeta.FlagSet()
flags.Parse(args) flags.Parse(args)
// V4 does not support AutoMTLS so we set a TLSConfig via TLSProviderFunc
tlsConfig := apiClientMeta.GetTLSConfig() tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig) tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
@@ -700,9 +847,9 @@ func TestBackend_PluginMainLogical(t *testing.T) {
} }
} }
func TestBackend_PluginMainCredentials(t *testing.T) { func TestBackend_PluginMainLogical(t *testing.T) {
args := []string{} args := []string{}
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" && os.Getenv(pluginutil.PluginMetadataModeEnv) != "true" { if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
return return
} }
@@ -715,6 +862,40 @@ func TestBackend_PluginMainCredentials(t *testing.T) {
apiClientMeta := &api.PluginAPIClientMeta{} apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet() flags := apiClientMeta.FlagSet()
flags.Parse(args) 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() tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig) tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
@@ -729,6 +910,32 @@ func TestBackend_PluginMainCredentials(t *testing.T) {
} }
} }
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. // TestBackend_PluginMainEnv is a mock plugin that simply checks for the existence of FOO env var.
func TestBackend_PluginMainEnv(t *testing.T) { func TestBackend_PluginMainEnv(t *testing.T) {
args := []string{} args := []string{}
@@ -751,14 +958,11 @@ func TestBackend_PluginMainEnv(t *testing.T) {
apiClientMeta := &api.PluginAPIClientMeta{} apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet() flags := apiClientMeta.FlagSet()
flags.Parse(args) flags.Parse(args)
tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
factoryFunc := mock.FactoryType(logical.TypeLogical) factoryFunc := mock.FactoryType(logical.TypeLogical)
err := lplugin.Serve(&lplugin.ServeOpts{ err := lplugin.Serve(&lplugin.ServeOpts{
BackendFactoryFunc: factoryFunc, BackendFactoryFunc: factoryFunc,
TLSProviderFunc: tlsProviderFunc,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -325,14 +325,48 @@ func (c *PluginCatalog) getPluginTypeFromUnknown(ctx context.Context, logger log
} }
merr = multierror.Append(merr, err) merr = multierror.Append(merr, err)
// Attempt to run as backend plugin pluginType, err := c.getBackendPluginType(ctx, plugin)
client, err := backendplugin.NewPluginClient(ctx, nil, plugin, log.NewNullLogger(), true)
if err == nil { if err == nil {
err := client.Setup(ctx, &logical.BackendConfig{}) return pluginType, nil
}
merr = multierror.Append(merr, err)
return consts.PluginTypeUnknown, merr
}
// getBackendPluginType returns the plugin type (secrets/auth) and an error if
// the plugin is not a backend plugin.
func (c *PluginCatalog) getBackendPluginType(ctx context.Context, pluginRunner *pluginutil.PluginRunner) (consts.PluginType, error) {
var client logical.Backend
var merr *multierror.Error
config := pluginutil.PluginClientConfig{
Name: pluginRunner.Name,
Logger: log.NewNullLogger(),
IsMetadataMode: false,
AutoMTLS: true,
}
// Attempt to run as backend V5 plugin
c.logger.Debug("attempting to load backend plugin", "name", pluginRunner.Name)
client, err := backendplugin.NewPluginClient(ctx, pluginRunner, config)
if err != nil {
merr = multierror.Append(merr, err)
c.logger.Debug("failed to dispense v5 backend plugin", "name", pluginRunner.Name, "error", err)
config.AutoMTLS = false
config.IsMetadataMode = true
// attemtp to run as a v4 backend plugin
client, err = backendplugin.NewPluginClient(ctx, pluginRunner, config)
if err != nil {
c.logger.Debug("failed to dispense v4 backend plugin", "name", pluginRunner.Name, "error", err)
return consts.PluginTypeUnknown, merr.ErrorOrNil()
}
c.logger.Debug("successfully dispensed v4 backend plugin", "name", pluginRunner.Name)
}
err = client.Setup(ctx, &logical.BackendConfig{})
if err != nil { if err != nil {
return consts.PluginTypeUnknown, err return consts.PluginTypeUnknown, err
} }
backendType := client.Type() backendType := client.Type()
client.Cleanup(ctx) client.Cleanup(ctx)
@@ -342,22 +376,21 @@ func (c *PluginCatalog) getPluginTypeFromUnknown(ctx context.Context, logger log
case logical.TypeLogical: case logical.TypeLogical:
return consts.PluginTypeSecrets, nil return consts.PluginTypeSecrets, nil
} }
} else {
merr = multierror.Append(merr, err)
}
if client == nil || client.Type() == logical.TypeUnknown { if client == nil || client.Type() == logical.TypeUnknown {
logger.Warn("unknown plugin type", c.logger.Warn("unknown plugin type",
"plugin name", plugin.Name, "plugin name", pluginRunner.Name,
"error", merr.Error()) "error", merr.Error())
} else { } else {
logger.Warn("unsupported plugin type", c.logger.Warn("unsupported plugin type",
"plugin name", plugin.Name, "plugin name", pluginRunner.Name,
"plugin type", client.Type().String(), "plugin type", client.Type().String(),
"error", merr.Error()) "error", merr.Error())
} }
return consts.PluginTypeUnknown, nil merr = multierror.Append(merr, fmt.Errorf("failed to load plugin as backend plugin: %w", err))
return consts.PluginTypeUnknown, merr.ErrorOrNil()
} }
// isDatabasePlugin returns true if the plugin supports multiplexing. An error // isDatabasePlugin returns true if the plugin supports multiplexing. An error