461 lines
19 KiB
Go
461 lines
19 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"context"
|
|
// #nosec G505 Used only for shortening the uid, not for security purposes.
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
const (
|
|
ScopeReceiversRoot = "receivers"
|
|
)
|
|
|
|
var (
|
|
ScopeReceiversProvider = ReceiverScopeProvider{ac.NewScopeProvider(ScopeReceiversRoot)}
|
|
ScopeReceiversAll = ScopeReceiversProvider.GetResourceAllScope()
|
|
)
|
|
|
|
type ReceiverScopeProvider struct {
|
|
ac.ScopeProvider
|
|
}
|
|
|
|
func (p ReceiverScopeProvider) GetResourceScopeUID(uid string) string {
|
|
return ScopeReceiversProvider.ScopeProvider.GetResourceScopeUID(p.GetResourceIDFromUID(uid))
|
|
}
|
|
|
|
// GetResourceIDFromUID converts a receiver uid to a resource id. This is necessary as resource ids are limited to 40 characters.
|
|
// If the uid is already less than or equal to 40 characters, it is returned as is.
|
|
func (p ReceiverScopeProvider) GetResourceIDFromUID(uid string) string {
|
|
if len(uid) <= util.MaxUIDLength {
|
|
return uid
|
|
}
|
|
// #nosec G505 Used only for shortening the uid, not for security purposes.
|
|
h := sha1.New()
|
|
h.Write([]byte(uid))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
// ReceiverPermission is a type for representing a receiver permission.
|
|
type ReceiverPermission string
|
|
|
|
const (
|
|
ReceiverPermissionView ReceiverPermission = "View"
|
|
ReceiverPermissionEdit ReceiverPermission = "Edit"
|
|
ReceiverPermissionAdmin ReceiverPermission = "Admin"
|
|
)
|
|
|
|
var (
|
|
// Asserts pre-conditions for read access to redacted receivers. If this evaluates to false, the user cannot read any redacted receivers.
|
|
readRedactedReceiversPreConditionsEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsRead), // Global action for all AM config. Org scope.
|
|
ac.EvalPermission(ac.ActionAlertingReceiversRead), // Action for redacted receivers. UID scope.
|
|
readDecryptedReceiversPreConditionsEval,
|
|
)
|
|
// Asserts pre-conditions for read access to decrypted receivers. If this evaluates to false, the user cannot read any decrypted receivers.
|
|
readDecryptedReceiversPreConditionsEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets), // Action for decrypted receivers. UID scope.
|
|
)
|
|
|
|
// Asserts read-only access to all redacted receivers.
|
|
readRedactedAllReceiversEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversRead, ScopeReceiversAll),
|
|
readDecryptedAllReceiversEval,
|
|
)
|
|
// Asserts read-only access to all decrypted receivers.
|
|
readDecryptedAllReceiversEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets, ScopeReceiversAll),
|
|
)
|
|
|
|
// Asserts read-only access to a specific redacted receiver.
|
|
readRedactedReceiverEval = func(uid string) ac.Evaluator {
|
|
return ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversRead, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
readDecryptedReceiverEval(uid),
|
|
)
|
|
}
|
|
|
|
// Asserts read-only access to a specific decrypted receiver.
|
|
readDecryptedReceiverEval = func(uid string) ac.Evaluator {
|
|
return ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
)
|
|
}
|
|
|
|
// Asserts read-only access to list redacted receivers. // TODO: Remove this with fgac.
|
|
readRedactedReceiversListEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversList),
|
|
)
|
|
|
|
// Extra permissions that give read-only access to all redacted receivers when called from provisioning api.
|
|
provisioningExtraReadRedactedPermissions = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingProvisioningRead), // Global provisioning action for all AM config. Org scope.
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningRead), // Global provisioning action for receivers. Org scope.
|
|
provisioningExtraReadDecryptedPermissions,
|
|
)
|
|
|
|
// Extra permissions that give read-only access to all decrypted receivers when called from provisioning api.
|
|
provisioningExtraReadDecryptedPermissions = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), // Global provisioning action for all AM config + secrets. Org scope.
|
|
)
|
|
|
|
// Create
|
|
|
|
// Asserts pre-conditions for create access to receivers. If this evaluates to false, the user cannot create any receivers.
|
|
// Create has no scope, so these permissions are both necessary and sufficient to create any and all receivers.
|
|
createReceiversEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite), // Global action for all AM config. Org scope.
|
|
ac.EvalPermission(ac.ActionAlertingReceiversCreate), // Action for receivers. Org scope.
|
|
)
|
|
|
|
// Update
|
|
|
|
// Asserts pre-conditions for update access to receivers. If this evaluates to false, the user cannot update any receivers.
|
|
updateReceiversPreConditionsEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite), // Global action for all AM config. Org scope.
|
|
ac.EvalPermission(ac.ActionAlertingReceiversUpdate), // Action for receivers. UID scope.
|
|
)
|
|
|
|
// Asserts update access to all receivers.
|
|
updateAllReceiversEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversUpdate, ScopeReceiversAll),
|
|
)
|
|
|
|
// Asserts update access to a specific receiver.
|
|
updateReceiverEval = func(uid string) ac.Evaluator {
|
|
return ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversUpdate, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
)
|
|
}
|
|
|
|
// Delete
|
|
|
|
// Asserts pre-conditions for delete access to receivers. If this evaluates to false, the user cannot delete any receivers.
|
|
deleteReceiversPreConditionsEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite), // Global action for all AM config. Org scope.
|
|
ac.EvalPermission(ac.ActionAlertingReceiversDelete), // Action for receivers. UID scope.
|
|
)
|
|
|
|
// Asserts delete access to all receivers.
|
|
deleteAllReceiversEval = ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversDelete, ScopeReceiversAll),
|
|
)
|
|
|
|
// Asserts delete access to a specific receiver.
|
|
deleteReceiverEval = func(uid string) ac.Evaluator {
|
|
return ac.EvalAny(
|
|
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversDelete, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
)
|
|
}
|
|
|
|
// Admin
|
|
|
|
// Asserts pre-conditions for resource permissions access to receivers. If this evaluates to false, the user cannot modify permissions for any receivers.
|
|
permissionsReceiversPreConditionsEval = ac.EvalAll(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsRead), // Action for receivers. UID scope.
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsWrite), // Action for receivers. UID scope.
|
|
)
|
|
|
|
// Asserts resource permissions access to all receivers.
|
|
permissionsAllReceiversEval = ac.EvalAll(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsRead, ScopeReceiversAll),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsWrite, ScopeReceiversAll),
|
|
)
|
|
|
|
// Asserts resource permissions access to a specific receiver.
|
|
permissionsReceiverEval = func(uid string) ac.Evaluator {
|
|
return ac.EvalAll(
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsRead, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
ac.EvalPermission(ac.ActionAlertingReceiversPermissionsWrite, ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
|
)
|
|
}
|
|
)
|
|
|
|
type ReceiverAccess[T models.Identified] struct {
|
|
read actionAccess[T]
|
|
readDecrypted actionAccess[T]
|
|
create actionAccess[T]
|
|
update actionAccess[T]
|
|
delete actionAccess[T]
|
|
permissions actionAccess[T]
|
|
}
|
|
|
|
// NewReceiverAccess creates a new ReceiverAccess service. If includeProvisioningActions is true, the service will include
|
|
// permissions specific to the provisioning API.
|
|
func NewReceiverAccess[T models.Identified](a ac.AccessControl, includeProvisioningActions bool) *ReceiverAccess[T] {
|
|
rcvAccess := &ReceiverAccess[T]{
|
|
read: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "receiver",
|
|
action: "read",
|
|
authorizeSome: readRedactedReceiversPreConditionsEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return readRedactedReceiverEval(receiver.GetUID())
|
|
},
|
|
authorizeAll: readRedactedAllReceiversEval,
|
|
},
|
|
readDecrypted: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "decrypted receiver",
|
|
action: "read",
|
|
authorizeSome: readDecryptedReceiversPreConditionsEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return readDecryptedReceiverEval(receiver.GetUID())
|
|
},
|
|
authorizeAll: readDecryptedAllReceiversEval,
|
|
},
|
|
create: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "receiver",
|
|
action: "create",
|
|
authorizeSome: createReceiversEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return createReceiversEval
|
|
},
|
|
authorizeAll: createReceiversEval,
|
|
},
|
|
update: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "receiver",
|
|
action: "update",
|
|
authorizeSome: updateReceiversPreConditionsEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return updateReceiverEval(receiver.GetUID())
|
|
},
|
|
authorizeAll: updateAllReceiversEval,
|
|
},
|
|
delete: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "receiver",
|
|
action: "delete",
|
|
authorizeSome: deleteReceiversPreConditionsEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return deleteReceiverEval(receiver.GetUID())
|
|
},
|
|
authorizeAll: deleteAllReceiversEval,
|
|
},
|
|
permissions: actionAccess[T]{
|
|
genericService: genericService{
|
|
ac: a,
|
|
},
|
|
resource: "receiver",
|
|
action: "admin", // Essentially read+write receiver resource permissions.
|
|
authorizeSome: permissionsReceiversPreConditionsEval,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return permissionsReceiverEval(receiver.GetUID())
|
|
},
|
|
authorizeAll: permissionsAllReceiversEval,
|
|
},
|
|
}
|
|
|
|
// If this service is meant for the provisioning API, we include the provisioning actions as possible permissions.
|
|
if includeProvisioningActions {
|
|
extendAccessControl(&rcvAccess.read, ac.EvalAny, actionAccess[T]{
|
|
authorizeSome: provisioningExtraReadRedactedPermissions,
|
|
authorizeAll: provisioningExtraReadRedactedPermissions,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return provisioningExtraReadRedactedPermissions
|
|
},
|
|
})
|
|
extendAccessControl(&rcvAccess.readDecrypted, ac.EvalAny, actionAccess[T]{
|
|
authorizeSome: provisioningExtraReadDecryptedPermissions,
|
|
authorizeAll: provisioningExtraReadDecryptedPermissions,
|
|
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
|
return provisioningExtraReadDecryptedPermissions
|
|
},
|
|
})
|
|
}
|
|
|
|
// Write, delete, and permissions management should require read permissions.
|
|
extendAccessControl(&rcvAccess.update, ac.EvalAll, rcvAccess.read)
|
|
extendAccessControl(&rcvAccess.delete, ac.EvalAll, rcvAccess.read)
|
|
extendAccessControl(&rcvAccess.permissions, ac.EvalAll, rcvAccess.read)
|
|
|
|
return rcvAccess
|
|
}
|
|
|
|
// extendAccessControl extends the access control of base with the extension. The operator function is used to combine
|
|
// the authorization evaluators.
|
|
func extendAccessControl[T models.Identified](base *actionAccess[T], operator func(evaluator ...ac.Evaluator) ac.Evaluator, extension actionAccess[T]) {
|
|
// Prevent infinite recursion.
|
|
baseSome := base.authorizeSome
|
|
baseAll := base.authorizeAll
|
|
baseOne := base.authorizeOne
|
|
|
|
// Extend the access control of base with the extension.
|
|
base.authorizeSome = operator(extension.authorizeSome, baseSome)
|
|
base.authorizeAll = operator(extension.authorizeAll, baseAll)
|
|
base.authorizeOne = func(resource models.Identified) ac.Evaluator {
|
|
return operator(extension.authorizeOne(resource), baseOne(resource))
|
|
}
|
|
}
|
|
|
|
// HasList checks if user has access to list redacted receivers. Returns false if user does not have access.
|
|
func (s ReceiverAccess[T]) HasList(ctx context.Context, user identity.Requester) (bool, error) { // TODO: Remove this with fgac.
|
|
return s.read.HasAccess(ctx, user, readRedactedReceiversListEval)
|
|
}
|
|
|
|
// FilterRead filters the given list of receivers based on the read redacted access control permissions of the user.
|
|
// This method is preferred when many receivers need to be checked.
|
|
func (s ReceiverAccess[T]) FilterRead(ctx context.Context, user identity.Requester, receivers ...T) ([]T, error) {
|
|
return s.read.Filter(ctx, user, receivers...)
|
|
}
|
|
|
|
// AuthorizeRead checks if user has access to read a redacted receiver. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeRead(ctx context.Context, user identity.Requester, receiver T) error {
|
|
return s.read.Authorize(ctx, user, receiver)
|
|
}
|
|
|
|
// HasRead checks if user has access to read a redacted receiver. Returns false if user does not have access.
|
|
func (s ReceiverAccess[T]) HasRead(ctx context.Context, user identity.Requester, receiver T) (bool, error) {
|
|
return s.read.Has(ctx, user, receiver)
|
|
}
|
|
|
|
// FilterReadDecrypted filters the given list of receivers based on the read decrypted access control permissions of the user.
|
|
// This method is preferred when many receivers need to be checked.
|
|
func (s ReceiverAccess[T]) FilterReadDecrypted(ctx context.Context, user identity.Requester, receivers ...T) ([]T, error) {
|
|
return s.readDecrypted.Filter(ctx, user, receivers...)
|
|
}
|
|
|
|
// AuthorizeReadDecrypted checks if user has access to read a decrypted receiver.
|
|
func (s ReceiverAccess[T]) AuthorizeReadDecrypted(ctx context.Context, user identity.Requester, receiver T) error {
|
|
return s.readDecrypted.Authorize(ctx, user, receiver)
|
|
}
|
|
|
|
// HasReadDecrypted checks if user has access to read a decrypted receiver. Returns false if user does not have access.
|
|
func (s ReceiverAccess[T]) HasReadDecrypted(ctx context.Context, user identity.Requester, receiver T) (bool, error) {
|
|
return s.readDecrypted.Has(ctx, user, receiver)
|
|
}
|
|
|
|
// AuthorizeUpdate checks if user has access to update a receiver. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeUpdate(ctx context.Context, user identity.Requester, receiver T) error {
|
|
return s.update.Authorize(ctx, user, receiver)
|
|
}
|
|
|
|
// Global
|
|
|
|
// AuthorizeCreate checks if user has access to create receivers. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeCreate(ctx context.Context, user identity.Requester) error {
|
|
return s.create.AuthorizeAll(ctx, user)
|
|
}
|
|
|
|
// By UID
|
|
|
|
type identified struct {
|
|
uid string
|
|
}
|
|
|
|
func (i identified) GetUID() string {
|
|
return i.uid
|
|
}
|
|
|
|
// AuthorizeDeleteByUID checks if user has access to delete a receiver by uid. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeDeleteByUID(ctx context.Context, user identity.Requester, uid string) error {
|
|
return s.delete.Authorize(ctx, user, identified{uid: uid})
|
|
}
|
|
|
|
// AuthorizeReadByUID checks if user has access to read a redacted receiver by uid. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeReadByUID(ctx context.Context, user identity.Requester, uid string) error {
|
|
return s.read.Authorize(ctx, user, identified{uid: uid})
|
|
}
|
|
|
|
// AuthorizeUpdateByUID checks if user has access to update a receiver by uid. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeUpdateByUID(ctx context.Context, user identity.Requester, uid string) error {
|
|
return s.update.Authorize(ctx, user, identified{uid: uid})
|
|
}
|
|
|
|
// Preconditions
|
|
|
|
// AuthorizeReadSome checks if user has access to read some redacted receivers. Returns an error if user does not have access.
|
|
func (s ReceiverAccess[T]) AuthorizeReadSome(ctx context.Context, user identity.Requester) error {
|
|
return s.read.AuthorizePreConditions(ctx, user)
|
|
}
|
|
|
|
// All access permissions for a given receiver.
|
|
|
|
// Access returns the permission sets for a slice of receivers. The permission set includes secrets, write, and
|
|
// delete which corresponds the given user being able to read, write, and delete each given receiver.
|
|
func (s ReceiverAccess[T]) Access(ctx context.Context, user identity.Requester, receivers ...T) (map[string]models.ReceiverPermissionSet, error) {
|
|
basePerms := models.NewReceiverPermissionSet()
|
|
if err := s.readDecrypted.AuthorizePreConditions(ctx, user); err != nil {
|
|
basePerms.Set(models.ReceiverPermissionReadSecret, false) // Doesn't match the preconditions.
|
|
} else if err := s.readDecrypted.AuthorizeAll(ctx, user); err == nil {
|
|
basePerms.Set(models.ReceiverPermissionReadSecret, true) // Has access to all receivers.
|
|
}
|
|
|
|
if err := s.permissions.AuthorizePreConditions(ctx, user); err != nil {
|
|
basePerms.Set(models.ReceiverPermissionAdmin, false) // Doesn't match the preconditions.
|
|
} else if err := s.permissions.AuthorizeAll(ctx, user); err == nil {
|
|
basePerms.Set(models.ReceiverPermissionAdmin, true) // Has access to all receivers.
|
|
}
|
|
|
|
if err := s.update.AuthorizePreConditions(ctx, user); err != nil {
|
|
basePerms.Set(models.ReceiverPermissionWrite, false) // Doesn't match the preconditions.
|
|
} else if err := s.update.AuthorizeAll(ctx, user); err == nil {
|
|
basePerms.Set(models.ReceiverPermissionWrite, true) // Has access to all receivers.
|
|
}
|
|
|
|
if err := s.delete.AuthorizePreConditions(ctx, user); err != nil {
|
|
basePerms.Set(models.ReceiverPermissionDelete, false) // Doesn't match the preconditions.
|
|
} else if err := s.delete.AuthorizeAll(ctx, user); err == nil {
|
|
basePerms.Set(models.ReceiverPermissionDelete, true) // Has access to all receivers.
|
|
}
|
|
|
|
if basePerms.AllSet() {
|
|
// Shortcut for the case when all permissions are known based on preconditions.
|
|
result := make(map[string]models.ReceiverPermissionSet, len(receivers))
|
|
for _, rcv := range receivers {
|
|
result[rcv.GetUID()] = basePerms.Clone()
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
result := make(map[string]models.ReceiverPermissionSet, len(receivers))
|
|
for _, rcv := range receivers {
|
|
permSet := basePerms.Clone()
|
|
if _, ok := permSet.Has(models.ReceiverPermissionReadSecret); !ok {
|
|
err := s.readDecrypted.authorize(ctx, user, rcv) // Check permissions ignoring preconditions and all access.
|
|
permSet.Set(models.ReceiverPermissionReadSecret, err == nil)
|
|
}
|
|
|
|
if _, ok := permSet.Has(models.ReceiverPermissionAdmin); !ok {
|
|
err := s.permissions.authorize(ctx, user, rcv)
|
|
permSet.Set(models.ReceiverPermissionAdmin, err == nil)
|
|
}
|
|
|
|
if _, ok := permSet.Has(models.ReceiverPermissionWrite); !ok {
|
|
err := s.update.authorize(ctx, user, rcv)
|
|
permSet.Set(models.ReceiverPermissionWrite, err == nil)
|
|
}
|
|
|
|
if _, ok := permSet.Has(models.ReceiverPermissionDelete); !ok {
|
|
err := s.delete.authorize(ctx, user, rcv)
|
|
permSet.Set(models.ReceiverPermissionDelete, err == nil)
|
|
}
|
|
|
|
result[rcv.GetUID()] = permSet
|
|
}
|
|
return result, nil
|
|
}
|