diff --git a/api/types/app.go b/api/types/app.go index 0ddf8512f988f..ab38733dc1467 100644 --- a/api/types/app.go +++ b/api/types/app.go @@ -482,6 +482,13 @@ func (a *AppV3) CheckAndSetDefaults() error { } } + // Set an "app-sub-kind" label can be used for RBAC. + if a.SubKind != "" { + if a.Metadata.Labels == nil { + a.Metadata.Labels = make(map[string]string) + } + a.Metadata.Labels[AppSubKindLabel] = a.SubKind + } return nil } diff --git a/api/types/app_test.go b/api/types/app_test.go index 3d47af4ad11b4..e9aeb474e1a2d 100644 --- a/api/types/app_test.go +++ b/api/types/app_test.go @@ -623,6 +623,7 @@ func TestNewAppV3(t *testing.T) { Metadata: Metadata{ Name: "mcp-everything", Namespace: "default", + Labels: map[string]string{AppSubKindLabel: "mcp"}, }, Spec: AppSpecV3{ URI: "mcp+stdio://", @@ -678,6 +679,7 @@ func TestNewAppV3(t *testing.T) { Namespace: "default", Labels: map[string]string{ TeleportInternalResourceType: DemoResource, + AppSubKindLabel: "mcp", }, }, Spec: AppSpecV3{ diff --git a/api/types/constants.go b/api/types/constants.go index 995c83d9fa1c7..c3c6d2280ca96 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -1214,6 +1214,9 @@ const ( // GitHubOrgLabel is the label for GitHub organization. GitHubOrgLabel = TeleportInternalLabelPrefix + "github-org" + + // AppSubKindLabel is the label that has the same value of "app.sub_kind". + AppSubKindLabel = TeleportInternalLabelPrefix + "app-sub-kind" ) const ( diff --git a/constants.go b/constants.go index d2ebe3d7d38ba..b593b16a8be77 100644 --- a/constants.go +++ b/constants.go @@ -753,6 +753,10 @@ const ( // PresetListAccessRequestResourcesRoleName is a name of a preset role that // includes permissions to read access request resources. PresetListAccessRequestResourcesRoleName = "list-access-request-resources" + + // PresetMCPUserRoleName is a name of a preset role that allows + // accessing MCP servers. + PresetMCPUserRoleName = "mcp-user" ) var PresetRoles = []string{PresetEditorRoleName, PresetAccessRoleName, PresetAuditorRoleName} diff --git a/lib/auth/init.go b/lib/auth/init.go index 85815704d9240..db8ccd8a90e9e 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -1301,6 +1301,7 @@ func GetPresetRoles() []types.Role { services.NewPresetWildcardWorkloadIdentityIssuerRole(), services.NewPresetAccessPluginRole(), services.NewPresetListAccessRequestResourcesRole(), + services.NewPresetMCPUserRole(), } // Certain `New$FooRole()` functions will return a nil role if the diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 1db480616fa42..b3e7d9e140501 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -958,6 +958,7 @@ func TestPresets(t *testing.T) { teleport.PresetWildcardWorkloadIdentityIssuerRoleName, teleport.PresetAccessPluginRoleName, teleport.PresetListAccessRequestResourcesRoleName, + teleport.PresetMCPUserRoleName, } t.Run("EmptyCluster", func(t *testing.T) { diff --git a/lib/services/presets.go b/lib/services/presets.go index dad36011aba96..c8f0e3144bf70 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -825,6 +825,34 @@ func NewPresetTerraformProviderRole() types.Role { return role } +// NewPresetMCPUserRole returns a new pre-defined role for accessing MCP +// servers. +func NewPresetMCPUserRole() types.Role { + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V8, + Metadata: types.Metadata{ + Name: teleport.PresetMCPUserRoleName, + Namespace: apidefaults.Namespace, + Description: "Access to MCP servers", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: map[string]apiutils.Strings{ + types.AppSubKindLabel: []string{types.SubKindMCP}, + }, + MCP: &types.MCPPermissions{ + Tools: []string{types.Wildcard}, + }, + }, + }, + } + return role +} + // NewPresetHealthCheckConfig returns a preset default health_check_config that // enables health checks for all resources. func NewPresetHealthCheckConfig() *healthcheckconfigv1.HealthCheckConfig { @@ -873,8 +901,9 @@ func bootstrapRoleMetadataLabels() map[string]map[string]string { teleport.SystemIdentityCenterAccessRoleName: { types.TeleportInternalResourceType: types.SystemResource, }, - // Group access, reviewer and requester are intentionally not added here as there may be - // existing customer defined roles that have these labels. + // These roles are intentionally not added here as there may be existing + // customer defined roles that have these labels: + // group-access, reviewer, requester, mcp-user } } @@ -1036,7 +1065,7 @@ func AddRoleDefaults(ctx context.Context, role types.Role) (types.Role, error) { // Check if the role has a TeleportInternalResourceType attached. We do this after setting the role metadata // labels because we set the role metadata labels for roles that have been well established (access, // editor, auditor) that may not already have this label set, but we don't set it for newer roles - // (group-access, reviewer, requester) that may have customer definitions. + // (group-access, reviewer, requester, mcp-user) that may have customer definitions. resourceType := labels[types.TeleportInternalResourceType] if resourceType != types.PresetResource && resourceType != types.SystemResource { return nil, trace.AlreadyExists("not modifying user created role") diff --git a/lib/srv/mcp/helpers_test.go b/lib/srv/mcp/helpers_test.go index a658c2b7f3351..47e93380095c4 100644 --- a/lib/srv/mcp/helpers_test.go +++ b/lib/srv/mcp/helpers_test.go @@ -73,17 +73,8 @@ func withRole(role types.Role) setupTestContextOptionFunc { // tools. func withAdminRole(t *testing.T) setupTestContextOptionFunc { t.Helper() - role, err := types.NewRole("admin", types.RoleSpecV6{ - Allow: types.RoleConditions{ - AppLabels: map[string]apiutils.Strings{ - types.Wildcard: {types.Wildcard}, - }, - MCP: &types.MCPPermissions{ - Tools: []string{types.Wildcard}, - }, - }, - }) - require.NoError(t, err) + role := services.NewPresetMCPUserRole() + require.NoError(t, services.CheckAndSetDefaults(role)) return withRole(role) } diff --git a/tool/tsh/common/testdata/Test_mcpListCommand/JSON_format.golden b/tool/tsh/common/testdata/Test_mcpListCommand/JSON_format.golden index 9ec6bb8a0f00c..2b6b15f764aee 100644 --- a/tool/tsh/common/testdata/Test_mcpListCommand/JSON_format.golden +++ b/tool/tsh/common/testdata/Test_mcpListCommand/JSON_format.golden @@ -7,7 +7,8 @@ "name": "allow-read", "description": "description", "labels": { - "env": "dev" + "env": "dev", + "teleport.internal/app-sub-kind": "mcp" } }, "spec": { @@ -39,7 +40,8 @@ "name": "deny-write", "description": "description", "labels": { - "env": "dev" + "env": "dev", + "teleport.internal/app-sub-kind": "mcp" } }, "spec": { diff --git a/tool/tsh/common/testdata/Test_mcpListCommand/YAML_format.golden b/tool/tsh/common/testdata/Test_mcpListCommand/YAML_format.golden index 3db859fad057f..5cefd6878e259 100644 --- a/tool/tsh/common/testdata/Test_mcpListCommand/YAML_format.golden +++ b/tool/tsh/common/testdata/Test_mcpListCommand/YAML_format.golden @@ -3,6 +3,7 @@ description: description labels: env: dev + teleport.internal/app-sub-kind: mcp name: allow-read permissions: mcp: @@ -24,6 +25,7 @@ description: description labels: env: dev + teleport.internal/app-sub-kind: mcp name: deny-write permissions: mcp: diff --git a/tool/tsh/common/testdata/Test_mcpListCommand/text_format_in_verbose.golden b/tool/tsh/common/testdata/Test_mcpListCommand/text_format_in_verbose.golden index 575a73eaa616f..5d3190a89aa04 100644 --- a/tool/tsh/common/testdata/Test_mcpListCommand/text_format_in_verbose.golden +++ b/tool/tsh/common/testdata/Test_mcpListCommand/text_format_in_verbose.golden @@ -1,5 +1,5 @@ -Name Description Type Labels Command Args Allowed Tools ----------- ----------- ----- ------- ------- ---- ---------------------- -allow-read description stdio env=dev test arg [read_*] -deny-write description stdio env=dev test arg [*], except: [write_*] +Name Description Type Labels Command Args Allowed Tools +---------- ----------- ----- ------------------------------------------ ------- ---- ---------------------- +allow-read description stdio env=dev,teleport.internal/app-sub-kind=mcp test arg [read_*] +deny-write description stdio env=dev,teleport.internal/app-sub-kind=mcp test arg [*], except: [write_*]