mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Adds RBAC support for Identity Center Accounts (#49808)
* Exposes Identity Center accounts as Apps in Unified Resource Cache
For the purposes of the UI, Identity Center accounts and account
assignments are treated like special Apps. This patch exposes
Account Assignments to the UI via the Unified Resource Cache.
Includes:
- Generating an App resource from an Identity Center Account resource
- General plumbing from backend through to cache and UI
* lint
* Adds RBAC support for Identity Center Accounts
- Adds custom RoleMatchers for Identity Center resources
- Disables label checking for Identity Center Account resources
- Adds custom action checker that understands the generic
`KindIdentityCenter` resource kind, and falls back from the
requested resource kind to the generic one if not explicitly
denied.
* Restore tests clobbered by merge
This commit is contained in:
@@ -145,6 +145,20 @@ func (a *ServerWithRoles) authConnectorAction(resource string, verb string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// identityCenterAction is a special checker that grants access to Identity Center
|
||||
// resources. In order to simplify the writing of role condition statements, the
|
||||
// various Identity Center resources are bundled up under an umbrella
|
||||
// `KindIdentityCenter` resource kind. This means that if access to the target
|
||||
// resource is not explicitly denied, then the user has a second chance to get
|
||||
// access via the generic resource kind.
|
||||
func (a *ServerWithRoles) identityCenterAction(namespace string, resource string, verbs ...string) error {
|
||||
err := a.actionNamespace(namespace, resource, verbs...)
|
||||
if err == nil || services.IsAccessExplicitlyDenied(err) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(a.actionNamespace(namespace, types.KindIdentityCenter, verbs...))
|
||||
}
|
||||
|
||||
// actionForListWithCondition extracts a restrictive filter condition to be
|
||||
// added to a list query after a simple resource check fails.
|
||||
func (a *ServerWithRoles) actionForListWithCondition(resource, identifier string) (*types.WhereExpr, error) {
|
||||
@@ -1292,6 +1306,17 @@ func (c *resourceAccess) checkAccess(resource types.ResourceWithLabels, filter s
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type actionChecker func(namespace, resourceKind string, verbs ...string) error
|
||||
|
||||
func (a *ServerWithRoles) selectActionChecker(resourceKind string) actionChecker {
|
||||
if resourceKind == types.KindIdentityCenterAccount {
|
||||
// Identity Center resources can be specified multiple ways in a Role
|
||||
// Condition statement, so we need a special checker to handle it.
|
||||
return a.identityCenterAction
|
||||
}
|
||||
return a.actionNamespace
|
||||
}
|
||||
|
||||
// ListUnifiedResources returns a paginated list of unified resources filtered by user access.
|
||||
func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.ListUnifiedResourcesRequest) (*proto.ListUnifiedResourcesResponse, error) {
|
||||
filter := services.MatchResourceFilter{
|
||||
@@ -1329,7 +1354,8 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
|
||||
actionVerbs = []string{types.VerbList}
|
||||
}
|
||||
|
||||
resourceAccess.kindAccessMap[kind] = a.action(kind, actionVerbs...)
|
||||
checkAction := a.selectActionChecker(kind)
|
||||
resourceAccess.kindAccessMap[kind] = checkAction(apidefaults.Namespace, kind, actionVerbs...)
|
||||
}
|
||||
|
||||
// Before doing any listing, verify that the user is allowed to list
|
||||
@@ -1673,7 +1699,8 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou
|
||||
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
|
||||
}
|
||||
|
||||
if err := a.actionNamespace(req.Namespace, req.ResourceType, actionVerbs...); err != nil {
|
||||
checkAction := a.selectActionChecker(req.ResourceType)
|
||||
if err := checkAction(req.Namespace, req.ResourceType, actionVerbs...); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -1800,9 +1827,14 @@ func (r resourceChecker) CanAccess(resource types.Resource) error {
|
||||
}
|
||||
case types.SAMLIdPServiceProvider:
|
||||
return r.CheckAccess(rr, state)
|
||||
|
||||
case types.Resource153Unwrapper:
|
||||
if checkable, ok := rr.(services.AccessCheckable); ok {
|
||||
return r.CheckAccess(checkable, state)
|
||||
}
|
||||
}
|
||||
|
||||
return trace.BadParameter("could not check access to resource type %T", r)
|
||||
return trace.BadParameter("could not check access to resource type %T", resource)
|
||||
}
|
||||
|
||||
// newResourceAccessChecker creates a resourceAccessChecker for the provided resource type
|
||||
@@ -1817,7 +1849,8 @@ func (a *ServerWithRoles) newResourceAccessChecker(resource string) (resourceAcc
|
||||
types.KindKubeServer,
|
||||
types.KindUserGroup,
|
||||
types.KindUnifiedResource,
|
||||
types.KindSAMLIdPServiceProvider:
|
||||
types.KindSAMLIdPServiceProvider,
|
||||
types.KindIdentityCenterAccount:
|
||||
return &resourceChecker{AccessChecker: a.context.Checker}, nil
|
||||
default:
|
||||
return nil, trace.BadParameter("could not check access to resource type %s", resource)
|
||||
|
||||
@@ -5804,11 +5804,18 @@ func TestUnifiedResources_IdentityCenter(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("access denied", func(t *testing.T) {
|
||||
// Asserts that, with no RBAC or matchers in place, acces to IC Accounts
|
||||
// is denied by default
|
||||
setAccountAssignment := func(role types.Role) {
|
||||
r := role.(*types.RoleV6)
|
||||
r.Spec.Allow.AccountAssignments = []types.IdentityCenterAccountAssignment{
|
||||
{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
userNoAccess, _, err := CreateUserAndRole(srv.Auth(), "test", nil, nil)
|
||||
t.Run("no access", func(t *testing.T) {
|
||||
userNoAccess, _, err := CreateUserAndRole(srv.Auth(), "no-access", nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := TestUser(userNoAccess.GetName())
|
||||
@@ -5816,16 +5823,83 @@ func TestUnifiedResources_IdentityCenter(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer clt.Close()
|
||||
|
||||
_, err = clt.ListResources(ctx, proto.ListResourcesRequest{
|
||||
resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{
|
||||
ResourceType: types.KindIdentityCenterAccount,
|
||||
Labels: map[string]string{
|
||||
types.OriginLabel: apicommon.OriginAWSIdentityCenter,
|
||||
},
|
||||
})
|
||||
require.True(t, trace.IsAccessDenied(err))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, resp.Resources)
|
||||
})
|
||||
|
||||
// TODO(tcsc): Add other tests one RBAC implemented
|
||||
t.Run("access via generic kind", func(t *testing.T) {
|
||||
user, _, err := CreateUserAndRole(srv.Auth(), "read-generic", nil,
|
||||
[]types.Rule{
|
||||
types.NewRule(types.KindIdentityCenter, services.RO()),
|
||||
},
|
||||
WithRoleMutator(setAccountAssignment))
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := TestUser(user.GetName())
|
||||
clt, err := srv.NewClient(identity)
|
||||
require.NoError(t, err)
|
||||
defer clt.Close()
|
||||
|
||||
resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{
|
||||
ResourceType: types.KindIdentityCenterAccount,
|
||||
Labels: map[string]string{
|
||||
types.OriginLabel: apicommon.OriginAWSIdentityCenter,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Resources, 1)
|
||||
})
|
||||
|
||||
t.Run("access via specific kind", func(t *testing.T) {
|
||||
user, _, err := CreateUserAndRole(srv.Auth(), "read-specific", nil,
|
||||
[]types.Rule{
|
||||
types.NewRule(types.KindIdentityCenterAccount, services.RO()),
|
||||
},
|
||||
WithRoleMutator(setAccountAssignment))
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := TestUser(user.GetName())
|
||||
clt, err := srv.NewClient(identity)
|
||||
require.NoError(t, err)
|
||||
defer clt.Close()
|
||||
|
||||
resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{
|
||||
ResourceType: types.KindIdentityCenterAccount,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Resources, 1)
|
||||
})
|
||||
|
||||
t.Run("denied via specific kind beats allow via generic kind", func(t *testing.T) {
|
||||
user, _, err := CreateUserAndRole(srv.Auth(), "specific-beats-generic", nil,
|
||||
[]types.Rule{
|
||||
types.NewRule(types.KindIdentityCenter, services.RO()),
|
||||
},
|
||||
WithRoleMutator(func(r types.Role) {
|
||||
setAccountAssignment(r)
|
||||
r.SetRules(types.Deny, []types.Rule{
|
||||
types.NewRule(types.KindIdentityCenterAccount, services.RO()),
|
||||
})
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := TestUser(user.GetName())
|
||||
clt, err := srv.NewClient(identity)
|
||||
require.NoError(t, err)
|
||||
defer clt.Close()
|
||||
|
||||
_, err = clt.ListResources(ctx, proto.ListResourcesRequest{
|
||||
ResourceType: types.KindIdentityCenterAccount,
|
||||
})
|
||||
require.True(t, trace.IsAccessDenied(err),
|
||||
"Expected Access Denied, got %v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkListUnifiedResourcesFilter(b *testing.B) {
|
||||
|
||||
@@ -444,6 +444,18 @@ func (a *accessChecker) CheckAccess(r AccessCheckable, state AccessState, matche
|
||||
if err := a.checkAllowedResources(r); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
switch rr := r.(type) {
|
||||
case types.Resource153Unwrapper:
|
||||
switch urr := rr.Unwrap().(type) {
|
||||
case IdentityCenterAccount:
|
||||
matchers = append(matchers, NewIdentityCenterAccountMatcher(urr))
|
||||
|
||||
case IdentityCenterAccountAssignment:
|
||||
matchers = append(matchers, NewIdentityCenterAccountAssignmentMatcher(urr))
|
||||
}
|
||||
}
|
||||
|
||||
return trace.Wrap(a.RoleSet.checkAccess(r, a.info.Traits, state, matchers...))
|
||||
}
|
||||
|
||||
|
||||
@@ -2191,19 +2191,32 @@ func (m *RequestValidator) pruneResourceRequestRoles(
|
||||
necessaryRoles := make(map[string]struct{})
|
||||
for _, resource := range resources {
|
||||
var (
|
||||
rolesForResource []types.Role
|
||||
resourceMatcher *KubeResourcesMatcher
|
||||
rolesForResource []types.Role
|
||||
matchers []RoleMatcher
|
||||
kubeResourceMatcher *KubeResourcesMatcher
|
||||
)
|
||||
kubernetesResources, err := getKubeResourcesFromResourceIDs(resourceIDs, resource.GetName())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if len(kubernetesResources) > 0 {
|
||||
resourceMatcher = NewKubeResourcesMatcher(kubernetesResources)
|
||||
kubeResourceMatcher = NewKubeResourcesMatcher(kubernetesResources)
|
||||
matchers = append(matchers, kubeResourceMatcher)
|
||||
}
|
||||
|
||||
switch rr := resource.(type) {
|
||||
case types.Resource153Unwrapper:
|
||||
switch urr := rr.Unwrap().(type) {
|
||||
case IdentityCenterAccount:
|
||||
matchers = append(matchers, NewIdentityCenterAccountMatcher(urr))
|
||||
|
||||
case IdentityCenterAccountAssignment:
|
||||
matchers = append(matchers, NewIdentityCenterAccountAssignmentMatcher(urr))
|
||||
}
|
||||
}
|
||||
|
||||
for _, role := range allRoles {
|
||||
roleAllowsAccess, err := m.roleAllowsResource(role, resource, loginHint, resourceMatcherToMatcherSlice(resourceMatcher)...)
|
||||
roleAllowsAccess, err := m.roleAllowsResource(role, resource, loginHint, matchers...)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -2217,7 +2230,7 @@ func (m *RequestValidator) pruneResourceRequestRoles(
|
||||
// If any of the requested resources didn't match with the provided roles,
|
||||
// we deny the request because the user is trying to request more access
|
||||
// than what is allowed by its search_as_roles.
|
||||
if resourceMatcher != nil && len(resourceMatcher.Unmatched()) > 0 {
|
||||
if kubeResourceMatcher != nil && len(kubeResourceMatcher.Unmatched()) > 0 {
|
||||
resourcesStr, err := types.ResourceIDsToString(resourceIDs)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -2226,7 +2239,7 @@ func (m *RequestValidator) pruneResourceRequestRoles(
|
||||
`no roles configured in the "search_as_roles" for this user allow `+
|
||||
`access to at least one requested resources. `+
|
||||
`resources: %s roles: %v unmatched resources: %v`,
|
||||
resourcesStr, roles, resourceMatcher.Unmatched())
|
||||
resourcesStr, roles, kubeResourceMatcher.Unmatched())
|
||||
}
|
||||
if len(loginHint) > 0 {
|
||||
// If we have a login hint, request the single role with the fewest
|
||||
@@ -2335,15 +2348,6 @@ func (m *RequestValidator) roleAllowsResource(
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// resourceMatcherToMatcherSlice returns the resourceMatcher in a RoleMatcher slice
|
||||
// if the resourceMatcher is not nil, otherwise returns a nil slice.
|
||||
func resourceMatcherToMatcherSlice(resourceMatcher *KubeResourcesMatcher) []RoleMatcher {
|
||||
if resourceMatcher == nil {
|
||||
return nil
|
||||
}
|
||||
return []RoleMatcher{resourceMatcher}
|
||||
}
|
||||
|
||||
// getUnderlyingResourcesByResourceIDs gets the underlying resources the user
|
||||
// requested access. Except for resource Kinds present in types.KubernetesResourcesKinds,
|
||||
// the underlying resources are the same as requested. If the resource requested
|
||||
|
||||
@@ -18,12 +18,15 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apiutils "github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/teleport/lib/utils/pagination"
|
||||
)
|
||||
|
||||
@@ -232,3 +235,105 @@ type IdentityCenter interface {
|
||||
IdentityCenterPrincipalAssignments
|
||||
IdentityCenterAccountAssignments
|
||||
}
|
||||
|
||||
// NewIdentityCenterAccountMatcher creates a new [IdentityCenterMatcher]
|
||||
// configured to match the supplied [IdentityCenterAccount].
|
||||
func NewIdentityCenterAccountMatcher(account IdentityCenterAccount) RoleMatcher {
|
||||
return &IdentityCenterAccountMatcher{
|
||||
accountID: account.GetSpec().GetId(),
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityCenterMatcher implements a [RoleMatcher] for comparing Identity Center
|
||||
// resources against the AccountAssignments specified in a Role condition.
|
||||
//
|
||||
// The same type is used for matching both [IdentityCenterAccount]s and
|
||||
// [IdentityCenterAccountAssignment]s, the permission set is `nil` when matching
|
||||
// an Account.
|
||||
type IdentityCenterAccountMatcher struct {
|
||||
accountID string
|
||||
}
|
||||
|
||||
// Match implements Role Matching for Identity Center Account resources. It
|
||||
// attempts to match the Account Assignments in a Role Condition against a
|
||||
// known Account ID.
|
||||
func (m *IdentityCenterAccountMatcher) Match(role types.Role, condition types.RoleConditionType) (bool, error) {
|
||||
// TODO(tcsc): Expand to cover role template expansion (e.g. {{external.account_assignments}})
|
||||
for _, asmt := range role.GetIdentityCenterAccountAssignments(condition) {
|
||||
accountMatches, err := matchExpression(m.accountID, asmt.Account)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if accountMatches {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *IdentityCenterAccountMatcher) String() string {
|
||||
return fmt.Sprintf("IdentityCenterAccountMatcher(account=%v)", m.accountID)
|
||||
}
|
||||
|
||||
// NewIdentityCenterAccountAssignmentMatcher creates a new [IdentityCenterAccountAssignmentMatcher]
|
||||
// configured to match the supplied [IdentityCenterAccountAssignment].
|
||||
func NewIdentityCenterAccountAssignmentMatcher(account IdentityCenterAccountAssignment) RoleMatcher {
|
||||
return &IdentityCenterAccountMatcher{
|
||||
accountID: account.GetSpec().GetAccountId(),
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityCenterMatcher implements a [RoleMatcher] for comparing Identity Center
|
||||
// resources against the AccountAssignments specified in a Role condition.
|
||||
//
|
||||
// The same type is used for matching both [IdentityCenterAccount]s and
|
||||
// [IdentityCenterAccountAssignment]s, the permission set is `nil` when matching
|
||||
// an Account.
|
||||
type IdentityCenterAccountAssignmentMatcher struct {
|
||||
accountID string
|
||||
permissionSetARN string
|
||||
}
|
||||
|
||||
// Match implements Role Matching for Identity Center Account resources. It
|
||||
// attempts to match the Account Assignments in a Role Condition against a
|
||||
// known Account ID.
|
||||
func (m *IdentityCenterAccountAssignmentMatcher) Match(role types.Role, condition types.RoleConditionType) (bool, error) {
|
||||
// TODO(tcsc): Expand to cover role template expansion (e.g. {{external.account_assignments}})
|
||||
for _, asmt := range role.GetIdentityCenterAccountAssignments(condition) {
|
||||
accountMatches, err := matchExpression(m.accountID, asmt.Account)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !accountMatches {
|
||||
continue
|
||||
}
|
||||
|
||||
permissionSetMatches, err := matchExpression(m.permissionSetARN, asmt.PermissionSet)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if permissionSetMatches {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *IdentityCenterAccountAssignmentMatcher) String() string {
|
||||
return fmt.Sprintf("IdentityCenterAccountMatcher(account=%v, permissionSet=%v)",
|
||||
m.accountID, m.permissionSetARN)
|
||||
}
|
||||
|
||||
func matchExpression(target, expression string) (bool, error) {
|
||||
if expression == types.Wildcard {
|
||||
return true, nil
|
||||
}
|
||||
matches, err := utils.MatchString(target, expression)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
@@ -95,3 +95,297 @@ func TestIdentityCenterAccountAssignmentClone(t *testing.T) {
|
||||
require.NotEqual(t, src, dst)
|
||||
require.Equal(t, "original name", dst.Spec.PermissionSet.Name)
|
||||
}
|
||||
|
||||
func TestIdentityCenterAccountMatcher(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
roleAssignments []types.IdentityCenterAccountAssignment
|
||||
condition types.RoleConditionType
|
||||
matcher RoleMatcher
|
||||
expectMatch require.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty nonmatch",
|
||||
roleAssignments: nil,
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "11111111",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "simple account match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "11111111",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "multiple account assignments match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{
|
||||
{
|
||||
Account: "00000000",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "11111111",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "simple account nonmatch",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "potato",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "multiple account assignments match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{
|
||||
{
|
||||
Account: "00000000",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "66666666",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "account glob match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "potato",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "account glob nonmatch",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*!",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountMatcher{
|
||||
accountID: "potato",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
roleSpec := types.RoleSpecV6{}
|
||||
condition := &roleSpec.Deny
|
||||
if testCase.condition == types.Allow {
|
||||
condition = &roleSpec.Allow
|
||||
}
|
||||
condition.AccountAssignments = append(condition.AccountAssignments,
|
||||
testCase.roleAssignments...)
|
||||
|
||||
r, err := types.NewRole("test", roleSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
match, err := testCase.matcher.Match(r, testCase.condition)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCase.expectMatch(t, match)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityCenterAccountAssignmentMatcher(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
roleAssignments []types.IdentityCenterAccountAssignment
|
||||
condition types.RoleConditionType
|
||||
matcher RoleMatcher
|
||||
expectMatch require.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty nonmatch",
|
||||
roleAssignments: nil,
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "simple match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "multiple match",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{
|
||||
{
|
||||
Account: "00000000",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "multiple nonmatch",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{
|
||||
{
|
||||
Account: "00000000",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
},
|
||||
},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "66666666",
|
||||
permissionSetARN: "some:other:arn",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "account glob",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*1",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "account glob nonmatch",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*!!!!",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "globbed",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*",
|
||||
PermissionSet: "*",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.True,
|
||||
},
|
||||
{
|
||||
name: "globbed nonmatch",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "*",
|
||||
PermissionSet: ":not:an:arn:*",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "bad account",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "potato",
|
||||
permissionSetARN: "some:arn",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
{
|
||||
name: "bad permissionset arn",
|
||||
roleAssignments: []types.IdentityCenterAccountAssignment{{
|
||||
Account: "11111111",
|
||||
PermissionSet: "some:arn",
|
||||
}},
|
||||
condition: types.Allow,
|
||||
matcher: &IdentityCenterAccountAssignmentMatcher{
|
||||
accountID: "11111111",
|
||||
permissionSetARN: "banana",
|
||||
},
|
||||
expectMatch: require.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
roleSpec := types.RoleSpecV6{}
|
||||
condition := &roleSpec.Deny
|
||||
if testCase.condition == types.Allow {
|
||||
condition = &roleSpec.Allow
|
||||
}
|
||||
condition.AccountAssignments = append(condition.AccountAssignments,
|
||||
testCase.roleAssignments...)
|
||||
|
||||
r, err := types.NewRole("test", roleSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
match, err := testCase.matcher.Match(r, testCase.condition)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCase.expectMatch(t, match)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+41
-21
@@ -2556,6 +2556,19 @@ func rbacDebugLogger() (debugEnabled bool, debugf func(format string, args ...in
|
||||
return
|
||||
}
|
||||
|
||||
// resourceRequiresLabelMatching decides if a resource requires lapel matching
|
||||
// when making RBAC access decisions.
|
||||
func resourceRequiresLabelMatching(r AccessCheckable) bool {
|
||||
// Some resources do not need label matching when assessing whether the user
|
||||
// should be granted access. Enable it by default, but turn it off in the
|
||||
// special cases.
|
||||
switch r.GetKind() {
|
||||
case types.KindIdentityCenterAccount, types.KindIdentityCenterAccountAssignment:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state AccessState, matchers ...RoleMatcher) error {
|
||||
// Note: logging in this function only happens in debug mode. This is because
|
||||
// adding logging to this function (which is called on every resource returned
|
||||
@@ -2567,6 +2580,7 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state
|
||||
return ErrSessionMFARequired
|
||||
}
|
||||
|
||||
requiresLabelMatching := resourceRequiresLabelMatching(r)
|
||||
namespace := types.ProcessNamespace(r.GetMetadata().Namespace)
|
||||
|
||||
// Additional message depending on kind of resource
|
||||
@@ -2589,18 +2603,20 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state
|
||||
if !matchNamespace {
|
||||
continue
|
||||
}
|
||||
|
||||
matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, traits, r, isDebugEnabled)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
if requiresLabelMatching {
|
||||
matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, traits, r, isDebugEnabled)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if matchLabels {
|
||||
debugf("Access to %v %q denied, deny rule in role %q matched; match(namespace=%v, %s)",
|
||||
r.GetKind(), r.GetName(), role.GetName(), namespaceMessage, labelsMessage)
|
||||
return trace.AccessDenied("access to %v denied. User does not have permissions. %v",
|
||||
r.GetKind(), additionalDeniedMessage)
|
||||
}
|
||||
} else {
|
||||
debugf("Role label matching skipped for %v %q", r.GetKind(), r.GetName())
|
||||
}
|
||||
if matchLabels {
|
||||
debugf("Access to %v %q denied, deny rule in role %q matched; match(namespace=%v, %s)",
|
||||
r.GetKind(), r.GetName(), role.GetName(), namespaceMessage, labelsMessage)
|
||||
return trace.AccessDenied("access to %v denied. User does not have permissions. %v",
|
||||
r.GetKind(), additionalDeniedMessage)
|
||||
}
|
||||
|
||||
// Deny rules are greedy on purpose. They will always match if
|
||||
// at least one of the matchers returns true.
|
||||
matchMatchers, matchersMessage, err := RoleMatchers(matchers).MatchAny(role, types.Deny)
|
||||
@@ -2634,17 +2650,21 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state
|
||||
continue
|
||||
}
|
||||
|
||||
matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, traits, r, isDebugEnabled)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !matchLabels {
|
||||
if isDebugEnabled {
|
||||
errs = append(errs, trace.AccessDenied("role=%v, match(%s)",
|
||||
role.GetName(), labelsMessage))
|
||||
if requiresLabelMatching {
|
||||
matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, traits, r, isDebugEnabled)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
continue
|
||||
|
||||
if !matchLabels {
|
||||
if isDebugEnabled {
|
||||
errs = append(errs, trace.AccessDenied("role=%v, match(%s)",
|
||||
role.GetName(), labelsMessage))
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
debugf("Role label matching skipped for %v %q", r.GetKind(), r.GetName())
|
||||
}
|
||||
|
||||
// Allow rules are not greedy. They will match only if all of the
|
||||
|
||||
Reference in New Issue
Block a user