mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Zanzana: Evaluate dashboard and folder permissions (#91539)
* Zanzana: basic folder permissions checks * Fix managed permissions for teams * fix sync batch size * add dashboards actions translations * migrate folder tree * migrate dashboard folders * remove action sets from schema * Adding more dashboard and folder-related permissions * refactor * Correctly translate dashboard permissions in folders * fix dashboard parent permissions
This commit is contained in:
parent
423d198d77
commit
1cc438a56c
@ -114,6 +114,7 @@ func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Reque
|
||||
return false, errAccessNotImplemented
|
||||
}
|
||||
|
||||
a.log.Debug("evaluating zanzana", "user", key.User, "relation", key.Relation, "object", key.Object)
|
||||
res, err := a.zclient.Check(ctx, &openfgav1.CheckRequest{
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: key.User,
|
||||
|
@ -3,6 +3,7 @@ package migrator
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
@ -33,6 +34,8 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu
|
||||
collectors,
|
||||
teamMembershipCollector(store),
|
||||
managedPermissionsCollector(store),
|
||||
folderTreeCollector(store),
|
||||
dashboardFolderCollector(store),
|
||||
)
|
||||
|
||||
return &ZanzanaSynchroniser{
|
||||
@ -111,9 +114,9 @@ func managedPermissionsCollector(store db.DB) TupleCollector {
|
||||
for _, p := range permissions {
|
||||
var subject string
|
||||
if len(p.UserUID) > 0 {
|
||||
subject = zanzana.NewObject(zanzana.TypeUser, p.UserUID)
|
||||
subject = zanzana.NewTupleEntry(zanzana.TypeUser, p.UserUID, "")
|
||||
} else if len(p.TeamUID) > 0 {
|
||||
subject = zanzana.NewObject(zanzana.TypeTeam, p.TeamUID)
|
||||
subject = zanzana.NewTupleEntry(zanzana.TypeTeam, p.TeamUID, "member")
|
||||
} else {
|
||||
// FIXME(kalleep): Unsuported role binding (org role). We need to have basic roles in place
|
||||
continue
|
||||
@ -161,8 +164,8 @@ func teamMembershipCollector(store db.DB) TupleCollector {
|
||||
|
||||
for _, m := range memberships {
|
||||
tuple := &openfgav1.TupleKey{
|
||||
User: zanzana.NewObject(zanzana.TypeUser, m.UserUID),
|
||||
Object: zanzana.NewObject(zanzana.TypeTeam, m.TeamUID),
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, m.UserUID, ""),
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeTeam, m.TeamUID, ""),
|
||||
}
|
||||
|
||||
// Admin permission is 4 and member 0
|
||||
@ -178,3 +181,75 @@ func teamMembershipCollector(store db.DB) TupleCollector {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// folderTreeCollector collects folder tree structure and writes it as relation tuples
|
||||
func folderTreeCollector(store db.DB) TupleCollector {
|
||||
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
|
||||
const collectorID = "folder"
|
||||
const query = `
|
||||
SELECT uid, parent_uid, org_id FROM folder WHERE parent_uid IS NOT NULL
|
||||
`
|
||||
type folder struct {
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
FolderUID string `xorm:"uid"`
|
||||
ParentUID string `xorm:"parent_uid"`
|
||||
}
|
||||
|
||||
var folders []folder
|
||||
err := store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.SQL(query).Find(&folders)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range folders {
|
||||
tuple := &openfgav1.TupleKey{
|
||||
User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.ParentUID, "", strconv.FormatInt(f.OrgID, 10)),
|
||||
Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.FolderUID, "", strconv.FormatInt(f.OrgID, 10)),
|
||||
Relation: zanzana.RelationParent,
|
||||
}
|
||||
|
||||
tuples[collectorID] = append(tuples[collectorID], tuple)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// dashboardFolderCollector collects information about dashboards parent folders
|
||||
func dashboardFolderCollector(store db.DB) TupleCollector {
|
||||
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
|
||||
const collectorID = "folder"
|
||||
const query = `
|
||||
SELECT org_id, uid, folder_uid, is_folder FROM dashboard WHERE is_folder = 0 AND folder_uid IS NOT NULL
|
||||
`
|
||||
type dashboard struct {
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
UID string `xorm:"uid"`
|
||||
ParentUID string `xorm:"folder_uid"`
|
||||
}
|
||||
|
||||
var dashboards []dashboard
|
||||
err := store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.SQL(query).Find(&dashboards)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range dashboards {
|
||||
tuple := &openfgav1.TupleKey{
|
||||
User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, d.ParentUID, "", strconv.FormatInt(d.OrgID, 10)),
|
||||
Object: zanzana.NewScopedTupleEntry(zanzana.TypeDashboard, d.UID, "", strconv.FormatInt(d.OrgID, 10)),
|
||||
Relation: zanzana.RelationParent,
|
||||
}
|
||||
|
||||
tuples[collectorID] = append(tuples[collectorID], tuple)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,47 @@ type org
|
||||
define member: [user]
|
||||
define viewer: [user]
|
||||
|
||||
# team management
|
||||
define team_create: [role#assignee]
|
||||
define team_read: [role#assignee]
|
||||
define team_write: [role#assignee] or team_create
|
||||
define team_delete: [role#assignee] or team_write
|
||||
define team_permissions_write: [role#assignee]
|
||||
define team_permissions_read: [role#assignee] or team_permissions_write
|
||||
|
||||
define folder_create: [role#assignee]
|
||||
define folder_read: [role#assignee] or folder_delete
|
||||
define folder_write: [role#assignee] or folder_create
|
||||
define folder_delete: [role#assignee] or folder_write
|
||||
define folder_permissions_write: [role#assignee]
|
||||
define folder_permissions_read: [role#assignee] or folder_permissions_write
|
||||
|
||||
define dashboard_annotations_create: [role#assignee]
|
||||
define dashboard_annotations_read: [role#assignee]
|
||||
define dashboard_annotations_write: [role#assignee]
|
||||
define dashboard_annotations_delete: [role#assignee]
|
||||
define dashboard_create: [role#assignee]
|
||||
define dashboard_delete: [role#assignee]
|
||||
define dashboard_permissions_read: [role#assignee]
|
||||
define dashboard_permissions_write: [role#assignee]
|
||||
define dashboard_public_write: [role#assignee] or dashboard_write
|
||||
define dashboard_read: [role#assignee]
|
||||
define dashboard_write: [role#assignee]
|
||||
|
||||
define library_panel_create: [role#assignee]
|
||||
define library_panel_read: [role#assignee] or library_panel_write
|
||||
define library_panel_write: [role#assignee] or library_panel_create
|
||||
define library_panel_delete: [role#assignee] or library_panel_create
|
||||
|
||||
define alert_rule_create: [role#assignee]
|
||||
define alert_rule_read: [role#assignee] or alert_rule_write
|
||||
define alert_rule_write: [role#assignee] or alert_rule_create
|
||||
define alert_rule_delete: [role#assignee] or alert_rule_write
|
||||
define alert_silence_create: [role#assignee]
|
||||
define alert_silence_delete: [role#assignee] or alert_silence_write
|
||||
define alert_silence_read: [role#assignee] or alert_silence_write
|
||||
define alert_silence_write: [role#assignee] or alert_silence_create
|
||||
|
||||
type role
|
||||
relations
|
||||
define org: [org]
|
||||
@ -22,3 +63,65 @@ type team
|
||||
define org: [org]
|
||||
define admin: [user]
|
||||
define member: [user] or admin
|
||||
|
||||
define read: [role#assignee] or member or team_read from org
|
||||
define write: [role#assignee] or admin or team_write from org
|
||||
define delete: [role#assignee] or admin or team_delete from org
|
||||
define permissions_read: [role#assignee] or admin or team_permissions_read from org
|
||||
define permissions_write: [role#assignee] or admin or team_permissions_write from org
|
||||
|
||||
type folder
|
||||
relations
|
||||
define parent: [folder]
|
||||
define org: [org]
|
||||
|
||||
define create: [user, team#member, role#assignee] or create from parent or folder_create from org
|
||||
define read: [user, team#member, role#assignee] or read from parent or folder_read from org
|
||||
define write: [user, team#member, role#assignee] or write from parent or folder_write from org
|
||||
define delete: [user, team#member, role#assignee] or delete from parent or folder_delete from org
|
||||
define permissions_read: [user, team#member, role#assignee] or permissions_read from parent or folder_permissions_read from org
|
||||
define permissions_write: [user, team#member, role#assignee] or permissions_write from parent or folder_permissions_write from org
|
||||
|
||||
define dashboard_create: [user, team#member, role#assignee] or dashboard_create from parent or dashboard_create from org
|
||||
define dashboard_read: [user, team#member, role#assignee] or dashboard_read from parent or dashboard_read from org
|
||||
define dashboard_write: [user, team#member, role#assignee] or dashboard_write from parent or dashboard_write from org
|
||||
define dashboard_delete: [user, team#member, role#assignee] or dashboard_delete from parent or dashboard_delete from org
|
||||
define dashboard_permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from parent or dashboard_permissions_read from org
|
||||
define dashboard_permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from parent or dashboard_permissions_write from org
|
||||
define dashboard_public_write: [user, team#member, role#assignee] or dashboard_public_write from parent or dashboard_public_write from org or dashboard_write
|
||||
define dashboard_annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from parent or dashboard_annotations_create from org
|
||||
define dashboard_annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from parent or dashboard_annotations_read from org
|
||||
define dashboard_annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from parent or dashboard_annotations_write from org
|
||||
define dashboard_annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from parent or dashboard_annotations_delete from org
|
||||
|
||||
define library_panel_create: [user, team#member, role#assignee] or library_panel_create from parent or library_panel_create from org
|
||||
define library_panel_read: [user, team#member, role#assignee] or library_panel_read from parent or library_panel_read from org or library_panel_write
|
||||
define library_panel_write: [user, team#member, role#assignee] or library_panel_write from parent or library_panel_write from org or library_panel_create
|
||||
define library_panel_delete: [user, team#member, role#assignee] or library_panel_delete from parent or library_panel_delete from org or library_panel_create
|
||||
|
||||
define alert_rule_create: [user, team#member, role#assignee] or alert_rule_create from parent or alert_rule_create from org
|
||||
define alert_rule_read: [user, team#member, role#assignee] or alert_rule_read from parent or alert_rule_read from org or alert_rule_write
|
||||
define alert_rule_write: [user, team#member, role#assignee] or alert_rule_write from parent or alert_rule_write from org or alert_rule_create
|
||||
define alert_rule_delete: [user, team#member, role#assignee] or alert_rule_delete from parent or alert_rule_delete from org or alert_rule_write
|
||||
define alert_silence_create: [user, team#member, role#assignee] or alert_silence_create from parent or alert_silence_create from org
|
||||
define alert_silence_read: [user, team#member, role#assignee] or alert_silence_read from parent or alert_silence_read from org or alert_silence_write
|
||||
define alert_silence_write: [user, team#member, role#assignee] or alert_silence_write from parent or alert_silence_write from org or alert_silence_create
|
||||
|
||||
# Dashboard
|
||||
type dashboard
|
||||
relations
|
||||
define org: [org]
|
||||
define parent: [folder]
|
||||
|
||||
define read: [user, team#member, role#assignee] or dashboard_read from parent or dashboard_read from org
|
||||
define write: [user, team#member, role#assignee] or dashboard_write from parent or dashboard_write from org
|
||||
define delete: [user, team#member, role#assignee] or dashboard_delete from parent or dashboard_delete from org
|
||||
define create: [user, team#member, role#assignee] or dashboard_create from parent or dashboard_create from org
|
||||
define permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from parent or dashboard_permissions_read from org
|
||||
define permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from parent or dashboard_permissions_write from org
|
||||
|
||||
define public_write: [user, team#member, role#assignee] or dashboard_public_write from parent or dashboard_public_write from org or write
|
||||
define annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from parent or dashboard_annotations_create from org
|
||||
define annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from parent or dashboard_annotations_read from org
|
||||
define annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from parent or dashboard_annotations_write from org
|
||||
define annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from parent or dashboard_annotations_delete from org
|
||||
|
61
pkg/services/authz/zanzana/translations.go
Normal file
61
pkg/services/authz/zanzana/translations.go
Normal file
@ -0,0 +1,61 @@
|
||||
package zanzana
|
||||
|
||||
type actionKindTranslation struct {
|
||||
objectType string
|
||||
orgScoped bool
|
||||
translations map[string]string
|
||||
}
|
||||
|
||||
// rbac action to relation translation
|
||||
var folderActions = map[string]string{
|
||||
"folders:create": "create",
|
||||
"folders:read": "read",
|
||||
"folders:write": "write",
|
||||
"folders:delete": "delete",
|
||||
"folders.permissions:read": "permissions_read",
|
||||
"folders.permissions:write": "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",
|
||||
}
|
||||
|
||||
var dashboardActions = map[string]string{
|
||||
"dashboards:create": "create",
|
||||
"dashboards:read": "read",
|
||||
"dashboards:write": "write",
|
||||
"dashboards:delete": "delete",
|
||||
"dashboards.permissions:read": "permissions_read",
|
||||
"dashboards.permissions:write": "permissions_write",
|
||||
}
|
||||
|
||||
// RBAC to OpenFGA translations grouped by kind
|
||||
var actionKindTranslations = map[string]actionKindTranslation{
|
||||
"folders": {
|
||||
objectType: "folder",
|
||||
orgScoped: true,
|
||||
translations: folderActions,
|
||||
},
|
||||
"dashboards": {
|
||||
objectType: "dashboard",
|
||||
orgScoped: true,
|
||||
translations: dashboardActions,
|
||||
},
|
||||
}
|
@ -8,41 +8,42 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeUser string = "user"
|
||||
TypeTeam string = "team"
|
||||
TypeUser string = "user"
|
||||
TypeTeam string = "team"
|
||||
TypeFolder string = "folder"
|
||||
TypeDashboard string = "dashboard"
|
||||
)
|
||||
|
||||
const (
|
||||
RelationTeamMember string = "member"
|
||||
RelationTeamAdmin string = "admin"
|
||||
RelationParent string = "parent"
|
||||
)
|
||||
|
||||
func NewObject(typ, id string) string {
|
||||
return fmt.Sprintf("%s:%s", typ, id)
|
||||
// NewTupleEntry constructs new openfga entry type:id[#relation].
|
||||
// Relation allows to specify group of users (subjects) related to type:id
|
||||
// (for example, team:devs#member refers to users which are members of team devs)
|
||||
func NewTupleEntry(objectType, id, relation string) string {
|
||||
obj := fmt.Sprintf("%s:%s", objectType, id)
|
||||
if relation != "" {
|
||||
obj = fmt.Sprintf("%s#%s", obj, relation)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
func NewScopedObject(typ, id, scope string) string {
|
||||
return NewObject(typ, fmt.Sprintf("%s-%s", scope, id))
|
||||
// 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), "")
|
||||
}
|
||||
|
||||
// rbac action to relation translation
|
||||
var actionTranslations = map[string]string{}
|
||||
|
||||
type kindTranslation struct {
|
||||
typ string
|
||||
orgScoped bool
|
||||
}
|
||||
|
||||
// all kinds that we can translate into a openFGA object
|
||||
var kindTranslations = map[string]kindTranslation{}
|
||||
|
||||
func TranslateToTuple(user string, action, kind, identifier string, orgID int64) (*openfgav1.TupleKey, bool) {
|
||||
relation, ok := actionTranslations[action]
|
||||
typeTranslation, ok := actionKindTranslations[kind]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
t, ok := kindTranslations[kind]
|
||||
relation, ok := typeTranslation.translations[action]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
@ -55,10 +56,10 @@ func TranslateToTuple(user string, action, kind, identifier string, orgID int64)
|
||||
tuple.Relation = relation
|
||||
|
||||
// Some uid:s in grafana are not guarantee to be unique across orgs so we need to scope them.
|
||||
if t.orgScoped {
|
||||
tuple.Object = NewScopedObject(t.typ, identifier, strconv.FormatInt(orgID, 10))
|
||||
if typeTranslation.orgScoped {
|
||||
tuple.Object = NewScopedTupleEntry(typeTranslation.objectType, identifier, "", strconv.FormatInt(orgID, 10))
|
||||
} else {
|
||||
tuple.Object = NewObject(t.typ, identifier)
|
||||
tuple.Object = NewTupleEntry(typeTranslation.objectType, identifier, "")
|
||||
}
|
||||
|
||||
return tuple, true
|
||||
|
Loading…
Reference in New Issue
Block a user