diff --git a/pkg/ape/request/frostfsid.go b/pkg/ape/request/frostfsid.go new file mode 100644 index 000000000..fa620e58c --- /dev/null +++ b/pkg/ape/request/frostfsid.go @@ -0,0 +1,36 @@ +package request + +import ( + "fmt" + "strconv" + "strings" + + frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" +) + +// FormFrostfsIDRequestProperties forms frostfsid specific request properties like user-claim tags and group ID. +func FormFrostfsIDRequestProperties(frostFSIDClient frostfsidcore.SubjectProvider, pk *keys.PublicKey) (map[string]string, error) { + reqProps := make(map[string]string) + subj, err := frostFSIDClient.GetSubjectExtended(pk.GetScriptHash()) + if err != nil { + if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) { + return nil, fmt.Errorf("get subject error: %w", err) + } + return reqProps, nil + } + for k, v := range subj.KV { + propertyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) + reqProps[propertyKey] = v + } + + groups := make([]string, len(subj.Groups)) + for i, group := range subj.Groups { + groups[i] = strconv.FormatInt(group.ID, 10) + } + reqProps[commonschema.PropertyKeyFrostFSIDGroupID] = apechain.FormCondSliceContainsValue(groups) + + return reqProps, nil +} diff --git a/pkg/services/container/ape.go b/pkg/services/container/ape.go index 9c38d1bba..22440ab80 100644 --- a/pkg/services/container/ape.go +++ b/pkg/services/container/ape.go @@ -27,7 +27,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" - commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) @@ -619,16 +618,12 @@ func (ac *apeChecker) fillWithUserClaimTags(reqProps map[string]string, pk *keys if reqProps == nil { reqProps = make(map[string]string) } - subj, err := ac.frostFSIDClient.GetSubject(pk.GetScriptHash()) + props, err := aperequest.FormFrostfsIDRequestProperties(ac.frostFSIDClient, pk) if err != nil { - if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) { - return nil, fmt.Errorf("get subject error: %w", err) - } - return reqProps, nil + return reqProps, err } - for k, v := range subj.KV { - properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) - reqProps[properyKey] = v + for propertyName, properyValue := range props { + reqProps[propertyName] = properyValue } return reqProps, nil } diff --git a/pkg/services/container/ape_test.go b/pkg/services/container/ape_test.go index a2fe79956..94c3027c0 100644 --- a/pkg/services/container/ape_test.go +++ b/pkg/services/container/ape_test.go @@ -45,6 +45,7 @@ func TestAPE(t *testing.T) { t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound) t.Run("deny get container for others", testDenyGetContainerForOthers) t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag) + t.Run("deny get container by group id", testDenyGetContainerByGroupID) t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR) t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken) t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken) @@ -279,6 +280,19 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) { }, }, }, + subjectsExt: map[util.Uint160]*client.SubjectExtended{ + pk.PublicKey().GetScriptHash(): { + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19888, + }, + }, + }, + }, } apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv) @@ -339,6 +353,104 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) { require.ErrorAs(t, err, &errAccessDenied) } +func testDenyGetContainerByGroupID(t *testing.T) { + t.Parallel() + srv := &srvStub{ + calls: map[string]int{}, + } + router := inmemory.NewInMemory() + contRdr := &containerStub{ + c: map[cid.ID]*containercore.Container{}, + } + ir := &irStub{ + keys: [][]byte{}, + } + nm := &netmapStub{} + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + + frostfsIDSubjectReader := &frostfsidStub{ + subjects: map[util.Uint160]*client.Subject{ + pk.PublicKey().GetScriptHash(): { + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + }, + }, + subjectsExt: map[util.Uint160]*client.SubjectExtended{ + pk.PublicKey().GetScriptHash(): { + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19888, + }, + }, + }, + }, + } + + apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv) + + contID := cidtest.ID() + testContainer := containertest.Container() + pp := netmap.PlacementPolicy{} + require.NoError(t, pp.DecodeString("REP 1")) + testContainer.SetPlacementPolicy(pp) + contRdr.c[contID] = &containercore.Container{Value: testContainer} + + nm.currentEpoch = 100 + nm.netmaps = map[uint64]*netmap.NetMap{} + var testNetmap netmap.NetMap + testNetmap.SetEpoch(nm.currentEpoch) + testNetmap.SetNodes([]netmap.NodeInfo{{}}) + nm.netmaps[nm.currentEpoch] = &testNetmap + nm.netmaps[nm.currentEpoch-1] = &testNetmap + + _, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{ + Rules: []chain.Rule{ + { + Status: chain.AccessDenied, + Actions: chain.Actions{ + Names: []string{ + nativeschema.MethodGetContainer, + }, + }, + Resources: chain.Resources{ + Names: []string{ + fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()), + }, + }, + Condition: []chain.Condition{ + { + Object: chain.ObjectRequest, + Key: commonschema.PropertyKeyFrostFSIDGroupID, + Value: "19888", + Op: chain.CondStringEquals, + }, + }, + }, + }, + }) + require.NoError(t, err) + + req := &container.GetRequest{} + req.SetBody(&container.GetRequestBody{}) + var refContID refs.ContainerID + contID.WriteToV2(&refContID) + req.GetBody().SetContainerID(&refContID) + + require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req)) + + resp, err := apeSrv.Get(context.Background(), req) + require.Nil(t, resp) + var errAccessDenied *apistatus.ObjectAccessDenied + require.ErrorAs(t, err, &errAccessDenied) +} + func testDenySetContainerEACLForIR(t *testing.T) { t.Parallel() srv := &srvStub{ @@ -621,6 +733,21 @@ func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) { Name: testDomainName, }, }, + subjectsExt: map[util.Uint160]*client.SubjectExtended{ + ownerScriptHash: { + Namespace: testDomainName, + Name: testDomainName, + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19888, + }, + }, + }, + }, } apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv) resp, err := apeSrv.Put(context.Background(), req) @@ -690,6 +817,21 @@ func testDenyPutContainerInvalidNamespace(t *testing.T) { Name: testDomainName, }, }, + subjectsExt: map[util.Uint160]*client.SubjectExtended{ + ownerScriptHash: { + Namespace: testDomainName, + Name: testDomainName, + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19888, + }, + }, + }, + }, } apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv) resp, err := apeSrv.Put(context.Background(), req) @@ -800,6 +942,34 @@ func testDenyListContainersValidationNamespaceError(t *testing.T) { Name: testDomainName, }, }, + subjectsExt: map[util.Uint160]*client.SubjectExtended{ + actorScriptHash: { + Namespace: actorDomain, + Name: actorDomain, + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19777, + }, + }, + }, + ownerScriptHash: { + Namespace: testDomainName, + Name: testDomainName, + KV: map[string]string{ + "tag-attr1": "value1", + "tag-attr2": "value2", + }, + Groups: []*client.Group{ + { + ID: 19888, + }, + }, + }, + }, } apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv) diff --git a/pkg/services/object/ape/request.go b/pkg/services/object/ape/request.go index 82b6adb1f..cbecc73e3 100644 --- a/pkg/services/object/ape/request.go +++ b/pkg/services/object/ape/request.go @@ -4,17 +4,14 @@ import ( "context" "fmt" "strconv" - "strings" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" - frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" - commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) @@ -148,16 +145,12 @@ func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm) if err != nil { return nil, err } - subj, err := c.frostFSIDClient.GetSubject(pk.GetScriptHash()) + props, err := aperequest.FormFrostfsIDRequestProperties(c.frostFSIDClient, pk) if err != nil { - if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) { - return nil, fmt.Errorf("get subject error: %w", err) - } - return reqProps, nil + return reqProps, err } - for k, v := range subj.KV { - properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) - reqProps[properyKey] = v + for propertyName, properyValue := range props { + reqProps[propertyName] = properyValue } return reqProps, nil } diff --git a/pkg/services/object/ape/request_test.go b/pkg/services/object/ape/request_test.go index cda78e69c..5c74e7d1d 100644 --- a/pkg/services/object/ape/request_test.go +++ b/pkg/services/object/ape/request_test.go @@ -275,6 +275,7 @@ func TestNewAPERequest(t *testing.T) { nativeschema.PropertyKeyActorRole: prm.Role, fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1", fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2", + commonschema.PropertyKeyFrostFSIDGroupID: "1", }, ) diff --git a/pkg/services/tree/ape.go b/pkg/services/tree/ape.go index 47e7cb41d..622049d6a 100644 --- a/pkg/services/tree/ape.go +++ b/pkg/services/tree/ape.go @@ -8,14 +8,12 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" - frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" - commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) @@ -80,16 +78,12 @@ func (s *Service) fillWithUserClaimTags(reqProps map[string]string, publicKey *k if reqProps == nil { reqProps = make(map[string]string) } - subj, err := s.frostfsidSubjectProvider.GetSubject(publicKey.GetScriptHash()) + props, err := aperequest.FormFrostfsIDRequestProperties(s.frostfsidSubjectProvider, publicKey) if err != nil { - if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) { - return nil, fmt.Errorf("get subject error: %w", err) - } - return reqProps, nil + return reqProps, err } - for k, v := range subj.KV { - properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) - reqProps[properyKey] = v + for propertyName, properyValue := range props { + reqProps[propertyName] = properyValue } return reqProps, nil }