From c8caf787d4318ae2e27b92c602a6b226c48ab7c8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 28 Nov 2024 10:41:30 +0100 Subject: [PATCH] Zanzana: handle service accounts (#97123) * add service account to the schema * sync managed permissions for service accounts * sync SA basic roles * sync SA roles * Fix endless loop in reconciler while read openfga --- .../accesscontrol/dualwrite/collectors.go | 54 ++++++++++++------- pkg/services/authz/zanzana/common/tuple.go | 13 ++--- .../authz/zanzana/schema/schema_core.fga | 26 ++++----- .../authz/zanzana/schema/schema_folder.fga | 18 +++---- .../authz/zanzana/schema/schema_resource.fga | 34 ++++++------ pkg/services/authz/zanzana/zanzana.go | 13 ++--- 6 files changed, 90 insertions(+), 68 deletions(-) diff --git a/pkg/services/accesscontrol/dualwrite/collectors.go b/pkg/services/accesscontrol/dualwrite/collectors.go index 97d5c6ee6f4..0be8336069d 100644 --- a/pkg/services/accesscontrol/dualwrite/collectors.go +++ b/pkg/services/accesscontrol/dualwrite/collectors.go @@ -115,7 +115,7 @@ func folderTreeCollector(store db.DB) legacyTupleCollector { func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name + SELECT u.uid as user_uid, u.is_service_account as is_service_account, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name FROM permission p INNER JOIN role r ON p.role_id = r.id LEFT JOIN user_role ur ON r.id = ur.role_id @@ -128,12 +128,13 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector AND p.kind = ? ` type Permission struct { - Action string `xorm:"action"` - Kind string - Identifier string - UserUID string `xorm:"user_uid"` - TeamUID string `xorm:"team_uid"` - BasicRoleName string `xorm:"basic_role_name"` + Action string `xorm:"action"` + Kind string + Identifier string + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + TeamUID string `xorm:"team_uid"` + BasicRoleName string `xorm:"basic_role_name"` } var permissions []Permission @@ -149,7 +150,9 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector for _, p := range permissions { var subject string - if len(p.UserUID) > 0 { + if len(p.UserUID) > 0 && p.IsServiceAccount { + subject = zanzana.NewTupleEntry(zanzana.TypeServiceAccount, p.UserUID, "") + } else if len(p.UserUID) > 0 { subject = zanzana.NewTupleEntry(zanzana.TypeUser, p.UserUID, "") } else if len(p.TeamUID) > 0 { subject = zanzana.NewTupleEntry(zanzana.TypeTeam, p.TeamUID, zanzana.RelationTeamMember) @@ -198,16 +201,19 @@ func tupleStringWithoutCondition(tuple *openfgav1.TupleKey) string { func basicRoleBindingsCollector(store db.DB) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT ou.org_id, u.uid as user_uid, ou.role as org_role + SELECT + ou.org_id, u.uid as user_uid, + u.is_service_account as is_service_account, + ou.role as org_role FROM org_user ou LEFT JOIN ` + store.GetDialect().Quote("user") + ` u ON u.id = ou.user_id WHERE ou.org_id = ? - AND NOT u.is_service_account ` // FIXME: handle service admin role type Binding struct { - UserUID string `xorm:"user_uid"` - OrgRole string `xorm:"org_role"` + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + OrgRole string `xorm:"org_role"` } var bindings []Binding @@ -222,8 +228,13 @@ func basicRoleBindingsCollector(store db.DB) legacyTupleCollector { tuples := make(map[string]map[string]*openfgav1.TupleKey) for _, b := range bindings { + userType := zanzana.TypeUser + if b.IsServiceAccount { + userType = zanzana.TypeServiceAccount + } + tuple := &openfgav1.TupleKey{ - User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""), + User: zanzana.NewTupleEntry(userType, b.UserUID, ""), Relation: zanzana.RelationAssignee, Object: zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(b.OrgRole), ""), } @@ -286,7 +297,7 @@ func teamRoleBindingsCollector(store db.DB) legacyTupleCollector { func userRoleBindingsCollector(store db.DB) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT u.uid AS user_uid, r.uid AS role_uid + SELECT u.uid AS user_uid, u.is_service_account as is_service_account, r.uid AS role_uid FROM user_role ur INNER JOIN ` + store.GetDialect().Quote("user") + ` u ON ur.user_id = u.id INNER JOIN role r ON ur.role_id = r.id @@ -294,8 +305,9 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector { AND r.name NOT LIKE 'managed:%' ` type Binding struct { - UserUID string `xorm:"user_uid"` - RoleUID string `xorm:"role_uid"` + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + RoleUID string `xorm:"role_uid"` } var bindings []Binding @@ -310,8 +322,13 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector { tuples := make(map[string]map[string]*openfgav1.TupleKey) for _, b := range bindings { + userType := zanzana.TypeUser + if b.IsServiceAccount { + userType = zanzana.TypeServiceAccount + } + tuple := &openfgav1.TupleKey{ - User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""), + User: zanzana.NewTupleEntry(userType, b.UserUID, ""), Relation: zanzana.RelationAssignee, Object: zanzana.NewTupleEntry(zanzana.TypeRole, b.RoleUID, ""), } @@ -397,7 +414,8 @@ func zanzanaCollector(relations []string) zanzanaTupleCollector { for c != "" { res, err := client.Read(ctx, &authzextv1.ReadRequest{ - Namespace: namespace, + ContinuationToken: c, + Namespace: namespace, TupleKey: &authzextv1.ReadRequestTupleKey{ Object: object, Relation: relation, diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index 2e413545232..1105e44275a 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -10,12 +10,13 @@ import ( ) const ( - TypeUser string = "user" - TypeTeam string = "team" - TypeRole string = "role" - TypeFolder string = "folder" - TypeResource string = "resource" - TypeNamespace string = "namespace" + TypeUser string = "user" + TypeServiceAccount string = "service-account" + TypeTeam string = "team" + TypeRole string = "role" + TypeFolder string = "folder" + TypeResource string = "resource" + TypeNamespace string = "namespace" ) const ( diff --git a/pkg/services/authz/zanzana/schema/schema_core.fga b/pkg/services/authz/zanzana/schema/schema_core.fga index 1003841a9ce..5b24adb38a9 100644 --- a/pkg/services/authz/zanzana/schema/schema_core.fga +++ b/pkg/services/authz/zanzana/schema/schema_core.fga @@ -2,28 +2,30 @@ module core type namespace relations - define view: [user, team#member, role#assignee] or edit - define edit: [user, team#member, role#assignee] or admin - define admin: [user, team#member, role#assignee] + define view: [user, service-account, team#member, role#assignee] or edit + define edit: [user, service-account, team#member, role#assignee] or admin + define admin: [user, service-account, team#member, role#assignee] - define read: [user, team#member, role#assignee] or view - define create: [user, team#member, role#assignee] or edit - define write: [user, team#member, role#assignee] or edit - define delete: [user, team#member, role#assignee] or edit - define permissions_read: [user, team#member, role#assignee] or admin - define permissions_write: [user, team#member, role#assignee] or admin + define read: [user, service-account, team#member, role#assignee] or view + define create: [user, service-account, team#member, role#assignee] or edit + define write: [user, service-account, team#member, role#assignee] or edit + define delete: [user, service-account, team#member, role#assignee] or edit + define permissions_read: [user, service-account, team#member, role#assignee] or admin + define permissions_write: [user, service-account, team#member, role#assignee] or admin type user +type service-account + type role relations - define assignee: [user, team#member, role#assignee] + define assignee: [user, service-account, team#member, role#assignee] type team relations # Action sets - define admin: [user] - define member: [user] or admin + define admin: [user, service-account] + define member: [user, service-account] or admin define read: [role#assignee] or member define write: [role#assignee] or admin diff --git a/pkg/services/authz/zanzana/schema/schema_folder.fga b/pkg/services/authz/zanzana/schema/schema_folder.fga index b66f20854cb..117bff01199 100644 --- a/pkg/services/authz/zanzana/schema/schema_folder.fga +++ b/pkg/services/authz/zanzana/schema/schema_folder.fga @@ -5,13 +5,13 @@ type folder define parent: [folder] # Action sets - define view: [user, team#member, role#assignee] or edit or view from parent - define edit: [user, team#member, role#assignee] or admin or edit from parent - define admin: [user, team#member, role#assignee] or admin from parent + define view: [user, service-account, team#member, role#assignee] or edit or view from parent + define edit: [user, service-account, team#member, role#assignee] or admin or edit from parent + define admin: [user, service-account, team#member, role#assignee] or admin from parent - define read: [user, team#member, role#assignee] or view or read from parent - define create: [user, team#member, role#assignee] or edit or create from parent - define write: [user, team#member, role#assignee] or edit or write from parent - define delete: [user, team#member, role#assignee] or edit or delete from parent - define permissions_read: [user, team#member, role#assignee] or admin or permissions_read from parent - define permissions_write: [user, team#member, role#assignee] or admin or permissions_write from parent + define read: [user, service-account, team#member, role#assignee] or view or read from parent + define create: [user, service-account, team#member, role#assignee] or edit or create from parent + define write: [user, service-account, team#member, role#assignee] or edit or write from parent + define delete: [user, service-account, team#member, role#assignee] or edit or delete from parent + define permissions_read: [user, service-account, team#member, role#assignee] or admin or permissions_read from parent + define permissions_write: [user, service-account, team#member, role#assignee] or admin or permissions_write from parent diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga index 951e1c151d2..9e90bf0472b 100644 --- a/pkg/services/authz/zanzana/schema/schema_resource.fga +++ b/pkg/services/authz/zanzana/schema/schema_resource.fga @@ -2,28 +2,28 @@ module resource extend type folder relations - define resource_view: [user, team#member, role#assignee] or resource_edit or resource_view from parent - define resource_edit: [user, team#member, role#assignee] or resource_admin or resource_edit from parent - define resource_admin: [user, team#member, role#assignee] or resource_admin from parent + define resource_view: [user, service-account, team#member, role#assignee] or resource_edit or resource_view from parent + define resource_edit: [user, service-account, team#member, role#assignee] or resource_admin or resource_edit from parent + define resource_admin: [user, service-account, team#member, role#assignee] or resource_admin from parent - define resource_read: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent - define resource_create: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent - define resource_write: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent - define resource_delete: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent - define resource_permissions_read: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent - define resource_permissions_write: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent + define resource_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent + define resource_create: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent + define resource_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent + define resource_delete: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent + define resource_permissions_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent + define resource_permissions_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent type resource relations - define view: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define edit: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin - define admin: [user with group_filter, team#member with group_filter, role#assignee with group_filter] + define view: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define edit: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define admin: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] - define read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or view - define write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define delete: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin - define permissions_write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view + define write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define delete: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define permissions_read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define permissions_write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin condition group_filter(requested_group: string, group_resource: string) { requested_group == group_resource diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index ce02d3e947e..b7af6983eda 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -11,12 +11,13 @@ import ( ) const ( - TypeUser = common.TypeUser - TypeTeam = common.TypeTeam - TypeRole = common.TypeRole - TypeFolder = common.TypeFolder - TypeResource = common.TypeResource - TypeNamespace = common.TypeNamespace + TypeUser = common.TypeUser + TypeServiceAccount = common.TypeServiceAccount + TypeTeam = common.TypeTeam + TypeRole = common.TypeRole + TypeFolder = common.TypeFolder + TypeResource = common.TypeResource + TypeNamespace = common.TypeNamespace ) const (