Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1469,12 +1469,10 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
Kinds: req.Kinds,
}

// resourceAccessMap is a map of resourceKind to error, that holds any errors returned when checking verbs
// for that resource (list/read)
resourceAccessMap := make(map[string]error)

// we want to populate the resourceAccessMap at the start of the request for all available kind in the
// unified resource cache
// Populate resourceAccessMap with any access errors the user has for each possible
// resource kind. This allows the access check to occur a single time per resource
// kind instead of once per matching resource.
resourceAccessMap := make(map[string]error, len(services.UnifiedResourceKinds))
for _, kind := range services.UnifiedResourceKinds {
actionVerbs := []string{types.VerbList, types.VerbRead}
if kind == types.KindNode {
Expand All @@ -1489,6 +1487,24 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
resourceAccessMap[kind] = a.action(apidefaults.Namespace, kind, actionVerbs...)
}

// Before doing any listing, verify that the user is allowed to list
// at least one of the requested kinds. If no access is permitted, then
// return an access denied error.
requested := req.Kinds
if len(req.Kinds) == 0 {
requested = services.UnifiedResourceKinds
}
var rbacErrors int
for _, kind := range requested {
if err, ok := resourceAccessMap[kind]; ok && err != nil {
rbacErrors++
}
}

if rbacErrors == len(requested) {
return nil, trace.AccessDenied("User does not have access to any of the requested kinds: %v", requested)
}

// Apply any requested additional search_as_roles and/or preview_as_roles
// for the duration of the search.
if req.UseSearchAsRoles || req.UsePreviewAsRoles {
Expand Down
29 changes: 29 additions & 0 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4579,6 +4579,35 @@ func TestListUnifiedResources_MixedAccess(t *testing.T) {
r := resource.GetDatabaseServer()
require.Equal(t, types.KindDatabaseServer, r.GetKind())
}

// Update the roles to prevent access to any resource kinds.
role.SetRules(types.Deny, []types.Rule{{Resources: services.UnifiedResourceKinds, Verbs: []string{types.VerbList, types.VerbRead}}})
err = srv.Auth().UpsertRole(ctx, role)
require.NoError(t, err)

// ensure updated roles have propagated to auth cache
flushCache(t, srv.Auth())

// Get a new client to test with the new roles.
clt, err = srv.NewClient(identity)
require.NoError(t, err)

// Validate that an error is returned when no kinds are requested.
resp, err = clt.ListUnifiedResources(ctx, &proto.ListUnifiedResourcesRequest{
Limit: 20,
SortBy: types.SortBy{IsDesc: true, Field: types.ResourceMetadataName},
})
require.True(t, trace.IsAccessDenied(err))
require.Nil(t, resp)

// Validate that an error is returned when a subset of kinds are requested.
resp, err = clt.ListUnifiedResources(ctx, &proto.ListUnifiedResourcesRequest{
Limit: 20,
SortBy: types.SortBy{IsDesc: true, Field: types.ResourceMetadataName},
Kinds: []string{types.KindNode, types.KindDatabaseServer},
})
require.True(t, trace.IsAccessDenied(err))
require.Nil(t, resp)
}

// TestListUnifiedResources_WithPredicate will return resources that match the
Expand Down