From 54a347463e2e61fcbaa54d11afe6754281d86d5b Mon Sep 17 00:00:00 2001 From: Misi Date: Wed, 24 Sep 2025 11:32:29 +0200 Subject: [PATCH] IAM: Use the new authorizer for the User resource (#111479) * Use the new authorizer for the User resource * Use accessClient * Update pkg/services/authz/rbac/mapper.go Co-authored-by: Gabriel MABILLE --------- Co-authored-by: Gabriel MABILLE --- pkg/registry/apis/iam/authorizer.go | 34 +----------------- pkg/registry/apis/iam/legacy/user.go | 8 ++--- pkg/registry/apis/iam/register.go | 4 +-- pkg/registry/apis/iam/user/rest_display.go | 4 +-- pkg/registry/apis/iam/user/store.go | 12 +++---- pkg/services/authz/rbac/mapper.go | 2 ++ pkg/services/authz/rbac/resolver.go | 41 ++++++++++++++++++++++ 7 files changed, 58 insertions(+), 47 deletions(-) diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 4fd5544bb56..78461f18aa1 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -23,7 +23,6 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth // Identity specific resources legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient) - resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = legacyAuthorizer resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = legacyAuthorizer resourceAuthorizer["display"] = legacyAuthorizer @@ -33,6 +32,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer} } @@ -53,25 +53,6 @@ func (s *iamAuthorizer) Authorize(ctx context.Context, attr authorizer.Attribute func newLegacyAccessClient(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) authlib.AccessClient { client := accesscontrol.NewLegacyAccessClient( ac, - accesscontrol.ResourceAuthorizerOptions{ - Resource: iamv0.UserResourceInfo.GetName(), - Attr: "id", - Mapping: map[string]string{ - utils.VerbCreate: accesscontrol.ActionUsersCreate, - utils.VerbDelete: accesscontrol.ActionUsersDelete, - utils.VerbGet: accesscontrol.ActionOrgUsersRead, - utils.VerbList: accesscontrol.ActionOrgUsersRead, - }, - Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) { - res, err := store.GetUserInternalID(ctx, ns, legacy.GetUserInternalIDQuery{ - UID: name, - }) - if err != nil { - return nil, err - } - return []string{fmt.Sprintf("users:id:%d", res.ID)}, nil - }), - }, accesscontrol.ResourceAuthorizerOptions{ Resource: "display", Unchecked: map[string]bool{ @@ -79,19 +60,6 @@ func newLegacyAccessClient(ac accesscontrol.AccessControl, store legacy.LegacyId utils.VerbList: true, }, }, - accesscontrol.ResourceAuthorizerOptions{ - Resource: iamv0.ServiceAccountResourceInfo.GetName(), - Attr: "id", - Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) { - res, err := store.GetServiceAccountInternalID(ctx, ns, legacy.GetServiceAccountInternalIDQuery{ - UID: name, - }) - if err != nil { - return nil, err - } - return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil - }), - }, accesscontrol.ResourceAuthorizerOptions{ Resource: iamv0.TeamResourceInfo.GetName(), Attr: "id", diff --git a/pkg/registry/apis/iam/legacy/user.go b/pkg/registry/apis/iam/legacy/user.go index d437879c3c6..358fbcfe71f 100644 --- a/pkg/registry/apis/iam/legacy/user.go +++ b/pkg/registry/apis/iam/legacy/user.go @@ -98,7 +98,7 @@ type ListUserQuery struct { } type ListUserResult struct { - Users []common.UserWithRole + Items []common.UserWithRole Continue int64 RV int64 } @@ -176,9 +176,9 @@ func (s *legacySQLStore) queryUsers(ctx context.Context, sql *legacysql.LegacyDa } lastID = u.ID - res.Users = append(res.Users, u) - if len(res.Users) > limit { - res.Users = res.Users[0 : len(res.Users)-1] + res.Items = append(res.Items, u) + if len(res.Items) > limit { + res.Items = res.Items[0 : len(res.Items)-1] res.Continue = lastID break } diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 228fc98b636..2910cfce37d 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -173,7 +173,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge // User store registration userResource := iamv0.UserResourceInfo - legacyStore := user.NewLegacyStore(b.store, b.legacyAccessClient, b.enableAuthnMutation) + legacyStore := user.NewLegacyStore(b.store, b.accessClient, b.enableAuthnMutation) storage[userResource.StoragePath()] = legacyStore if b.enableDualWriter { @@ -194,7 +194,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge // Service Accounts store registration serviceAccountResource := iamv0.ServiceAccountResourceInfo - saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.legacyAccessClient, b.enableAuthnMutation) + saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.accessClient, b.enableAuthnMutation) storage[serviceAccountResource.StoragePath()] = saLegacyStore if b.enableDualWriter { diff --git a/pkg/registry/apis/iam/user/rest_display.go b/pkg/registry/apis/iam/user/rest_display.go index b1b4232005e..551ab72d7f7 100644 --- a/pkg/registry/apis/iam/user/rest_display.go +++ b/pkg/registry/apis/iam/user/rest_display.go @@ -123,9 +123,9 @@ func (r *LegacyDisplayREST) handleDisplay(w http.ResponseWriter, req *http.Reque rsp := &iam.DisplayList{ Keys: keys.keys, InvalidKeys: keys.invalid, - Items: make([]iam.Display, 0, len(users.Users)+len(keys.disp)+1), + Items: make([]iam.Display, 0, len(users.Items)+len(keys.disp)+1), } - for _, user := range users.Users { + for _, user := range users.Items { disp := iam.Display{ Identity: iam.IdentityRef{ Type: authlib.TypeUser, diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index 893fa456c60..09ac48ac70c 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -75,11 +75,11 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation if err != nil { return nil, false, err } - if found == nil || len(found.Users) < 1 { + if found == nil || len(found.Items) < 1 { return nil, false, resource.NewNotFound(name) } - userToDelete := &found.Users[0] + userToDelete := &found.Items[0] if deleteValidation != nil { userObj := toUserItem(userToDelete, ns.Value) @@ -135,8 +135,8 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt return nil, err } - users := make([]iamv0alpha1.User, 0, len(found.Users)) - for _, u := range found.Users { + users := make([]iamv0alpha1.User, 0, len(found.Items)) + for _, u := range found.Items { users = append(users, toUserItem(&u, ns.Value)) } @@ -172,11 +172,11 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO if found == nil || err != nil { return nil, resource.NewNotFound(name) } - if len(found.Users) < 1 { + if len(found.Items) < 1 { return nil, resource.NewNotFound(name) } - obj := toUserItem(&found.Users[0], ns.Value) + obj := toUserItem(&found.Items[0], ns.Value) return &obj, nil } diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index 950287a1059..e688ef16e08 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -110,6 +110,8 @@ func NewMapperRegistry() MapperRegistry { "folders": newResourceTranslation("folders", "uid", true, false), }, "iam.grafana.app": { + // Users is a special case. We translate user permissions from id to uid based. + "users": newResourceTranslation("users", "uid", false, true), "serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, true), // Teams is a special case. We translate user permissions from id to uid based. "teams": newResourceTranslation("teams", "uid", false, true), diff --git a/pkg/services/authz/rbac/resolver.go b/pkg/services/authz/rbac/resolver.go index aaad1818852..56aacf539d6 100644 --- a/pkg/services/authz/rbac/resolver.go +++ b/pkg/services/authz/rbac/resolver.go @@ -119,6 +119,44 @@ func (s *Service) newTeamNameResolver(ctx context.Context, ns types.NamespaceInf }, nil } +func (s *Service) fetchUsers(ctx context.Context, ns types.NamespaceInfo) (map[int64]string, error) { + users, err := s.identityStore.ListUsers(ctx, ns, legacy.ListUserQuery{}) + if err != nil { + return nil, fmt.Errorf("could not fetch users: %w", err) + } + userIDs := make(map[int64]string, len(users.Items)) + for _, user := range users.Items { + userIDs[user.ID] = user.UID + } + return userIDs, nil +} + +// Should return an error if we fail to build the resolver. +func (s *Service) newUserNameResolver(ctx context.Context, ns types.NamespaceInfo) (ScopeResolverFunc, error) { + return func(scope string) (string, error) { + userIDs, err := s.fetchUsers(ctx, ns) + if err != nil { + return "", fmt.Errorf("could not build resolver: %w", err) + } + + userIDStr := strings.TrimPrefix(scope, "users:id:") + if userIDStr == "" { + return "", fmt.Errorf("user ID is empty") + } + if userIDStr == "*" { + return "users:uid:*", nil + } + userID, err := strconv.ParseInt(userIDStr, 10, 64) + if err != nil { + return "", fmt.Errorf("invalid user ID %s: %w", userIDStr, err) + } + if userName, ok := userIDs[userID]; ok { + return "users:uid:" + userName, nil + } + return "", fmt.Errorf("user ID %s not found", userIDStr) + }, nil +} + func permissionsDelegateResolverFunc(scope string) (string, error) { if strings.TrimPrefix(scope, "permissions:type:") == "delegate" { // The permissions:type:delegate scope does not have any discriminating value, @@ -138,6 +176,9 @@ func (s *Service) nameResolver(ctx context.Context, ns types.NamespaceInfo, scop if scopePrefix == "serviceaccounts:id:" { return s.newServiceAccountNameResolver(ctx, ns) } + if scopePrefix == "users:id:" { + return s.newUserNameResolver(ctx, ns) + } // No resolver found for the given scope prefix. return nil, nil }