Zanzana: change type name from namespace to group_resource (#97741)

* Change type name from namespace to group_resource

* update function names and test descriptions
pull/97747/head
Karl Persson 5 months ago committed by GitHub
parent b4927ff1cd
commit 87ba9c60b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 27
      pkg/services/authz/zanzana/common/tuple.go
  2. 6
      pkg/services/authz/zanzana/schema/README.md
  3. 12
      pkg/services/authz/zanzana/schema/schema_core.fga
  4. 11
      pkg/services/authz/zanzana/schema/schema_resource.fga
  5. 2
      pkg/services/authz/zanzana/server/server_batch_check.go
  6. 4
      pkg/services/authz/zanzana/server/server_batch_check_test.go
  7. 4
      pkg/services/authz/zanzana/server/server_capabilities.go
  8. 4
      pkg/services/authz/zanzana/server/server_capabilities_test.go
  9. 12
      pkg/services/authz/zanzana/server/server_check.go
  10. 4
      pkg/services/authz/zanzana/server/server_check_test.go
  11. 2
      pkg/services/authz/zanzana/server/server_list.go
  12. 6
      pkg/services/authz/zanzana/server/server_test.go
  13. 4
      pkg/services/authz/zanzana/zanzana.go

@ -16,9 +16,12 @@ const (
TypeRenderService string = "render"
TypeTeam string = "team"
TypeRole string = "role"
TypeFolder string = "folder"
TypeResource string = "resource"
TypeNamespace string = "namespace"
)
const (
TypeFolder string = "folder"
TypeResource string = "resource"
TypeGroupResouce string = "group_resource"
)
const (
@ -46,8 +49,8 @@ const (
RelationFolderResourceDelete string = "resource_" + RelationDelete
)
// RelationsNamespace are relations that can be added on type "namespace".
var RelationsNamespace = []string{
// RelationsGroupResource are relations that can be added on type "group_resource".
var RelationsGroupResource = []string{
RelationGet,
RelationUpdate,
RelationCreate,
@ -78,8 +81,8 @@ var RelationsFolder = append(
RelationDelete,
)
func IsNamespaceRelation(relation string) bool {
return isValidRelation(relation, RelationsNamespace)
func IsGroupResourceRelation(relation string) bool {
return isValidRelation(relation, RelationsGroupResource)
}
func IsFolderResourceRelation(relation string) bool {
@ -115,8 +118,8 @@ func NewFolderIdent(name string) string {
return fmt.Sprintf("%s:%s", TypeFolder, name)
}
func NewNamespaceResourceIdent(group, resource string) string {
return fmt.Sprintf("%s:%s", TypeNamespace, FormatGroupResource(group, resource))
func NewGroupResourceIdent(group, resource string) string {
return fmt.Sprintf("%s:%s", TypeGroupResouce, FormatGroupResource(group, resource))
}
func FormatGroupResource(group, resource string) string {
@ -169,11 +172,11 @@ func NewFolderResourceTuple(subject, relation, group, resource, folder string) *
}
}
func NewNamespaceResourceTuple(subject, relation, group, resource string) *openfgav1.TupleKey {
func NewGroupResourceTuple(subject, relation, group, resource string) *openfgav1.TupleKey {
return &openfgav1.TupleKey{
User: subject,
Relation: relation,
Object: NewNamespaceResourceIdent(group, resource),
Object: NewGroupResourceIdent(group, resource),
}
}
@ -289,7 +292,7 @@ func AddRenderContext(req *openfgav1.CheckRequest) {
req.ContextualTuples.TupleKeys = append(req.ContextualTuples.TupleKeys, &openfgav1.TupleKey{
User: req.TupleKey.User,
Relation: RelationSetView,
Object: NewNamespaceResourceIdent(
Object: NewGroupResourceIdent(
dashboardalpha1.DashboardResourceInfo.GroupResource().Group,
dashboardalpha1.DashboardResourceInfo.GroupResource().Resource,
),

@ -2,10 +2,10 @@
Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/modeling/getting-started) (schema) using to model access control in Grafana.
## Namespace level permissions
## GroupResource level permissions
A relation to a namespace object grant access to all objects of the GroupResource in the entire namespace.
They take the form of `{ “user”: “user:1”, relation: “read”, object:”namespace:dashboard.grafana.app/dashboard” }`. This
A relation to a group_resource object grants access to all objects of the GroupResource.
They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboard” }`. This
example would grant `user:1` access to all `dashboard.grafana.app/dashboard` in the namespace.
## Folder level permissions

@ -6,24 +6,12 @@ type service-account
type render
type namespace
relations
define view: [user, service-account, render, 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 get: [user, service-account, render, team#member, role#assignee] or view
define create: [user, service-account, team#member, role#assignee] or edit
define update: [user, service-account, team#member, role#assignee] or edit
define delete: [user, service-account, team#member, role#assignee] or edit
type role
relations
define assignee: [user, service-account, team#member, role#assignee]
type team
relations
# Action sets
define admin: [user, service-account]
define member: [user, service-account] or admin

@ -11,6 +11,17 @@ extend type folder
define resource_update: [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_update 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
type group_resource
relations
define view: [user, service-account, render, 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 get: [user, service-account, render, team#member, role#assignee] or view
define create: [user, service-account, team#member, role#assignee] or edit
define update: [user, service-account, team#member, role#assignee] or edit
define delete: [user, service-account, team#member, role#assignee] or edit
type resource
relations
define view: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit

@ -56,7 +56,7 @@ func (s *Server) batchCheckItem(
allowed, ok := groupResourceAccess[groupResource]
if !ok {
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), store)
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), store)
if err != nil {
return nil, err
}

@ -44,7 +44,7 @@ func testBatchCheck(t *testing.T, server *Server) {
assert.False(t, res.Groups[groupResource].Items["2"])
})
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) {
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through group_resource", func(t *testing.T) {
groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
{Name: "1", Folder: "1"},
@ -108,7 +108,7 @@ func testBatchCheck(t *testing.T, server *Server) {
assert.False(t, res.Groups[groupResource].Items["2"])
})
t.Run("user:7 should be able to read folder {1,2} through namespace access", func(t *testing.T) {
t.Run("user:7 should be able to read folder {1,2} through group_resource access", func(t *testing.T) {
groupResource := zanzana.FormatGroupResource(folderGroup, folderResource)
res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{
{Name: "1"},

@ -22,7 +22,7 @@ func (s *Server) Capabilities(ctx context.Context, r *authzextv1.CapabilitiesReq
func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.CapabilitiesRequest, info common.TypeInfo, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) {
out := make([]string, 0, len(common.RelationsResource))
for _, relation := range info.Relations {
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
if err != nil {
return nil, err
}
@ -48,7 +48,7 @@ func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.Capabiliti
func (s *Server) capabilitiesGeneric(ctx context.Context, r *authzextv1.CapabilitiesRequest, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) {
out := make([]string, 0, len(common.RelationsResource))
for _, relation := range common.RelationsResource {
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
if err != nil {
return nil, err
}

@ -29,7 +29,7 @@ func testCapabilities(t *testing.T, server *Server) {
assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities())
})
t.Run("user:2 should be able to read and write resource:dashboards.grafana.app/dashboards/1 through namespace", func(t *testing.T) {
t.Run("user:2 should be able to read and write resource:dashboards.grafana.app/dashboards/1 through group_resource", func(t *testing.T) {
res, err := server.Capabilities(context.Background(), newReq("user:2", dashboardGroup, dashboardResource, "1", "1"))
require.NoError(t, err)
assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities())
@ -59,7 +59,7 @@ func testCapabilities(t *testing.T, server *Server) {
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())
})
t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) {
t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) {
res, err := server.Capabilities(context.Background(), newReq("user:7", folderGroup, folderResource, "", "1"))
require.NoError(t, err)
assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities())

@ -21,7 +21,7 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
}
relation := common.VerbMapping[r.GetVerb()]
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
if err != nil {
return nil, err
}
@ -36,10 +36,10 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
return s.checkGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), store)
}
// checkTyped checks on the root "namespace". If subject has access through the namespace they have access to
// every resource for that "GroupResource".
func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) {
if !common.IsNamespaceRelation(relation) {
// checkGroupResource check if subject has access to the full "GroupResource", if they do they can access every object
// within it.
func (s *Server) checkGroupResource(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) {
if !common.IsGroupResourceRelation(relation) {
return &authzv1.CheckResponse{Allowed: false}, nil
}
@ -49,7 +49,7 @@ func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, r
TupleKey: &openfgav1.CheckRequestTupleKey{
User: subject,
Relation: relation,
Object: common.NewNamespaceResourceIdent(group, resource),
Object: common.NewGroupResourceIdent(group, resource),
},
}

@ -35,7 +35,7 @@ func testCheck(t *testing.T, server *Server) {
assert.False(t, res.GetAllowed())
})
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through namespace", func(t *testing.T) {
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through group_resource", func(t *testing.T) {
res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1"))
require.NoError(t, err)
assert.True(t, res.GetAllowed())
@ -83,7 +83,7 @@ func testCheck(t *testing.T, server *Server) {
assert.True(t, res.GetAllowed())
})
t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) {
t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) {
res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "1"))
require.NoError(t, err)
assert.True(t, res.GetAllowed())

@ -22,7 +22,7 @@ func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzext
relation := common.VerbMapping[r.GetVerb()]
res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store)
if err != nil {
return nil, err
}

@ -82,14 +82,14 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
TupleKeys: []*openfgav1.TupleKey{
common.NewResourceTuple("user:1", common.RelationGet, dashboardGroup, dashboardResource, "1"),
common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "1"),
common.NewNamespaceResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource),
common.NewNamespaceResourceTuple("user:2", common.RelationUpdate, dashboardGroup, dashboardResource),
common.NewGroupResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource),
common.NewGroupResourceTuple("user:2", common.RelationUpdate, dashboardGroup, dashboardResource),
common.NewResourceTuple("user:3", common.RelationSetView, dashboardGroup, dashboardResource, "1"),
common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "1"),
common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "3"),
common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "1"),
common.NewFolderTuple("user:6", common.RelationGet, "1"),
common.NewNamespaceResourceTuple("user:7", common.RelationGet, folderGroup, folderResource),
common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource),
common.NewFolderParentTuple("5", "4"),
common.NewFolderParentTuple("6", "5"),
common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "5"),

@ -18,7 +18,7 @@ const (
TypeRole = common.TypeRole
TypeFolder = common.TypeFolder
TypeResource = common.TypeResource
TypeNamespace = common.TypeNamespace
TypeNamespace = common.TypeGroupResouce
)
const (
@ -95,7 +95,7 @@ func TranslateToResourceTuple(subject string, action, kind, name string) (*openf
}
if name == "*" {
return common.NewNamespaceResourceTuple(subject, m.relation, translation.group, translation.resource), true
return common.NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource), true
}
if translation.typ == TypeResource {

Loading…
Cancel
Save