AuthZ service: Support anonymous access (#98322)

support anonymous access
This commit is contained in:
Ieva 2024-12-20 15:32:57 +00:00 committed by GitHub
parent efb7cc0343
commit 1334caa6c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 102 additions and 52 deletions

View File

@ -8,6 +8,10 @@ func userIdentifierCacheKeyById(namespace, ID string) string {
return "ID_" + namespace + "_" + ID
}
func anonymousPermCacheKey(namespace, action string) string {
return namespace + "_anonymous_" + action
}
func userPermCacheKey(namespace, userUID, action string) string {
return namespace + "_" + userUID + "_" + action
}

View File

@ -4,6 +4,7 @@ import "github.com/grafana/authlib/claims"
type CheckRequest struct {
Namespace claims.NamespaceInfo
IdentityType claims.IdentityType
UserUID string
Action string
Group string
@ -14,12 +15,13 @@ type CheckRequest struct {
}
type ListRequest struct {
Namespace claims.NamespaceInfo
UserUID string
Group string
Resource string
Verb string
Action string
Namespace claims.NamespaceInfo
IdentityType claims.IdentityType
UserUID string
Group string
Resource string
Verb string
Action string
}
type FolderNode struct {

View File

@ -86,7 +86,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.UserUID, checkReq.Action)
permissions, err := s.getUserPermissions(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
@ -112,7 +112,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.UserUID, listReq.Action)
permissions, err := s.getUserPermissions(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
@ -127,7 +127,7 @@ func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRe
return nil, err
}
userUID, err := s.validateSubject(ctx, req.GetSubject())
userUID, idType, err := s.validateSubject(ctx, req.GetSubject())
if err != nil {
return nil, err
}
@ -140,6 +140,7 @@ func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRe
checkReq := &CheckRequest{
Namespace: ns,
UserUID: userUID,
IdentityType: idType,
Action: action,
Group: req.GetGroup(),
Resource: req.GetResource(),
@ -156,7 +157,7 @@ func (s *Service) validateListRequest(ctx context.Context, req *authzv1.ListRequ
return nil, err
}
userUID, err := s.validateSubject(ctx, req.GetSubject())
userUID, idType, err := s.validateSubject(ctx, req.GetSubject())
if err != nil {
return nil, err
}
@ -167,12 +168,13 @@ func (s *Service) validateListRequest(ctx context.Context, req *authzv1.ListRequ
}
listReq := &ListRequest{
Namespace: ns,
UserUID: userUID,
Action: action,
Group: req.GetGroup(),
Resource: req.GetResource(),
Verb: req.GetVerb(),
Namespace: ns,
UserUID: userUID,
IdentityType: idType,
Action: action,
Group: req.GetGroup(),
Resource: req.GetResource(),
Verb: req.GetVerb(),
}
return listReq, nil
}
@ -196,21 +198,22 @@ func validateNamespace(ctx context.Context, nameSpace string) (claims.NamespaceI
return ns, nil
}
func (s *Service) validateSubject(ctx context.Context, subject string) (string, error) {
func (s *Service) validateSubject(ctx context.Context, subject string) (string, claims.IdentityType, error) {
if subject == "" {
return "", status.Error(codes.InvalidArgument, "subject is required")
return "", "", status.Error(codes.InvalidArgument, "subject is required")
}
ctxLogger := s.logger.FromContext(ctx)
identityType, userUID, err := claims.ParseTypeID(subject)
if err != nil {
return "", err
return "", "", err
}
// Permission check currently only checks user and service account permissions, so might return a false negative for other types
if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount) {
ctxLogger.Warn("unsupported identity type", "type", identityType)
// Permission check currently only checks user, anonymous user and service account permissions
if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount || identityType == claims.TypeAnonymous) {
ctxLogger.Error("unsupported identity type", "type", identityType)
return "", "", status.Error(codes.PermissionDenied, "unsupported identity type")
}
return userUID, nil
return userUID, identityType, nil
}
func (s *Service) validateAction(ctx context.Context, group, resource, verb string) (string, error) {
@ -226,7 +229,11 @@ func (s *Service) validateAction(ctx context.Context, group, resource, verb stri
return action, nil
}
func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, userID, action string) (map[string]bool, error) {
func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, idType claims.IdentityType, userID, action string) (map[string]bool, error) {
if idType == claims.TypeAnonymous {
return s.getAnonymousPermissions(ctx, ns, action)
}
userIdentifiers, err := s.GetUserIdentifiers(ctx, ns, userID)
if err != nil {
return nil, err
@ -264,6 +271,21 @@ func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInf
return scopeMap, nil
}
func (s *Service) getAnonymousPermissions(ctx context.Context, ns claims.NamespaceInfo, action string) (map[string]bool, error) {
anonPermKey := anonymousPermCacheKey(ns.Value, action)
if cached, ok := s.permCache.Get(anonPermKey); ok {
return cached.(map[string]bool), nil
}
permissions, err := s.store.GetUserPermissions(ctx, ns, store.PermissionsQuery{Action: action, Role: "Viewer"})
if err != nil {
return nil, err
}
scopeMap := getScopeMap(permissions)
s.permCache.Set(anonPermKey, scopeMap, 0)
return scopeMap, 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

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

View File

@ -1,13 +1,15 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM {{ .Ident .PermissionTable }} as p
WHERE p.action = {{ .Arg .Query.Action }} AND p.role_id IN (
SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0))
{{ if .Query.IsServerAdmin }}
OR (br.role = 'Grafana Admin')
{{ end }}
{{ if .Query.UserID }}
UNION
SELECT role_id FROM {{ .Ident .UserRoleTable }} as ur WHERE ur.user_id = {{ .Arg .Query.UserID }} AND (ur.org_id = {{ .Arg .Query.OrgID }} OR ur.org_id = 0)
{{ if .Query.TeamIDs }}
{{ end }}
{{ if .Query.TeamIDs }}
UNION
SELECT role_id FROM {{ .Ident .TeamRoleTable }} as tr WHERE tr.team_id IN ({{ .ArgList .Query.TeamIDs }}) AND tr.org_id = {{ .Arg .Query.OrgID }}
{{ end }}
UNION ALL
SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0))
{{ if .Query.IsServerAdmin }}
OR (br.role = 'Grafana Admin')
{{ end }}
)

View File

@ -97,6 +97,14 @@ func TestIdentityQueries(t *testing.T) {
TeamIDs: []int64{1, 2},
}),
},
{
Name: "anonymous_user",
Data: getPermissions(&PermissionsQuery{
OrgID: 1,
Action: "folders:read",
Role: "Viewer",
}),
},
},
sqlFolders: {
{

View File

@ -1,7 +1,7 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
OR (br.role = 'Grafana Admin')
OR (br.role = 'Grafana Admin')
UNION
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)

View File

@ -0,0 +1,4 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,8 +1,8 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION
SELECT role_id FROM `grafana`.`team_role` as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
UNION ALL
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,6 +1,6 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)

View File

@ -1,7 +1,7 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
OR (br.role = 'Grafana Admin')
OR (br.role = 'Grafana Admin')
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)

View File

@ -0,0 +1,4 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,8 +1,8 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,6 +1,6 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)

View File

@ -1,7 +1,7 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
OR (br.role = 'Grafana Admin')
OR (br.role = 'Grafana Admin')
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)

View File

@ -0,0 +1,4 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,8 +1,8 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
)

View File

@ -1,6 +1,6 @@
SELECT p.action, p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
WHERE p.action = 'folders:read' AND p.role_id IN (
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
UNION ALL
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
UNION
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
)