Alerting: Protected fields for Contact points (#115442)
* Alerting: Protect sensitive fields of contact points from unauthorized modification - Introduce a new permission alert.notifications.receivers.protected:write. The permission is granted to contact point administrators. - Introduce field Protected to NotifierOption - Introduce DiffReport for models.Integrations with focus on Settings. The diff report is extended with methods that return all keys that are different between two settings. - Add new annotation 'grafana.com/access/CanModifyProtected' to Receiver model - Update receiver service to enforce the permission and return status 403 if unauthorized user modifies protected field - Update receiver testing API to enforce permission and return status 403 if unauthorized user modifies protected field. - Update UI to disable protected fields if user cannot modify them
This commit is contained in:
@@ -95,6 +95,26 @@ var (
|
||||
)
|
||||
}
|
||||
|
||||
// Asserts pre-conditions for access to modify protected fields of receivers. If this evaluates to false, the user cannot modify protected fields of any receivers.
|
||||
updateReceiversProtectedPreConditionsEval = ac.EvalAll(
|
||||
updateReceiversPreConditionsEval,
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected), // Action for receivers. UID scope.
|
||||
)
|
||||
|
||||
// Asserts access to modify protected fields of a specific receiver.
|
||||
updateReceiverProtectedEval = func(uid string) ac.Evaluator {
|
||||
return ac.EvalAll(
|
||||
updateReceiverEval(uid),
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected, models.ScopeReceiversProvider.GetResourceScopeUID(uid)),
|
||||
)
|
||||
}
|
||||
|
||||
// Asserts access to modify protected fields of all receivers.
|
||||
updateAllReceiverProtectedEval = ac.EvalAll(
|
||||
updateAllReceiversEval,
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected, models.ScopeReceiversAll),
|
||||
)
|
||||
|
||||
// Delete
|
||||
|
||||
// Asserts pre-conditions for delete access to receivers. If this evaluates to false, the user cannot delete any receivers.
|
||||
@@ -141,12 +161,13 @@ var (
|
||||
)
|
||||
|
||||
type ReceiverAccess[T models.Identified] struct {
|
||||
read actionAccess[T]
|
||||
readDecrypted actionAccess[T]
|
||||
create actionAccess[T]
|
||||
update actionAccess[T]
|
||||
delete actionAccess[T]
|
||||
permissions actionAccess[T]
|
||||
read actionAccess[T]
|
||||
readDecrypted actionAccess[T]
|
||||
create actionAccess[T]
|
||||
update actionAccess[T]
|
||||
updateProtected actionAccess[T]
|
||||
delete actionAccess[T]
|
||||
permissions actionAccess[T]
|
||||
}
|
||||
|
||||
// NewReceiverAccess creates a new ReceiverAccess service. If includeProvisioningActions is true, the service will include
|
||||
@@ -201,6 +222,18 @@ func NewReceiverAccess[T models.Identified](a ac.AccessControl, includeProvision
|
||||
},
|
||||
authorizeAll: updateAllReceiversEval,
|
||||
},
|
||||
updateProtected: actionAccess[T]{
|
||||
genericService: genericService{
|
||||
ac: a,
|
||||
},
|
||||
resource: "receiver",
|
||||
action: "update protected fields of", // this produces message "user is not authorized to update protected fields of X receiver"
|
||||
authorizeSome: updateReceiversProtectedPreConditionsEval,
|
||||
authorizeOne: func(receiver models.Identified) ac.Evaluator {
|
||||
return updateReceiverProtectedEval(receiver.GetUID())
|
||||
},
|
||||
authorizeAll: updateAllReceiverProtectedEval,
|
||||
},
|
||||
delete: actionAccess[T]{
|
||||
genericService: genericService{
|
||||
ac: a,
|
||||
@@ -311,6 +344,14 @@ func (s ReceiverAccess[T]) AuthorizeUpdate(ctx context.Context, user identity.Re
|
||||
return s.update.Authorize(ctx, user, receiver)
|
||||
}
|
||||
|
||||
func (s ReceiverAccess[T]) HasUpdateProtected(ctx context.Context, user identity.Requester, receiver T) (bool, error) {
|
||||
return s.updateProtected.Has(ctx, user, receiver)
|
||||
}
|
||||
|
||||
func (s ReceiverAccess[T]) AuthorizeUpdateProtected(ctx context.Context, user identity.Requester, receiver T) error {
|
||||
return s.updateProtected.Authorize(ctx, user, receiver)
|
||||
}
|
||||
|
||||
// Global
|
||||
|
||||
// AuthorizeCreate checks if user has access to create receivers. Returns an error if user does not have access.
|
||||
@@ -380,6 +421,12 @@ func (s ReceiverAccess[T]) Access(ctx context.Context, user identity.Requester,
|
||||
basePerms.Set(models.ReceiverPermissionDelete, true) // Has access to all receivers.
|
||||
}
|
||||
|
||||
if err := s.updateProtected.AuthorizePreConditions(ctx, user); err != nil {
|
||||
basePerms.Set(models.ReceiverPermissionModifyProtected, false)
|
||||
} else if err := s.updateProtected.AuthorizeAll(ctx, user); err == nil {
|
||||
basePerms.Set(models.ReceiverPermissionModifyProtected, true)
|
||||
}
|
||||
|
||||
if basePerms.AllSet() {
|
||||
// Shortcut for the case when all permissions are known based on preconditions.
|
||||
result := make(map[string]models.ReceiverPermissionSet, len(receivers))
|
||||
@@ -412,6 +459,11 @@ func (s ReceiverAccess[T]) Access(ctx context.Context, user identity.Requester,
|
||||
permSet.Set(models.ReceiverPermissionDelete, err == nil)
|
||||
}
|
||||
|
||||
if _, ok := permSet.Has(models.ReceiverPermissionModifyProtected); !ok {
|
||||
err := s.updateProtected.authorize(ctx, user, rcv)
|
||||
permSet.Set(models.ReceiverPermissionModifyProtected, err == nil)
|
||||
}
|
||||
|
||||
result[rcv.GetUID()] = permSet
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -204,6 +204,33 @@ func TestReceiverAccess(t *testing.T) {
|
||||
recv3.UID: permissions(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update protected cannot update receivers",
|
||||
user: newEmptyUser(
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversRead, Scope: models.ScopeReceiversAll},
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversAll},
|
||||
),
|
||||
expected: map[string]models.ReceiverPermissionSet{
|
||||
recv1.UID: permissions(),
|
||||
recv2.UID: permissions(),
|
||||
recv3.UID: permissions(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update protected receivers",
|
||||
user: newEmptyUser(
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversRead, Scope: models.ScopeReceiversAll},
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID)},
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversUpdate, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID)},
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversUpdate, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv2.UID)},
|
||||
ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID)},
|
||||
),
|
||||
expected: map[string]models.ReceiverPermissionSet{
|
||||
recv1.UID: permissions(models.ReceiverPermissionWrite, models.ReceiverPermissionModifyProtected),
|
||||
recv2.UID: permissions(models.ReceiverPermissionWrite),
|
||||
recv3.UID: permissions(),
|
||||
},
|
||||
},
|
||||
// Receiver delete.
|
||||
{
|
||||
name: "global receiver delete should have delete but no write",
|
||||
|
||||
Reference in New Issue
Block a user