RBAC: generated prefixed uids for external service role (#76601)

* Replace FixedRoleUID function with a common function to generate these prefixes

* Use common function to generate prefixed uid for external service accounts

Co-authored-by: Gabriel MABILLE <gabriel.mabille@grafana.com>

---------

Co-authored-by: Gabriel MABILLE <gabriel.mabille@grafana.com>
This commit is contained in:
Karl Persson 2023-10-16 13:12:16 +02:00 committed by GitHub
parent bc98f3d139
commit ae5e03034b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 47 deletions

View File

@ -2,9 +2,7 @@ package database
import ( import (
"context" "context"
// #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes
"crypto/sha1"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@ -13,24 +11,13 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
) )
// extServiceRoleUID generates a 160 bit unique ID using SHA-1 that fits within the 40 characters limit of the role UID.
func extServiceRoleUID(externalServiceID string) string {
uid := fmt.Sprintf("%s%s_permissions", accesscontrol.ExternalServiceRoleUIDPrefix, externalServiceID)
// #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes
hasher := sha1.New()
hasher.Write([]byte(uid))
return hex.EncodeToString(hasher.Sum(nil))
}
func extServiceRoleName(externalServiceID string) string { func extServiceRoleName(externalServiceID string) string {
name := fmt.Sprintf("%s%s:permissions", accesscontrol.ExternalServiceRolePrefix, externalServiceID) name := fmt.Sprintf("%s%s:permissions", accesscontrol.ExternalServiceRolePrefix, externalServiceID)
return name return name
} }
func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error {
uid := extServiceRoleUID(externalServiceID) uid := accesscontrol.PrefixedRoleUID(extServiceRoleName(externalServiceID))
return s.sql.WithDbSession(ctx, func(sess *db.Session) error { return s.sql.WithDbSession(ctx, func(sess *db.Session) error {
stored, errGet := getRoleByUID(ctx, sess, uid) stored, errGet := getRoleByUID(ctx, sess, uid)
if errGet != nil { if errGet != nil {
@ -90,11 +77,12 @@ func (s *AccessControlStore) SaveExternalServiceRole(ctx context.Context, cmd ac
} }
func genExternalServiceRole(cmd accesscontrol.SaveExternalServiceRoleCommand) accesscontrol.Role { func genExternalServiceRole(cmd accesscontrol.SaveExternalServiceRoleCommand) accesscontrol.Role {
name := extServiceRoleName(cmd.ExternalServiceID)
role := accesscontrol.Role{ role := accesscontrol.Role{
OrgID: cmd.OrgID, OrgID: cmd.OrgID,
Version: 1, Version: 1,
Name: extServiceRoleName(cmd.ExternalServiceID), Name: name,
UID: extServiceRoleUID(cmd.ExternalServiceID), UID: accesscontrol.PrefixedRoleUID(name),
DisplayName: fmt.Sprintf("External Service %s Permissions", cmd.ExternalServiceID), DisplayName: fmt.Sprintf("External Service %s Permissions", cmd.ExternalServiceID),
Description: fmt.Sprintf("External Service %s permissions", cmd.ExternalServiceID), Description: fmt.Sprintf("External Service %s permissions", cmd.ExternalServiceID),
Group: "External Service", Group: "External Service",

View File

@ -3,10 +3,8 @@ package database
import ( import (
"context" "context"
// #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes // #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes
"crypto/sha1"
"encoding/hex"
"errors" "errors"
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
@ -166,7 +164,7 @@ func TestAccessControlStore_SaveExternalServiceRole(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
errDBSession := s.sql.WithDbSession(ctx, func(sess *db.Session) error { errDBSession := s.sql.WithDbSession(ctx, func(sess *db.Session) error {
storedRole, err := getRoleByUID(ctx, sess, sha1Hash(fmt.Sprintf("externalservice_%s_permissions", tt.runs[i].cmd.ExternalServiceID))) storedRole, err := getRoleByUID(ctx, sess, accesscontrol.PrefixedRoleUID(extServiceRoleName(tt.runs[i].cmd.ExternalServiceID)))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, storedRole) require.NotNil(t, storedRole)
require.Equal(t, tt.runs[i].cmd.Global, storedRole.Global(), "Incorrect global state of the role") require.Equal(t, tt.runs[i].cmd.Global, storedRole.Global(), "Incorrect global state of the role")
@ -253,7 +251,7 @@ func TestAccessControlStore_DeleteExternalServiceRole(t *testing.T) {
} }
roleID := int64(-1) roleID := int64(-1)
err := s.sql.WithDbSession(ctx, func(sess *db.Session) error { err := s.sql.WithDbSession(ctx, func(sess *db.Session) error {
role, err := getRoleByUID(ctx, sess, extServiceRoleUID(tt.id)) role, err := getRoleByUID(ctx, sess, accesscontrol.PrefixedRoleUID(extServiceRoleName(tt.id)))
if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) { if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) {
return err return err
} }
@ -295,7 +293,7 @@ func TestAccessControlStore_DeleteExternalServiceRole(t *testing.T) {
// Role should be deleted // Role should be deleted
_ = s.sql.WithDbSession(ctx, func(sess *db.Session) error { _ = s.sql.WithDbSession(ctx, func(sess *db.Session) error {
storedRole, err := getRoleByUID(ctx, sess, extServiceRoleUID(tt.id)) storedRole, err := getRoleByUID(ctx, sess, accesscontrol.PrefixedRoleUID(extServiceRoleName(tt.id)))
require.ErrorIs(t, err, accesscontrol.ErrRoleNotFound) require.ErrorIs(t, err, accesscontrol.ErrRoleNotFound)
require.Nil(t, storedRole) require.Nil(t, storedRole)
return nil return nil
@ -303,9 +301,3 @@ func TestAccessControlStore_DeleteExternalServiceRole(t *testing.T) {
}) })
} }
} }
func sha1Hash(text string) string {
h := sha1.New()
_, _ = h.Write([]byte(text))
return hex.EncodeToString(h.Sum(nil))
}

View File

@ -319,22 +319,9 @@ func (cmd *SaveExternalServiceRoleCommand) Validate() error {
} }
const ( const (
GlobalOrgID = 0 GlobalOrgID = 0
FixedRolePrefix = "fixed:"
FixedRoleUIDPrefix = "fixed_"
ManagedRolePrefix = "managed:"
BasicRolePrefix = "basic:"
PluginRolePrefix = "plugins:"
ExternalServiceRolePrefix = "externalservice:"
BasicRoleUIDPrefix = "basic_"
ExternalServiceRoleUIDPrefix = "externalservice_"
RoleGrafanaAdmin = "Grafana Admin"
GeneralFolderUID = "general" GeneralFolderUID = "general"
RoleGrafanaAdmin = "Grafana Admin"
// Basic Role None
BasicRoleNoneUID = "basic_none"
BasicRoleNoneName = "basic:none"
// Permission actions // Permission actions

View File

@ -12,6 +12,24 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
const (
BasicRolePrefix = "basic:"
BasicRoleUIDPrefix = "basic_"
ExternalServiceRolePrefix = "extsvc:"
ExternalServiceRoleUIDPrefix = "extsvc_"
FixedRolePrefix = "fixed:"
FixedRoleUIDPrefix = "fixed_"
ManagedRolePrefix = "managed:"
PluginRolePrefix = "plugins:"
BasicRoleNoneUID = "basic_none"
BasicRoleNoneName = "basic:none"
)
// Roles definition // Roles definition
var ( var (
ldapReaderRole = RoleDTO{ ldapReaderRole = RoleDTO{
@ -256,13 +274,16 @@ func ConcatPermissions(permissions ...[]Permission) []Permission {
return perms return perms
} }
// FixedRoleUID generates a UID of 34 bytes: "fixed_" + base64(sha1(roleName)) // PrefixedRoleUID generates a uid from name with the same prefix.
func FixedRoleUID(roleName string) string { // Generated uid is 28 bytes + length of prefix: <prefix>_base64(sha1(roleName))
func PrefixedRoleUID(roleName string) string {
prefix := strings.Split(roleName, ":")[0] + "_"
// #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes // #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes
hasher := sha1.New() hasher := sha1.New()
hasher.Write([]byte(roleName)) hasher.Write([]byte(roleName))
return fmt.Sprintf("%s%s", FixedRoleUIDPrefix, base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))) return fmt.Sprintf("%s%s", prefix, base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)))
} }
// ValidateFixedRole errors when a fixed role does not match expected pattern // ValidateFixedRole errors when a fixed role does not match expected pattern