MT AuthZ: Resolve renderer permissions in MT authZ service (#99362)

* resolve renderer permissions in MT authZ service

* also include DS read perms

* fix tests and linting
This commit is contained in:
Ieva 2025-01-23 10:21:43 +00:00 committed by GitHub
parent 4fb7b47971
commit 723fa7ddf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 12 deletions

View File

@ -41,9 +41,13 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
}
if renderUsr.UserID <= 0 {
identityType := claims.TypeAnonymous
if org.RoleType(renderUsr.OrgRole) == org.RoleAdmin {
identityType = claims.TypeRenderService
}
return &authn.Identity{
ID: "0",
Type: claims.TypeRenderService,
Type: identityType,
OrgID: renderUsr.OrgID,
OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)},
ClientParams: authn.ClientParams{SyncPermissions: true},

View File

@ -29,7 +29,29 @@ func TestRender_Authenticate(t *testing.T) {
tests := []TestCase{
{
desc: "expect valid render key to return render user identity",
desc: "expect valid render key to return anonymous user identity for org role Viewer",
renderKey: "123",
req: &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{"Cookie": {"renderKey=123"}},
},
},
expectedIdentity: &authn.Identity{
ID: "0",
Type: claims.TypeAnonymous,
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{SyncPermissions: true},
},
expectedRenderUsr: &rendering.RenderUser{
OrgID: 1,
UserID: 0,
OrgRole: "Viewer",
},
},
{
desc: "expect valid render key to return render user identity for org role Admin",
renderKey: "123",
req: &authn.Request{
HTTPRequest: &http.Request{
@ -40,14 +62,14 @@ func TestRender_Authenticate(t *testing.T) {
ID: "0",
Type: claims.TypeRenderService,
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{SyncPermissions: true},
},
expectedRenderUsr: &rendering.RenderUser{
OrgID: 1,
UserID: 0,
OrgRole: "Viewer",
OrgRole: "Admin",
},
},
{

View File

@ -98,7 +98,7 @@ func (s *Service) Check(ctx context.Context, req *authzv1.CheckRequest) (*authzv
}
ctx = request.WithNamespace(ctx, req.GetNamespace())
permissions, err := s.getUserPermissions(ctx, checkReq.Namespace, checkReq.IdentityType, checkReq.UserUID, checkReq.Action)
permissions, err := s.getIdentityPermissions(ctx, checkReq.Namespace, checkReq.IdentityType, checkReq.UserUID, checkReq.Action)
if err != nil {
ctxLogger.Error("could not get user permissions", "subject", req.GetSubject(), "error", err)
return deny, err
@ -124,7 +124,7 @@ func (s *Service) List(ctx context.Context, req *authzv1.ListRequest) (*authzv1.
}
ctx = request.WithNamespace(ctx, req.GetNamespace())
permissions, err := s.getUserPermissions(ctx, listReq.Namespace, listReq.IdentityType, listReq.UserUID, listReq.Action)
permissions, err := s.getIdentityPermissions(ctx, listReq.Namespace, listReq.IdentityType, listReq.UserUID, listReq.Action)
if err != nil {
ctxLogger.Error("could not get user permissions", "subject", req.GetSubject(), "error", err)
return nil, err
@ -226,8 +226,8 @@ func (s *Service) validateSubject(ctx context.Context, subject string) (string,
if err != nil {
return "", "", err
}
// Permission check currently only checks user, anonymous user and service account permissions
if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount || identityType == claims.TypeAnonymous) {
// Permission check currently only checks user, anonymous user, service account and renderer permissions
if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount || identityType == claims.TypeAnonymous || identityType == claims.TypeRenderService) {
ctxLogger.Error("unsupported identity type", "type", identityType)
return "", "", status.Error(codes.PermissionDenied, "unsupported identity type")
}
@ -252,8 +252,8 @@ func (s *Service) validateAction(ctx context.Context, group, resource, verb stri
return action, nil
}
func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, idType claims.IdentityType, userID, action string) (map[string]bool, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getUserPermissions")
func (s *Service) getIdentityPermissions(ctx context.Context, ns claims.NamespaceInfo, idType claims.IdentityType, userID, action string) (map[string]bool, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getIdentityPermissions")
defer span.End()
// When checking folder creation permissions, also check edit and admin action sets for folder, as the scoped folder create actions aren't stored in the DB separately
@ -263,9 +263,21 @@ func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInf
actionSets = append(actionSets, "folders:admin")
}
if idType == claims.TypeAnonymous {
switch idType {
case claims.TypeAnonymous:
return s.getAnonymousPermissions(ctx, ns, action, actionSets)
case claims.TypeRenderService:
return s.getRendererPermissions(ctx, action)
case claims.TypeUser, claims.TypeServiceAccount:
return s.getUserPermissions(ctx, ns, userID, action, actionSets)
default:
return nil, fmt.Errorf("unsupported identity type: %s", idType)
}
}
func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, userID, action string, actionSets []string) (map[string]bool, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getUserPermissions")
defer span.End()
userIdentifiers, err := s.GetUserIdentifiers(ctx, ns, userID)
if err != nil {
@ -342,6 +354,17 @@ func (s *Service) getAnonymousPermissions(ctx context.Context, ns claims.Namespa
return res.(map[string]bool), nil
}
// Renderer is granted permissions to read all dashboards and folders, and no other permissions
func (s *Service) getRendererPermissions(ctx context.Context, action string) (map[string]bool, error) {
_, span := s.tracer.Start(ctx, "authz_direct_db.service.getRendererPermissions")
defer span.End()
if action == "dashboards:read" || action == "folders:read" || action == "datasources:read" {
return map[string]bool{"*": true}, nil
}
return map[string]bool{}, nil
}
func (s *Service) GetUserIdentifiers(ctx context.Context, ns claims.NamespaceInfo, userUID string) (*store.UserIdentifiers, error) {
uidCacheKey := userIdentifierCacheKey(ns.Value, userUID)
if cached, ok := s.idCache.Get(uidCacheKey); ok {

View File

@ -379,7 +379,7 @@ func TestService_getUserPermissions(t *testing.T) {
teamCache: localcache.New(shortCacheTTL, shortCleanupInterval),
}
perms, err := s.getUserPermissions(ctx, ns, claims.TypeUser, userID.UID, action)
perms, err := s.getIdentityPermissions(ctx, ns, claims.TypeUser, userID.UID, action)
require.NoError(t, err)
require.Len(t, perms, len(tc.expectedPerms))
for _, perm := range tc.permissions {