2023-03-15 12:00:52 -04:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
2022-03-08 11:33:24 -05:00
|
|
|
package dbplugin
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"reflect"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
|
|
|
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
|
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 19:37:59 -04:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2022-03-08 11:33:24 -05:00
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestNewPluginClient(t *testing.T) {
|
|
|
|
|
type testCase struct {
|
|
|
|
|
config pluginutil.PluginClientConfig
|
|
|
|
|
pluginClient pluginutil.PluginClient
|
|
|
|
|
expectedResp *DatabasePluginClient
|
|
|
|
|
expectedErr error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := map[string]testCase{
|
|
|
|
|
"happy path": {
|
|
|
|
|
config: testPluginClientConfig(),
|
|
|
|
|
pluginClient: &fakePluginClient{
|
|
|
|
|
connResp: nil,
|
|
|
|
|
dispenseResp: gRPCClient{client: fakeClient{}},
|
|
|
|
|
dispenseErr: nil,
|
|
|
|
|
},
|
|
|
|
|
expectedResp: &DatabasePluginClient{
|
|
|
|
|
client: &fakePluginClient{
|
|
|
|
|
connResp: nil,
|
|
|
|
|
dispenseResp: gRPCClient{client: fakeClient{}},
|
|
|
|
|
dispenseErr: nil,
|
|
|
|
|
},
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 19:37:59 -04:00
|
|
|
Database: gRPCClient{client: proto.NewDatabaseClient(nil), versionClient: logical.NewPluginVersionClient(nil), doneCtx: context.Context(nil)},
|
2022-03-08 11:33:24 -05:00
|
|
|
},
|
|
|
|
|
expectedErr: nil,
|
|
|
|
|
},
|
|
|
|
|
"dispense error": {
|
|
|
|
|
config: testPluginClientConfig(),
|
|
|
|
|
pluginClient: &fakePluginClient{
|
|
|
|
|
connResp: nil,
|
|
|
|
|
dispenseResp: gRPCClient{},
|
|
|
|
|
dispenseErr: errors.New("dispense error"),
|
|
|
|
|
},
|
|
|
|
|
expectedResp: nil,
|
|
|
|
|
expectedErr: errors.New("dispense error"),
|
|
|
|
|
},
|
|
|
|
|
"error unsupported client type": {
|
|
|
|
|
config: testPluginClientConfig(),
|
|
|
|
|
pluginClient: &fakePluginClient{
|
|
|
|
|
connResp: nil,
|
|
|
|
|
dispenseResp: nil,
|
|
|
|
|
dispenseErr: nil,
|
|
|
|
|
},
|
|
|
|
|
expectedResp: nil,
|
|
|
|
|
expectedErr: errors.New("unsupported client type"),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, test := range tests {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
mockWrapper := new(mockRunnerUtil)
|
|
|
|
|
mockWrapper.On("NewPluginClient", ctx, mock.Anything).
|
|
|
|
|
Return(test.pluginClient, nil)
|
|
|
|
|
defer mockWrapper.AssertNumberOfCalls(t, "NewPluginClient", 1)
|
|
|
|
|
|
|
|
|
|
resp, err := NewPluginClient(ctx, mockWrapper, test.config)
|
|
|
|
|
if test.expectedErr != nil && err == nil {
|
|
|
|
|
t.Fatalf("err expected, got nil")
|
|
|
|
|
}
|
|
|
|
|
if test.expectedErr == nil && err != nil {
|
|
|
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
|
|
|
}
|
|
|
|
|
if test.expectedErr == nil && !reflect.DeepEqual(resp, test.expectedResp) {
|
|
|
|
|
t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPluginClientConfig() pluginutil.PluginClientConfig {
|
|
|
|
|
return pluginutil.PluginClientConfig{
|
|
|
|
|
Name: "test-plugin",
|
|
|
|
|
PluginSets: PluginSets,
|
|
|
|
|
PluginType: consts.PluginTypeDatabase,
|
|
|
|
|
HandshakeConfig: HandshakeConfig,
|
|
|
|
|
Logger: log.NewNullLogger(),
|
|
|
|
|
IsMetadataMode: true,
|
|
|
|
|
AutoMTLS: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ pluginutil.PluginClient = &fakePluginClient{}
|
|
|
|
|
|
|
|
|
|
type fakePluginClient struct {
|
|
|
|
|
connResp grpc.ClientConnInterface
|
|
|
|
|
|
|
|
|
|
dispenseResp interface{}
|
|
|
|
|
dispenseErr error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *fakePluginClient) Conn() grpc.ClientConnInterface {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-29 22:42:26 -04:00
|
|
|
func (f *fakePluginClient) Reload() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 11:33:24 -05:00
|
|
|
func (f *fakePluginClient) Dispense(name string) (interface{}, error) {
|
|
|
|
|
return f.dispenseResp, f.dispenseErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *fakePluginClient) Ping() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *fakePluginClient) Close() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ pluginutil.RunnerUtil = &mockRunnerUtil{}
|
|
|
|
|
|
|
|
|
|
type mockRunnerUtil struct {
|
|
|
|
|
mock.Mock
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 13:29:51 -05:00
|
|
|
func (m *mockRunnerUtil) VaultVersion(ctx context.Context) (string, error) {
|
|
|
|
|
return "dummyversion", nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 11:33:24 -05:00
|
|
|
func (m *mockRunnerUtil) NewPluginClient(ctx context.Context, config pluginutil.PluginClientConfig) (pluginutil.PluginClient, error) {
|
|
|
|
|
args := m.Called(ctx, config)
|
|
|
|
|
return args.Get(0).(pluginutil.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)
|
|
|
|
|
}
|
2023-10-18 09:30:00 -04:00
|
|
|
|
|
|
|
|
func (m *mockRunnerUtil) ClusterID(ctx context.Context) (string, error) {
|
|
|
|
|
return "clusterid", nil
|
|
|
|
|
}
|