mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Fix per-receiver RBAC for receivers with long names (#95084)
* Implement uidToResourceID * add middleware * Move uidToResourceID to alerting package * Only hash uid if it's too long * Use hashed uid in access control * Move ReceiverUidToResourceId to ScopeProvider * resolve uid in middleware only if param exists * Tests * Linting --------- Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
parent
25e85f8947
commit
4aad44e848
@ -76,12 +76,13 @@ type ReceiverPermissionsService struct {
|
||||
// SetDefaultPermissions sets the default permissions for a newly created receiver.
|
||||
func (r ReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, orgID int64, user identity.Requester, uid string) {
|
||||
r.log.Debug("Setting default permissions for receiver", "receiver_uid", uid)
|
||||
resourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid)
|
||||
permissions := defaultPermissions()
|
||||
clearCache := false
|
||||
if user != nil && user.IsIdentityType(claims.TypeUser) {
|
||||
userID, err := user.GetInternalID()
|
||||
if err != nil {
|
||||
r.log.Error("Could not make user admin", "receiver_uid", uid, "id", user.GetID(), "error", err)
|
||||
r.log.Error("Could not make user admin", "receiver_uid", uid, "resource_id", resourceId, "id", user.GetID(), "error", err)
|
||||
} else {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
|
||||
UserID: userID, Permission: string(alertingac.ReceiverPermissionAdmin),
|
||||
@ -90,8 +91,8 @@ func (r ReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, o
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := r.SetPermissions(ctx, orgID, uid, permissions...); err != nil {
|
||||
r.log.Error("Could not set default permissions", "receiver_uid", uid, "error", err)
|
||||
if _, err := r.SetPermissions(ctx, orgID, resourceId, permissions...); err != nil {
|
||||
r.log.Error("Could not set default permissions", "receiver_uid", uid, "resource_id", resourceId, "id", "error", err)
|
||||
}
|
||||
|
||||
if clearCache {
|
||||
@ -120,13 +121,15 @@ func copyPermissionUser(orgID int64) identity.Requester {
|
||||
// name.
|
||||
func (r ReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID int64, user identity.Requester, oldUID, newUID string) (int, error) {
|
||||
r.log.Debug("Copying permissions from receiver", "old_uid", oldUID, "new_uid", newUID)
|
||||
currentPermissions, err := r.GetPermissions(ctx, copyPermissionUser(orgID), oldUID)
|
||||
oldResourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(oldUID)
|
||||
newResourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(newUID)
|
||||
currentPermissions, err := r.GetPermissions(ctx, copyPermissionUser(orgID), oldResourceId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
setPermissionCommands := r.toSetResourcePermissionCommands(currentPermissions)
|
||||
if _, err := r.SetPermissions(ctx, orgID, newUID, setPermissionCommands...); err != nil {
|
||||
if _, err := r.SetPermissions(ctx, orgID, newResourceId, setPermissionCommands...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@ -146,6 +149,10 @@ func (r ReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID i
|
||||
return countCustomPermissions(setPermissionCommands), nil
|
||||
}
|
||||
|
||||
func (r ReceiverPermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, uid string) error {
|
||||
return r.Service.DeleteResourcePermissions(ctx, orgID, alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid))
|
||||
}
|
||||
|
||||
// toSetResourcePermissionCommands converts a list of resource permissions to a list of set resource permission commands.
|
||||
// Only includes managed permissions.
|
||||
func (r ReceiverPermissionsService) toSetResourcePermissionCommands(permissions []accesscontrol.ResourcePermission) []accesscontrol.SetResourcePermissionCommand {
|
||||
|
@ -5,16 +5,18 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/resourcepermissions")
|
||||
@ -48,6 +50,9 @@ func (a *api) registerEndpoints() {
|
||||
if a.service.options.Resource == "teams" {
|
||||
teamUIDResolverResource = team.MiddlewareTeamUIDResolver(a.service.teamService, ":resourceID")
|
||||
}
|
||||
if a.service.options.Resource == "receivers" {
|
||||
teamUIDResolverResource = MiddlewareReceiverUIDResolver(":resourceID")
|
||||
}
|
||||
|
||||
a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) {
|
||||
actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource)
|
||||
@ -430,3 +435,13 @@ func permissionSetResponse(cmd setPermissionCommand) response.Response {
|
||||
}
|
||||
return response.Success(message)
|
||||
}
|
||||
|
||||
func MiddlewareReceiverUIDResolver(paramName string) web.Handler {
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
gotParams := web.Params(c.Req)
|
||||
if uid, ok := gotParams[paramName]; ok {
|
||||
gotParams[paramName] = alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid)
|
||||
web.SetURLParams(c.Req, gotParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,14 @@ 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 (
|
||||
@ -13,10 +17,30 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ScopeReceiversProvider = ac.NewScopeProvider(ScopeReceiversRoot)
|
||||
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
|
||||
|
||||
|
@ -851,7 +851,7 @@ func TestReceiverServiceAC_Read(t *testing.T) {
|
||||
emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
|
||||
recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
allReceivers := func() []models.Receiver {
|
||||
return []models.Receiver{recv1, recv2, recv3}
|
||||
}
|
||||
@ -990,7 +990,7 @@ func TestReceiverServiceAC_Create(t *testing.T) {
|
||||
emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
|
||||
recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
allReceivers := func() []models.Receiver {
|
||||
return []models.Receiver{recv1, recv2, recv3}
|
||||
}
|
||||
@ -1078,7 +1078,7 @@ func TestReceiverServiceAC_Update(t *testing.T) {
|
||||
emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
|
||||
recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
allReceivers := func() []models.Receiver {
|
||||
return []models.Receiver{recv1, recv2, recv3}
|
||||
}
|
||||
@ -1230,7 +1230,7 @@ func TestReceiverServiceAC_Delete(t *testing.T) {
|
||||
emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
|
||||
recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
|
||||
allReceivers := func() []models.Receiver {
|
||||
return []models.Receiver{recv1, recv2, recv3}
|
||||
}
|
||||
|
@ -481,6 +481,14 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
)...),
|
||||
})
|
||||
|
||||
// Test receivers with uids longer than 40 characters. User name is used in receiver name.
|
||||
adminLikeUserLongName := helper.CreateUser("adminLikeUserCreatingAReallyLongReceiverName", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
createWildcardPermission(append(
|
||||
[]string{accesscontrol.ActionAlertingReceiversCreate},
|
||||
ossaccesscontrol.ReceiversAdminActions...,
|
||||
)...),
|
||||
})
|
||||
|
||||
// endregion
|
||||
|
||||
testCases := []testCase{
|
||||
@ -555,6 +563,15 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
canAdmin: true,
|
||||
canReadSecrets: true,
|
||||
},
|
||||
{
|
||||
user: adminLikeUserLongName,
|
||||
canRead: true,
|
||||
canCreate: true,
|
||||
canUpdate: true,
|
||||
canDelete: true,
|
||||
canAdmin: true,
|
||||
canReadSecrets: true,
|
||||
},
|
||||
}
|
||||
|
||||
admin := org1.Admin
|
||||
|
Loading…
Reference in New Issue
Block a user