mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 13:39:37 +08:00
* Add plugin exchange service * Add Plugin methods to auth * Add gRPC-layer methods for Plugin * Add RBAC presets for Plugin * Test GetPlugin()/NoSecrets access * Make error assertions more correct in role test * Deny setting credentials if user can not read them * gofmt * Apply minor suggestions from code review Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Co-authored-by: Tiago Silva <tiago.silva@goteleport.com> * Move dependency into an existing block in go.mod * Improve error messages for failed type assertions * DRY WithSecrets access checks for Plugins * Run new tests in parallel * Improve error assertions in auth_with_roles_test * TestGetPluginWithSecrets: split cases to subtests * Clean up test servers and clients * Add proto for plugin service * Remove Plugin methods from auth service Moved to a dedicated service * Remove plugin-related auth methods Moved to a dedicated service in Enterprise * Remove CreatePlugin test from auth_with_roles_test Moved to a dedicated service in Enterprise * Pass "backend getter" to local plugins service This pattern is used in Enterprise to set up secondary services before auth (and backend) are created. * Rename InitialCredentials to BootstrapCredentials * Add plugins service to genproto.sh * Reformat generated proto * Remove obsolete PluginExchangeService The equivalent of this is now in Enterprise * Add kube service to genproto.sh; regenerate * Add ListPlugins to plugin backend service * Reimplement GetPlugins on top of ListPlugins This is a "convenience" implementation for the backend service layer. * Replace GetPlugins with ListPlugins in gRPC schema * Fix ListKubernetesResources unit test * Simplify plugin pagination key to just the name * Use existing constant for page size * Make dummy clients return errors instead of panic * Remove obsolete field * Ensure go.mod is valid for corresponding e changes * Fix passing mutex ref * Move teleport-plugins import to e_imports * Revert oauth change in go.mod * Use limit+1 to look-ahead when paginating plugins * Test plugin pagination with pageSize > numPlugins * Add descriptive messages to gRPC dummy clients * Plugin: add RW for editor; remove secrets from gRPC * Make message more descriptive for dummy gRPC conn --------- Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>
201 lines
5.9 KiB
Go
201 lines
5.9 KiB
Go
/*
|
|
Copyright 2023 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package local
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
apidefaults "github.com/gravitational/teleport/api/defaults"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/backend/memory"
|
|
)
|
|
|
|
// TestPluginsCRUD tests backend operations with plugin resources.
|
|
func TestPluginsCRUD(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
mem, err := memory.New(memory.Config{
|
|
Context: ctx,
|
|
Clock: clockwork.NewFakeClock(),
|
|
})
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { mem.Close() })
|
|
|
|
service := NewPluginsService(func() backend.Backend { return mem })
|
|
|
|
// Define two plugins
|
|
plugin1 := types.NewPluginV1(types.Metadata{Name: "p1"}, types.PluginSpecV1{
|
|
Settings: &types.PluginSpecV1_SlackAccessPlugin{
|
|
SlackAccessPlugin: &types.PluginSlackAccessSettings{
|
|
FallbackChannel: "#foo",
|
|
},
|
|
},
|
|
}, nil)
|
|
plugin2 := types.NewPluginV1(types.Metadata{Name: "p2"}, types.PluginSpecV1{
|
|
Settings: &types.PluginSpecV1_SlackAccessPlugin{
|
|
SlackAccessPlugin: &types.PluginSlackAccessSettings{
|
|
FallbackChannel: "#bar",
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
// Initially we expect no items.
|
|
out, err := service.GetPlugins(ctx, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(out))
|
|
|
|
// Create both plugins.
|
|
err = service.CreatePlugin(ctx, plugin1)
|
|
require.NoError(t, err)
|
|
err = service.CreatePlugin(ctx, plugin2)
|
|
require.NoError(t, err)
|
|
|
|
// Fetch all plugins.
|
|
out, err = service.GetPlugins(ctx, true)
|
|
require.NoError(t, err)
|
|
require.Empty(t, cmp.Diff([]types.Plugin{plugin1, plugin2}, out,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
))
|
|
|
|
// Fetch a specific plugin.
|
|
cluster, err := service.GetPlugin(ctx, plugin2.GetName(), true)
|
|
require.NoError(t, err)
|
|
require.Empty(t, cmp.Diff(plugin2, cluster,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
))
|
|
|
|
// Try to fetch a plugin that doesn't exist.
|
|
_, err = service.GetPlugin(ctx, "doesnotexist", true)
|
|
require.IsType(t, trace.NotFound(""), err)
|
|
|
|
// Try to create a duplicate plugin.
|
|
err = service.CreatePlugin(ctx, plugin1)
|
|
require.IsType(t, trace.AlreadyExists(""), err)
|
|
|
|
// Set plugin status.
|
|
status := types.PluginStatusV1{
|
|
Code: types.PluginStatusCode_OTHER_ERROR,
|
|
}
|
|
err = service.SetPluginStatus(ctx, plugin1.GetName(), status)
|
|
require.NoError(t, err)
|
|
cluster, err = service.GetPlugin(ctx, plugin1.GetName(), true)
|
|
require.NoError(t, err)
|
|
// Fields other than Status should remain unchanged
|
|
require.Empty(t, cmp.Diff(plugin1, cluster,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
cmpopts.IgnoreFields(types.PluginV1{}, "Status"),
|
|
))
|
|
require.Empty(t, cmp.Diff(status, cluster.GetStatus()))
|
|
|
|
// Delete a plugin.
|
|
err = service.DeletePlugin(ctx, plugin1.GetName())
|
|
require.NoError(t, err)
|
|
out, err = service.GetPlugins(ctx, true)
|
|
require.NoError(t, err)
|
|
require.Empty(t, cmp.Diff([]types.Plugin{plugin2}, out,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
))
|
|
|
|
// Try to delete a plugin that doesn't exist.
|
|
err = service.DeletePlugin(ctx, "doesnotexist")
|
|
require.IsType(t, trace.NotFound(""), err)
|
|
|
|
// Delete all plugin.
|
|
err = service.DeleteAllPlugins(ctx)
|
|
require.NoError(t, err)
|
|
out, err = service.GetPlugins(ctx, true)
|
|
require.NoError(t, err)
|
|
require.Len(t, out, 0)
|
|
}
|
|
|
|
func TestListPlugins(t *testing.T) {
|
|
const pageSize = 5
|
|
const numPlugins = 2*pageSize + 1
|
|
ctx := context.Background()
|
|
|
|
mem, err := memory.New(memory.Config{
|
|
Context: ctx,
|
|
Clock: clockwork.NewFakeClock(),
|
|
})
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { mem.Close() })
|
|
|
|
service := NewPluginsService(func() backend.Backend { return mem })
|
|
|
|
var insertedPlugins []types.Plugin
|
|
for i := 0; i < numPlugins; i++ {
|
|
plugin := types.NewPluginV1(
|
|
types.Metadata{Name: fmt.Sprintf("p%02d", i+1)},
|
|
types.PluginSpecV1{
|
|
Settings: &types.PluginSpecV1_SlackAccessPlugin{
|
|
SlackAccessPlugin: &types.PluginSlackAccessSettings{
|
|
FallbackChannel: fmt.Sprintf("#foo-%02d", i+1),
|
|
},
|
|
},
|
|
},
|
|
nil)
|
|
err := service.CreatePlugin(ctx, plugin)
|
|
require.NoError(t, err)
|
|
insertedPlugins = append(insertedPlugins, plugin)
|
|
}
|
|
|
|
t.Run("paginated", func(t *testing.T) {
|
|
page1, nextKey, err := service.ListPlugins(ctx, pageSize, "", true)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, nextKey)
|
|
require.Len(t, page1, pageSize)
|
|
|
|
page2, nextKey, err := service.ListPlugins(ctx, pageSize, nextKey, true)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, nextKey)
|
|
require.Len(t, page2, pageSize)
|
|
|
|
page3, nextKey, err := service.ListPlugins(ctx, pageSize, nextKey, true)
|
|
require.NoError(t, err)
|
|
require.Empty(t, nextKey)
|
|
require.Len(t, page3, 1)
|
|
|
|
var fetchedPlugins []types.Plugin
|
|
fetchedPlugins = append(fetchedPlugins, page1...)
|
|
fetchedPlugins = append(fetchedPlugins, page2...)
|
|
fetchedPlugins = append(fetchedPlugins, page3...)
|
|
|
|
require.Empty(t, cmp.Diff(insertedPlugins, fetchedPlugins,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
))
|
|
})
|
|
|
|
t.Run("single", func(t *testing.T) {
|
|
fetchedPlugins, nextKey, err := service.ListPlugins(ctx, apidefaults.DefaultChunkSize, "", true)
|
|
require.NoError(t, err)
|
|
require.Empty(t, nextKey)
|
|
|
|
require.Empty(t, cmp.Diff(insertedPlugins, fetchedPlugins,
|
|
cmpopts.IgnoreFields(types.Metadata{}, "ID"),
|
|
))
|
|
})
|
|
}
|