From cbeea47dbd11ed2fdb4566a945af36fa6cd277e5 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 2 Jul 2026 19:51:20 +0200 Subject: [PATCH] fix(decomposedfs): merge service account permissions with node grants Service accounts short-circuited to ServiceAccountPermissions and ignored grants on the node (e.g. the SpaceManager grant a creator gets on project space creation), so the initial creator share was denied and the space rolled back with CODE_INTERNAL. Read the grants first, then merge the service account defaults on top. --- .../pkg/decomposedfs/node/node_test.go | 26 +++++++++++++++++++ .../pkg/decomposedfs/node/permissions.go | 9 ++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pkg/storage/pkg/decomposedfs/node/node_test.go b/pkg/storage/pkg/decomposedfs/node/node_test.go index 48d06e1a499..ce1fff24ce7 100644 --- a/pkg/storage/pkg/decomposedfs/node/node_test.go +++ b/pkg/storage/pkg/decomposedfs/node/node_test.go @@ -290,6 +290,32 @@ var _ = Describe("Node", func() { expected := ocsconv.NewManagerRole().CS3ResourcePermissions() Expect(grants.PermissionsEqual(perms, expected)).To(BeTrue()) }) + It("Merges grants with the service account permissions for service accounts", func() { + pSpace, err := env.CreateTestStorageSpace("project", &provider.Quota{QuotaMaxBytes: 2000}) + Expect(err).ToNot(HaveOccurred()) + sa := &userpb.User{Id: &userpb.UserId{OpaqueId: "service-account", Type: userpb.UserType_USER_TYPE_SERVICE}} + env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ + AddGrant: true, + Stat: true, + }, nil).Times(1) + err = env.Fs.AddGrant(env.Ctx, &provider.Reference{ + ResourceId: &provider.ResourceId{SpaceId: pSpace.SpaceId, OpaqueId: pSpace.OpaqueId}, + }, &provider.Grant{ + Grantee: &provider.Grantee{Type: provider.GranteeType_GRANTEE_TYPE_USER, Id: &provider.Grantee_UserId{UserId: sa.Id}}, + Permissions: ocsconv.NewEditorRole().CS3ResourcePermissions(), + }) + Expect(err).ToNot(HaveOccurred()) + // resolve as the service account: grant must be honored (not shadowed by an + // early return) and merged with, not replaced by, the service account defaults. + spaceRoot, err := env.Lookup.NodeFromSpaceID(env.Ctx, pSpace.SpaceId) + Expect(err).ToNot(HaveOccurred()) + saCtx := ctxpkg.ContextSetUser(env.Ctx, sa) + perms, err := node.NewPermissions(env.Lookup).AssemblePermissions(saCtx, spaceRoot) + Expect(err).ToNot(HaveOccurred()) + expected := ocsconv.NewEditorRole().CS3ResourcePermissions() + node.AddPermissions(expected, node.ServiceAccountPermissions()) + Expect(grants.PermissionsEqual(perms, expected)).To(BeTrue()) + }) It("Checks the Editor permissions on a project space and a denial", func() { storageSpace, err := env.CreateTestStorageSpace("project", &provider.Quota{QuotaMaxBytes: 2000}) Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/storage/pkg/decomposedfs/node/permissions.go b/pkg/storage/pkg/decomposedfs/node/permissions.go index 9fe2a459fa9..1f4ea0da49d 100644 --- a/pkg/storage/pkg/decomposedfs/node/permissions.go +++ b/pkg/storage/pkg/decomposedfs/node/permissions.go @@ -134,10 +134,6 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr return NoPermissions(), nil } - if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE { - return ServiceAccountPermissions(), nil - } - // are we reading a revision? if strings.Contains(n.ID, RevisionIDDelimiter) { // verify revision key format @@ -204,6 +200,11 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr return OwnerPermissions(), nil } + // merge in the general service account permissions; grants can only add to them + if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE { + AddPermissions(ap, ServiceAccountPermissions()) + } + appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Interface("user", u).Msg("returning agregated permissions") return ap, nil }