grafana-iam: Implement resourcepermission update (#110891)
* first go at update implementation * template tests * SQL tests * more tests * set namespace for read resource permissions * fix a bug with perms being removed right after they're added * remove unwanted changes * fix tests and check error * PR feedback * Update pkg/registry/apis/iam/resourcepermission/sql.go --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
@@ -156,7 +156,7 @@ func (s *ResourcePermSqlBackend) getRbacAssignmentsWithTx(ctx context.Context, s
|
||||
}
|
||||
|
||||
// getResourcePermission retrieves a single ResourcePermission by its name in the format <group>-<resource>-<name> (e.g. dashboard.grafana.app-dashboards-ad5rwqs)
|
||||
func (s *ResourcePermSqlBackend) getResourcePermission(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, ns types.NamespaceInfo, name string) (*v0alpha1.ResourcePermission, error) {
|
||||
func (s *ResourcePermSqlBackend) getResourcePermission(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, tx *session.SessionTx, ns types.NamespaceInfo, name string) (*v0alpha1.ResourcePermission, error) {
|
||||
mapper, grn, err := s.splitResourceName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -168,11 +168,10 @@ func (s *ResourcePermSqlBackend) getResourcePermission(ctx context.Context, sql
|
||||
ActionSets: mapper.ActionSets(),
|
||||
}
|
||||
|
||||
var assignments []rbacAssignment
|
||||
err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(tx *session.SessionTx) error {
|
||||
assignments, err = s.getRbacAssignmentsWithTx(ctx, sql, tx, resourceQuery)
|
||||
return err
|
||||
})
|
||||
assignments, err := s.getRbacAssignmentsWithTx(ctx, sql, tx, resourceQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(assignments) == 0 {
|
||||
return nil, fmt.Errorf("resource permission %q: %w", resourceQuery.Scopes, errNotFound)
|
||||
@@ -263,10 +262,10 @@ func (s *ResourcePermSqlBackend) storeRbacAssignment(ctx context.Context, dbHelp
|
||||
|
||||
// buildRbacAssignments builds the list of assignments (role assignments and permissions) for a given ResourcePermission spec
|
||||
// It resolves user/team/service account UIDs to internal IDs for the role name and assignee subjectID
|
||||
func (s *ResourcePermSqlBackend) buildRbacAssignments(ctx context.Context, ns types.NamespaceInfo, mapper Mapper, v0ResourcePerm *v0alpha1.ResourcePermission, rbacScope string) ([]rbacAssignmentCreate, error) {
|
||||
assignments := make([]rbacAssignmentCreate, 0, len(v0ResourcePerm.Spec.Permissions))
|
||||
func (s *ResourcePermSqlBackend) buildRbacAssignments(ctx context.Context, ns types.NamespaceInfo, mapper Mapper, v0ResourcePerm []v0alpha1.ResourcePermissionspecPermission, rbacScope string) ([]rbacAssignmentCreate, error) {
|
||||
assignments := make([]rbacAssignmentCreate, 0, len(v0ResourcePerm))
|
||||
|
||||
for _, perm := range v0ResourcePerm.Spec.Permissions {
|
||||
for _, perm := range v0ResourcePerm {
|
||||
rbacActionSet, err := mapper.ActionSet(perm.Verb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -371,22 +370,11 @@ func (s *ResourcePermSqlBackend) existsResourcePermission(ctx context.Context, t
|
||||
func (s *ResourcePermSqlBackend) createResourcePermission(
|
||||
ctx context.Context, dbHelper *legacysql.LegacyDatabaseHelper, ns types.NamespaceInfo, mapper Mapper, grn *groupResourceName, v0ResourcePerm *v0alpha1.ResourcePermission,
|
||||
) (int64, error) {
|
||||
if v0ResourcePerm == nil {
|
||||
return 0, fmt.Errorf("resource permission cannot be nil")
|
||||
if err := validateCreateAndUpdateInput(v0ResourcePerm, grn); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(v0ResourcePerm.Spec.Permissions) == 0 {
|
||||
return 0, fmt.Errorf("resource permission must have at least one permission: %w", errInvalidSpec)
|
||||
}
|
||||
|
||||
// Validate that the group/resource/name in the name matches the spec
|
||||
if grn.Group != v0ResourcePerm.Spec.Resource.ApiGroup ||
|
||||
grn.Resource != v0ResourcePerm.Spec.Resource.Resource ||
|
||||
grn.Name != v0ResourcePerm.Spec.Resource.Name {
|
||||
return 0, fmt.Errorf("resource permission name does not match spec: %w", errInvalidSpec)
|
||||
}
|
||||
|
||||
assignments, err := s.buildRbacAssignments(ctx, ns, mapper, v0ResourcePerm, mapper.Scope(grn.Name))
|
||||
assignments, err := s.buildRbacAssignments(ctx, ns, mapper, v0ResourcePerm.Spec.Permissions, mapper.Scope(grn.Name))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -416,6 +404,116 @@ func (s *ResourcePermSqlBackend) createResourcePermission(
|
||||
|
||||
// Update
|
||||
|
||||
func (s *ResourcePermSqlBackend) updateResourcePermission(ctx context.Context, dbHelper *legacysql.LegacyDatabaseHelper, ns types.NamespaceInfo, mapper Mapper, grn *groupResourceName, v0ResourcePerm *v0alpha1.ResourcePermission) (int64, error) {
|
||||
if err := validateCreateAndUpdateInput(v0ResourcePerm, grn); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err := dbHelper.DB.GetSqlxSession().WithTransaction(ctx, func(tx *session.SessionTx) error {
|
||||
currentPerms, err := s.getResourcePermission(ctx, dbHelper, tx, ns, grn.string())
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotFound) {
|
||||
return fmt.Errorf("resource permissions not found: %w", errNotFound)
|
||||
}
|
||||
s.logger.Error("could not get resource permissions", "orgID", ns.OrgID, "scope", grn.Name, "error", err.Error())
|
||||
return fmt.Errorf("could not get the existing resource permissions for resource %s", grn.Name)
|
||||
}
|
||||
|
||||
permissionsToAdd, permissionsToRemove := diffPermissions(currentPerms.Spec.Permissions, v0ResourcePerm.Spec.Permissions)
|
||||
|
||||
if len(permissionsToRemove) > 0 {
|
||||
permsToRemove, err := s.buildRbacAssignments(ctx, ns, mapper, permissionsToRemove, mapper.Scope(grn.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, perm := range permsToRemove {
|
||||
removePermQuery, args, err := buildRemovePermissionQuery(dbHelper, perm.Scope, perm.Action, perm.RoleName, ns.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(ctx, removePermQuery, args...)
|
||||
if err != nil {
|
||||
s.logger.Error("could not remove role permission", "scope", perm.Scope, "role", perm.RoleName, "error", err.Error())
|
||||
return fmt.Errorf("could not remove role permission")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(permissionsToAdd) > 0 {
|
||||
permsToAdd, err := s.buildRbacAssignments(ctx, ns, mapper, permissionsToAdd, mapper.Scope(grn.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, assignment := range permsToAdd {
|
||||
if err := s.storeRbacAssignment(ctx, dbHelper, tx, ns.OrgID, assignment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Return a timestamp as resource version
|
||||
return timeNow().UnixMilli(), nil
|
||||
}
|
||||
|
||||
func diffPermissions(currentPermissions, desiredPermissions []v0alpha1.ResourcePermissionspecPermission) (permissionsToAdd, permissionsToRemove []v0alpha1.ResourcePermissionspecPermission) {
|
||||
for _, desired := range desiredPermissions {
|
||||
found := false
|
||||
for _, existing := range currentPermissions {
|
||||
if desired.Name == existing.Name && desired.Kind == existing.Kind && desired.Verb == existing.Verb {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
permissionsToAdd = append(permissionsToAdd, desired)
|
||||
}
|
||||
}
|
||||
|
||||
// Compile a list of permissions to remove
|
||||
for _, existing := range currentPermissions {
|
||||
found := false
|
||||
for _, desired := range desiredPermissions {
|
||||
if desired.Name == existing.Name && desired.Kind == existing.Kind && desired.Verb == existing.Verb {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
permissionsToRemove = append(permissionsToRemove, existing)
|
||||
}
|
||||
}
|
||||
|
||||
return permissionsToAdd, permissionsToRemove
|
||||
}
|
||||
|
||||
func validateCreateAndUpdateInput(v0ResourcePerm *v0alpha1.ResourcePermission, grn *groupResourceName) error {
|
||||
if v0ResourcePerm == nil {
|
||||
return fmt.Errorf("resource permission cannot be nil")
|
||||
}
|
||||
|
||||
if len(v0ResourcePerm.Spec.Permissions) == 0 {
|
||||
return fmt.Errorf("resource permission must have at least one permission: %w", errInvalidSpec)
|
||||
}
|
||||
|
||||
// Validate that the group/resource/name in the name matches the spec
|
||||
if grn.Group != v0ResourcePerm.Spec.Resource.ApiGroup ||
|
||||
grn.Resource != v0ResourcePerm.Spec.Resource.Resource ||
|
||||
grn.Name != v0ResourcePerm.Spec.Resource.Name {
|
||||
return fmt.Errorf("resource permission name does not match spec: %w", errInvalidSpec)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete
|
||||
|
||||
// deleteResourcePermission deletes resource permissions for a single ResourcePermission resource referenced by its name in the format <group>-<resource>-<name> (e.g. dashboard.grafana.app-dashboards-ad5rwqs)
|
||||
|
||||
Reference in New Issue
Block a user