// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package pluginutil import ( "context" "fmt" "os/exec" "testing" "time" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestMakeConfig(t *testing.T) { type testCase struct { rc runConfig responseWrapInfo *wrapping.ResponseWrapInfo responseWrapInfoErr error responseWrapInfoTimes int mlockEnabled bool mlockEnabledTimes int expectedConfig *plugin.ClientConfig expectTLSConfig bool } tests := map[string]testCase{ "metadata mode, not-AutoMTLS": { rc: runConfig{ command: "echo", args: []string{"foo", "bar"}, sha256: []byte("some_sha256"), env: []string{"initial=true"}, PluginClientConfig: PluginClientConfig{ PluginSets: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, Logger: hclog.NewNullLogger(), IsMetadataMode: true, AutoMTLS: false, }, }, responseWrapInfoTimes: 0, mlockEnabled: false, mlockEnabledTimes: 1, expectedConfig: &plugin.ClientConfig{ HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, VersionedPlugins: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, Cmd: commandWithEnv( "echo", []string{"foo", "bar"}, []string{ "initial=true", fmt.Sprintf("%s=%s", PluginVaultVersionEnv, "dummyversion"), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true), fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, false), }, ), SecureConfig: &plugin.SecureConfig{ Checksum: []byte("some_sha256"), // Hash is generated }, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolNetRPC, plugin.ProtocolGRPC, }, Logger: hclog.NewNullLogger(), AutoMTLS: false, }, expectTLSConfig: false, }, "non-metadata mode, not-AutoMTLS": { rc: runConfig{ command: "echo", args: []string{"foo", "bar"}, sha256: []byte("some_sha256"), env: []string{"initial=true"}, PluginClientConfig: PluginClientConfig{ PluginSets: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, Logger: hclog.NewNullLogger(), IsMetadataMode: false, AutoMTLS: false, }, }, responseWrapInfo: &wrapping.ResponseWrapInfo{ Token: "testtoken", }, responseWrapInfoTimes: 1, mlockEnabled: true, mlockEnabledTimes: 1, expectedConfig: &plugin.ClientConfig{ HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, VersionedPlugins: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, Cmd: commandWithEnv( "echo", []string{"foo", "bar"}, []string{ "initial=true", fmt.Sprintf("%s=%t", PluginMlockEnabled, true), fmt.Sprintf("%s=%s", PluginVaultVersionEnv, "dummyversion"), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false), fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, false), fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, "testtoken"), }, ), SecureConfig: &plugin.SecureConfig{ Checksum: []byte("some_sha256"), // Hash is generated }, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolNetRPC, plugin.ProtocolGRPC, }, Logger: hclog.NewNullLogger(), AutoMTLS: false, }, expectTLSConfig: true, }, "metadata mode, AutoMTLS": { rc: runConfig{ command: "echo", args: []string{"foo", "bar"}, sha256: []byte("some_sha256"), env: []string{"initial=true"}, PluginClientConfig: PluginClientConfig{ PluginSets: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, Logger: hclog.NewNullLogger(), IsMetadataMode: true, AutoMTLS: true, }, }, responseWrapInfoTimes: 0, mlockEnabled: false, mlockEnabledTimes: 1, expectedConfig: &plugin.ClientConfig{ HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, VersionedPlugins: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, Cmd: commandWithEnv( "echo", []string{"foo", "bar"}, []string{ "initial=true", fmt.Sprintf("%s=%s", PluginVaultVersionEnv, "dummyversion"), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, true), fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, true), }, ), SecureConfig: &plugin.SecureConfig{ Checksum: []byte("some_sha256"), // Hash is generated }, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolNetRPC, plugin.ProtocolGRPC, }, Logger: hclog.NewNullLogger(), AutoMTLS: true, }, expectTLSConfig: false, }, "not-metadata mode, AutoMTLS": { rc: runConfig{ command: "echo", args: []string{"foo", "bar"}, sha256: []byte("some_sha256"), env: []string{"initial=true"}, PluginClientConfig: PluginClientConfig{ PluginSets: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, Logger: hclog.NewNullLogger(), IsMetadataMode: false, AutoMTLS: true, }, }, responseWrapInfoTimes: 0, mlockEnabled: false, mlockEnabledTimes: 1, expectedConfig: &plugin.ClientConfig{ HandshakeConfig: plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "magic_cookie_key", MagicCookieValue: "magic_cookie_value", }, VersionedPlugins: map[int]plugin.PluginSet{ 1: { "bogus": nil, }, }, Cmd: commandWithEnv( "echo", []string{"foo", "bar"}, []string{ "initial=true", fmt.Sprintf("%s=%s", PluginVaultVersionEnv, "dummyversion"), fmt.Sprintf("%s=%t", PluginMetadataModeEnv, false), fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, true), }, ), SecureConfig: &plugin.SecureConfig{ Checksum: []byte("some_sha256"), // Hash is generated }, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolNetRPC, plugin.ProtocolGRPC, }, Logger: hclog.NewNullLogger(), AutoMTLS: true, }, expectTLSConfig: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { mockWrapper := new(mockRunnerUtil) mockWrapper.On("ResponseWrapData", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(test.responseWrapInfo, test.responseWrapInfoErr) mockWrapper.On("MlockEnabled"). Return(test.mlockEnabled) test.rc.Wrapper = mockWrapper defer mockWrapper.AssertNumberOfCalls(t, "ResponseWrapData", test.responseWrapInfoTimes) defer mockWrapper.AssertNumberOfCalls(t, "MlockEnabled", test.mlockEnabledTimes) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() config, err := test.rc.makeConfig(ctx) if err != nil { t.Fatalf("no error expected, got: %s", err) } // The following fields are generated, so we just need to check for existence, not specific value // The value must be nilled out before performing a DeepEqual check hsh := config.SecureConfig.Hash if hsh == nil { t.Fatalf("Missing SecureConfig.Hash") } config.SecureConfig.Hash = nil if test.expectTLSConfig && config.TLSConfig == nil { t.Fatalf("TLS config expected, got nil") } if !test.expectTLSConfig && config.TLSConfig != nil { t.Fatalf("no TLS config expected, got: %#v", config.TLSConfig) } config.TLSConfig = nil require.Equal(t, test.expectedConfig, config) }) } } func commandWithEnv(cmd string, args []string, env []string) *exec.Cmd { c := exec.Command(cmd, args...) c.Env = env return c } var _ RunnerUtil = &mockRunnerUtil{} type mockRunnerUtil struct { mock.Mock } func (m *mockRunnerUtil) VaultVersion(ctx context.Context) (string, error) { return "dummyversion", nil } func (m *mockRunnerUtil) NewPluginClient(ctx context.Context, config PluginClientConfig) (PluginClient, error) { args := m.Called(ctx, config) return args.Get(0).(PluginClient), args.Error(1) } func (m *mockRunnerUtil) ResponseWrapData(ctx context.Context, data map[string]interface{}, ttl time.Duration, jwt bool) (*wrapping.ResponseWrapInfo, error) { args := m.Called(ctx, data, ttl, jwt) return args.Get(0).(*wrapping.ResponseWrapInfo), args.Error(1) } func (m *mockRunnerUtil) MlockEnabled() bool { args := m.Called() return args.Bool(0) }