Zanzana: Migrate basic, fixed and custom roles (#91814)

* Zanzana: Migrate basic roles permissions

* add basic roles assignments

* refactor

* Sync basic roles permissions in all orgs

* migrate fixed roles

* map root folders to orgs

* fix basic role assignments in orgs

* migrate other roles

* migrate team roles assignments

* add notes about authorization schema

* don't migrate fixed roles
This commit is contained in:
Alexander Zobnin
2024-08-15 16:13:27 +02:00
committed by GitHub
parent 4b0e8653f2
commit aaf33c7923
5 changed files with 504 additions and 12 deletions
+110
View File
@@ -0,0 +1,110 @@
# Authorization schema
Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/modeling/getting-started) (schema) using to model access control in Grafana.
## Org-level permissions
Most of the permissions are exist in org. Users, teams, dashboards, folders and other objects also related to specific org.
## Dashboards and folders
Folder hierarchy is stored directly in OpenFGA database. Each dashboard has parent folder and every folder could have sub-folders. Root-level folders do not have parents, but instead, they related to specific org:
```text
type org
relations
define instance: [instance]
define member: [user]
type folder
relations
define parent: [folder]
define org: [org]
type dashboard
relations
define org: [org]
define parent: [folder]
```
Therefore, folders tree is stored as tuples like this:
```text
folder:<org_id>-<folder_uid> parent dashboard:<org_id>-<dashboard_uid>
folder:<org_id>-<folder_uid> parent folder:<org_id>-<folder_uid>
org:<org_id> org folder:<org_id>-<folder_uid>
```
## Managed permissions
In the RBAC model managed permissions stored as a special "managed" role permissions. OpenFGA model allows to assign permissions directly to users, so it produces following tuples:
```text
user:<user_uid> read folder:<org_id>-<folder_uid>
```
It's also possible to assign permissions for team members using `#member` relation:
```text
team:<team_uid>#member read folder:<org_id>-<folder_uid>
```
It's important to understand that folder permissions cannot be directly assigned to teams, because it's restricted by schema:
```text
type folder
relations
define parent: [folder]
define org: [org]
define read: [user, team#member, role#assignee] or read from parent or folder_read from org
type team
relations
define org: [org]
define admin: [user]
define member: [user] or admin
```
Therefore, `team#member` can have `read` relation to folder and user will be automatically granted the same permission if it has `member` relation to specific team.
## Roles and role assignments
RBAC authorization model grants permissions to users through roles and role assignments. All permissions are linked to roles and then roles granted to users. To model this in OpenFGA, we use org-level permission and `role` type.
To understand how RBAC permissions linked to roles, let's take a look at the dashboard read permission as example:
```text
type org
relations
define instance: [instance]
define member: [user]
define folder_read: [role#assignee]
type role
relations
define org: [org]
define assignee: [user, team#member, role#assignee]
type folder
relations
define parent: [folder]
define org: [org]
define read: [user, team#member, role#assignee] or read from parent or folder_read from org
```
According to the schema, user can get `read` access to dashboard if it has `read` relation granted directly to the dashboard ot its parent folders, or by having `folder_read from org`. If we take a look at `folder_read` definition in the org type, we could see that this relation could be granted to `role#assignee`. So in order to allow user to read all dahboards in org, following tuples should be added:
```text
role:<org_id>-<role_uid>#assignee folder_read org:<org_uid>
user:<user_uid> assignee role:<role_uid>
```
In case of `Admin` basic role, it will be looking like:
```text
role:1-basic_admin#assignee folder_read org:1
user:admin assignee role:1-basic_admin
```
@@ -9,7 +9,6 @@ type org
relations
define instance: [instance]
define member: [user]
define viewer: [user]
# team management
define team_create: [role#assignee]
+47 -4
View File
@@ -46,16 +46,59 @@ var dashboardActions = map[string]string{
"dashboards.permissions:write": "permissions_write",
}
var orgActions = map[string]string{
"folders:create": "folder_create",
"folders:read": "folder_read",
"folders:write": "folder_write",
"folders:delete": "folder_delete",
"folders.permissions:read": "folder_permissions_read",
"folders.permissions:write": "folder_permissions_write",
"dashboards:create": "dashboard_create",
"dashboards:read": "dashboard_read",
"dashboards:write": "dashboard_write",
"dashboards:delete": "dashboard_delete",
"dashboards.permissions:read": "dashboard_permissions_read",
"dashboards.permissions:write": "dashboard_permissions_write",
"library.panels:create": "library_panel_create",
"library.panels:read": "library_panel_read",
"library.panels:write": "library_panel_write",
"library.panels:delete": "library_panel_delete",
"alert.rules:create": "alert_rule_create",
"alert.rules:read": "alert_rule_read",
"alert.rules:write": "alert_rule_write",
"alert.rules:delete": "alert_rule_delete",
"alert.silences:create": "alert_silence_create",
"alert.silences:read": "alert_silence_read",
"alert.silences:write": "alert_silence_write",
}
// RBAC to OpenFGA translations grouped by kind
var actionKindTranslations = map[string]actionKindTranslation{
"folders": {
objectType: "folder",
KindOrg: {
objectType: TypeOrg,
orgScoped: false,
translations: orgActions,
},
KindFolders: {
objectType: TypeFolder,
orgScoped: true,
translations: folderActions,
},
"dashboards": {
objectType: "dashboard",
KindDashboards: {
objectType: TypeDashboard,
orgScoped: true,
translations: dashboardActions,
},
}
var basicRolesTranslations = map[string]string{
RoleGrafanaAdmin: "basic_grafana_admin",
RoleAdmin: "basic_admin",
RoleEditor: "basic_editor",
RoleViewer: "basic_viewer",
RoleNone: "basic_none",
}
+48 -1
View File
@@ -10,14 +10,37 @@ import (
const (
TypeUser string = "user"
TypeTeam string = "team"
TypeRole string = "role"
TypeFolder string = "folder"
TypeDashboard string = "dashboard"
TypeOrg string = "org"
)
const (
RelationTeamMember string = "member"
RelationTeamAdmin string = "admin"
RelationParent string = "parent"
RelationAssignee string = "assignee"
RelationOrg string = "org"
)
const (
KindOrg string = "org"
KindDashboards string = "dashboards"
KindFolders string = "folders"
)
const (
RoleGrafanaAdmin = "Grafana Admin"
RoleAdmin = "Admin"
RoleEditor = "Editor"
RoleViewer = "Viewer"
RoleNone = "None"
BasicRolePrefix = "basic:"
BasicRoleUIDPrefix = "basic_"
GlobalOrgID = 0
)
// NewTupleEntry constructs new openfga entry type:id[#relation].
@@ -34,7 +57,7 @@ func NewTupleEntry(objectType, id, relation string) string {
// NewScopedTupleEntry constructs new openfga entry type:id[#relation]
// with id prefixed by scope (usually org id)
func NewScopedTupleEntry(objectType, id, relation, scope string) string {
return NewTupleEntry(objectType, fmt.Sprintf("%s-%s", scope, id), "")
return NewTupleEntry(objectType, fmt.Sprintf("%s-%s", scope, id), relation)
}
func TranslateToTuple(user string, action, kind, identifier string, orgID int64) (*openfgav1.TupleKey, bool) {
@@ -64,3 +87,27 @@ func TranslateToTuple(user string, action, kind, identifier string, orgID int64)
return tuple, true
}
func TranslateToOrgTuple(user string, action string, orgID int64) (*openfgav1.TupleKey, bool) {
typeTranslation, ok := actionKindTranslations[KindOrg]
if !ok {
return nil, false
}
relation, ok := typeTranslation.translations[action]
if !ok {
return nil, false
}
tuple := &openfgav1.TupleKey{
Relation: relation,
User: user,
Object: NewTupleEntry(typeTranslation.objectType, strconv.FormatInt(orgID, 10), ""),
}
return tuple, true
}
func TranslateBasicRole(role string) string {
return basicRolesTranslations[role]
}