From 0144117cc97f1221f7f018c84c0203b5a5ba9400 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Wed, 8 May 2024 10:02:54 +0300 Subject: [PATCH] [#1125] objectSvc: Add EC header APE check Signed-off-by: Dmitrii Stepanov --- cmd/frostfs-node/object.go | 5 +- pkg/core/object/sender_classifier.go | 10 +- pkg/services/object/ape/checker.go | 22 +-- pkg/services/object/ape/checker_test.go | 169 +++++++++++++++++++++++- pkg/services/object/ape/request.go | 66 ++++++++- pkg/services/object/ape/service.go | 23 +++- 6 files changed, 272 insertions(+), 23 deletions(-) diff --git a/cmd/frostfs-node/object.go b/cmd/frostfs-node/object.go index a7a084fd1..833daf628 100644 --- a/cmd/frostfs-node/object.go +++ b/cmd/frostfs-node/object.go @@ -445,8 +445,11 @@ func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *object c.log, objectAPE.NewChecker( c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter, - objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage), + objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage, c.cfgObject.getSvc), c.shared.frostfsidClient, + c.netMapSource, + c.cfgObject.cnrSource, + c.binPublicKey, ), splitSvc, ) diff --git a/pkg/core/object/sender_classifier.go b/pkg/core/object/sender_classifier.go index ac881431e..13d0ebfb1 100644 --- a/pkg/core/object/sender_classifier.go +++ b/pkg/core/object/sender_classifier.go @@ -122,7 +122,7 @@ func (c SenderClassifier) isContainerKey( return false, err } - in, err := lookupKeyInContainer(nm, owner, idCnr, cnr) + in, err := LookupKeyInContainer(nm, owner, idCnr, cnr) if err != nil { return false, err } else if in { @@ -136,12 +136,12 @@ func (c SenderClassifier) isContainerKey( return false, err } - return lookupKeyInContainer(nm, owner, idCnr, cnr) + return LookupKeyInContainer(nm, owner, idCnr, cnr) } -func lookupKeyInContainer( +func LookupKeyInContainer( nm *netmap.NetMap, - owner, idCnr []byte, + pkey, idCnr []byte, cnr container.Container, ) (bool, error) { cnrVectors, err := nm.ContainerNodes(cnr.PlacementPolicy(), idCnr) @@ -151,7 +151,7 @@ func lookupKeyInContainer( for i := range cnrVectors { for j := range cnrVectors[i] { - if bytes.Equal(cnrVectors[i][j].PublicKey(), owner) { + if bytes.Equal(cnrVectors[i][j].PublicKey(), pkey) { return true, nil } } diff --git a/pkg/services/object/ape/checker.go b/pkg/services/object/ape/checker.go index e74b05379..c39f922f7 100644 --- a/pkg/services/object/ape/checker.go +++ b/pkg/services/object/ape/checker.go @@ -6,7 +6,9 @@ import ( "fmt" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" @@ -17,20 +19,22 @@ import ( ) type checkerImpl struct { - chainRouter policyengine.ChainRouter - - headerProvider HeaderProvider - + chainRouter policyengine.ChainRouter + headerProvider HeaderProvider frostFSIDClient frostfsidcore.SubjectProvider + nm netmap.Source + cnrSource container.Source + nodePK []byte } -func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider, frostFSIDClient frostfsidcore.SubjectProvider) Checker { +func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider, frostFSIDClient frostfsidcore.SubjectProvider, nm netmap.Source, cnrSource container.Source, nodePK []byte) Checker { return &checkerImpl{ - chainRouter: chainRouter, - - headerProvider: headerProvider, - + chainRouter: chainRouter, + headerProvider: headerProvider, frostFSIDClient: frostFSIDClient, + nm: nm, + cnrSource: cnrSource, + nodePK: nodePK, } } diff --git a/pkg/services/object/ape/checker_test.go b/pkg/services/object/ape/checker_test.go index c591bc494..b5f064428 100644 --- a/pkg/services/object/ape/checker_test.go +++ b/pkg/services/object/ape/checker_test.go @@ -3,15 +3,22 @@ package ape import ( "context" "encoding/hex" + "errors" "fmt" "testing" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" + containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" @@ -36,7 +43,7 @@ func (h *headerProviderMock) addHeader(c cid.ID, o oid.ID, header *objectSDK.Obj h.m[addr] = header } -func (h *headerProviderMock) GetHeader(_ context.Context, c cid.ID, o oid.ID) (*objectSDK.Object, error) { +func (h *headerProviderMock) GetHeader(_ context.Context, c cid.ID, o oid.ID, _ bool) (*objectSDK.Object, error) { var addr oid.Address addr.SetContainer(c) addr.SetObject(o) @@ -404,7 +411,7 @@ func TestAPECheck(t *testing.T) { router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls) - checker := NewChecker(router, headerProvider, frostfsidProvider) + checker := NewChecker(router, headerProvider, frostfsidProvider, nil, nil, nil) prm := Prm{ Method: method, @@ -436,3 +443,161 @@ func TestAPECheck(t *testing.T) { }) } } + +type netmapStub struct { + netmaps map[uint64]*netmapSDK.NetMap + currentEpoch uint64 +} + +func (s *netmapStub) GetNetMap(diff uint64) (*netmapSDK.NetMap, error) { + if diff >= s.currentEpoch { + return nil, errors.New("invalid diff") + } + return s.GetNetMapByEpoch(s.currentEpoch - diff) +} + +func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, error) { + if nm, found := s.netmaps[epoch]; found { + return nm, nil + } + return nil, errors.New("netmap not found") +} + +func (s *netmapStub) Epoch() (uint64, error) { + return s.currentEpoch, nil +} + +type testContainerSource struct { + containers map[cid.ID]*container.Container +} + +func (s *testContainerSource) Get(cnrID cid.ID) (*container.Container, error) { + if cnr, found := s.containers[cnrID]; found { + return cnr, nil + } + return nil, fmt.Errorf("container not found") +} + +func (s *testContainerSource) DeletionInfo(cid.ID) (*container.DelInfo, error) { + return nil, nil +} + +func TestPutECChunk(t *testing.T) { + headerProvider := newHeaderProviderMock() + frostfsidProvider := newFrostfsIDProviderMock(t) + + cnr := newContainerIDSDK(t, containerID) + obj := newObjectIDSDK(t, &objectID) + + ls := inmemory.NewInmemoryLocalStorage() + ms := inmemory.NewInmemoryMorphRuleChainStorage() + + ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(containerID), &chain.Chain{ + Rules: []chain.Rule{ + { + Status: chain.AccessDenied, + Actions: chain.Actions{Names: methodsOptionalOID}, + Resources: chain.Resources{ + Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)}, + }, + Any: true, + Condition: []chain.Condition{ + { + Op: chain.CondStringEquals, + Object: chain.ObjectResource, + Key: "attr1", + Value: "value", + }, + }, + }, + }, + MatchType: chain.MatchTypeFirstMatch, + }) + + router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls) + + node1Key, err := keys.NewPrivateKey() + require.NoError(t, err) + node1 := netmapSDK.NodeInfo{} + node1.SetPublicKey(node1Key.PublicKey().Bytes()) + netmap := &netmapSDK.NetMap{} + netmap.SetEpoch(100) + netmap.SetNodes([]netmapSDK.NodeInfo{node1}) + + nm := &netmapStub{ + currentEpoch: 100, + netmaps: map[uint64]*netmapSDK.NetMap{ + 100: netmap, + }, + } + + cont := containerSDK.Container{} + cont.Init() + pp := netmapSDK.PlacementPolicy{} + require.NoError(t, pp.DecodeString("REP 1")) + cont.SetPlacementPolicy(pp) + cs := &testContainerSource{ + containers: map[cid.ID]*container.Container{ + cnr: { + Value: cont, + }, + }, + } + + checker := NewChecker(router, headerProvider, frostfsidProvider, nm, cs, node1Key.PublicKey().Bytes()) + + ecParentID := oidtest.ID() + chunkHeader := newHeaderObjectSDK(cnr, obj, nil).ToV2().GetHeader() + ecHeader := object.ECHeader{ + Index: 1, + Total: 5, + Parent: &refs.ObjectID{}, + } + chunkHeader.SetEC(&ecHeader) + ecParentID.WriteToV2(ecHeader.Parent) + + parentHeader := newHeaderObjectSDK(cnr, &ecParentID, &headerObjectSDKParams{ + attributes: []struct { + key string + val string + }{ + { + key: "attr1", + val: "value", + }, + }, + }) + headerProvider.addHeader(cnr, ecParentID, parentHeader) + + t.Run("access denied for container node", func(t *testing.T) { + prm := Prm{ + Method: nativeschema.MethodPutObject, + Container: cnr, + Object: obj, + Role: role, + SenderKey: senderKey, + Header: chunkHeader, + SoftAPECheck: true, + } + + err = checker.CheckAPE(context.Background(), prm) + require.Error(t, err) + }) + t.Run("access allowed for non container node", func(t *testing.T) { + otherKey, err := keys.NewPrivateKey() + require.NoError(t, err) + checker = NewChecker(router, headerProvider, frostfsidProvider, nm, cs, otherKey.PublicKey().Bytes()) + prm := Prm{ + Method: nativeschema.MethodPutObject, + Container: cnr, + Object: obj, + Role: nativeschema.PropertyValueContainerRoleOthers, + SenderKey: senderKey, + Header: chunkHeader, + SoftAPECheck: true, + } + + err = checker.CheckAPE(context.Background(), prm) + require.NoError(t, err) + }) +} diff --git a/pkg/services/object/ape/request.go b/pkg/services/object/ape/request.go index cbecc73e3..71a9aec2c 100644 --- a/pkg/services/object/ape/request.go +++ b/pkg/services/object/ape/request.go @@ -2,11 +2,14 @@ package ape import ( "context" + "crypto/sha256" "fmt" "strconv" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" "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" @@ -110,12 +113,12 @@ func (c *checkerImpl) newAPERequest(ctx context.Context, prm Prm) (aperequest.Re if prm.Header != nil { header = prm.Header } else if prm.Object != nil && !prm.WithoutHeaderRequest { - headerObjSDK, err := c.headerProvider.GetHeader(ctx, prm.Container, *prm.Object) + headerObjSDK, err := c.headerProvider.GetHeader(ctx, prm.Container, *prm.Object, true) if err == nil { header = headerObjSDK.ToV2().GetHeader() } } - + header = c.fillHeaderWithECParent(ctx, prm, header) reqProps := map[string]string{ nativeschema.PropertyKeyActorPublicKey: prm.SenderKey, nativeschema.PropertyKeyActorRole: prm.Role, @@ -136,6 +139,65 @@ func (c *checkerImpl) newAPERequest(ctx context.Context, prm Prm) (aperequest.Re ), nil } +func (c *checkerImpl) fillHeaderWithECParent(ctx context.Context, prm Prm, header *objectV2.Header) *objectV2.Header { + if header == nil { + return header + } + if header.GetEC() == nil { + return header + } + if prm.Role == nativeschema.PropertyValueContainerRoleContainer || + prm.Role == nativeschema.PropertyValueContainerRoleIR { + return header + } + parentObjRefID := header.GetEC().Parent + if parentObjRefID == nil { + return header + } + var parentObjID oid.ID + if err := parentObjID.ReadFromV2(*parentObjRefID); err != nil { + return header + } + // only container node have access to collect parent object + contNode, err := c.currentNodeIsContainerNode(prm.Container) + if err != nil || !contNode { + return header + } + parentObj, err := c.headerProvider.GetHeader(ctx, prm.Container, parentObjID, false) + if err != nil { + return header + } + return parentObj.ToV2().GetHeader() +} + +func (c *checkerImpl) currentNodeIsContainerNode(cnrID cid.ID) (bool, error) { + cnr, err := c.cnrSource.Get(cnrID) + if err != nil { + return false, err + } + + nm, err := netmap.GetLatestNetworkMap(c.nm) + if err != nil { + return false, err + } + idCnr := make([]byte, sha256.Size) + cnrID.Encode(idCnr) + + in, err := object.LookupKeyInContainer(nm, c.nodePK, idCnr, cnr.Value) + if err != nil { + return false, err + } else if in { + return true, nil + } + + nm, err = netmap.GetPreviousNetworkMap(c.nm) + if err != nil { + return false, err + } + + return object.LookupKeyInContainer(nm, c.nodePK, idCnr, cnr.Value) +} + // fillWithUserClaimTags fills ape request properties with user claim tags getting them from frostfsid contract by actor public key. func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm) (map[string]string, error) { if reqProps == nil { diff --git a/pkg/services/object/ape/service.go b/pkg/services/object/ape/service.go index 95f36be79..63c2a9909 100644 --- a/pkg/services/object/ape/service.go +++ b/pkg/services/object/ape/service.go @@ -10,6 +10,8 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" objectSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object" + getsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/get" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" @@ -31,23 +33,36 @@ type Service struct { var _ objectSvc.ServiceServer = (*Service)(nil) type HeaderProvider interface { - GetHeader(ctx context.Context, cnr cid.ID, oid oid.ID) (*objectSDK.Object, error) + GetHeader(ctx context.Context, cnr cid.ID, oid oid.ID, local bool) (*objectSDK.Object, error) } type storageEngineHeaderProvider struct { storageEngine *engine.StorageEngine + getSvc *getsvc.Service } -func (p storageEngineHeaderProvider) GetHeader(ctx context.Context, cnr cid.ID, objID oid.ID) (*objectSDK.Object, error) { +func (p storageEngineHeaderProvider) GetHeader(ctx context.Context, cnr cid.ID, objID oid.ID, local bool) (*objectSDK.Object, error) { var addr oid.Address addr.SetContainer(cnr) addr.SetObject(objID) - return engine.Head(ctx, p.storageEngine, addr) + if local { + return engine.Head(ctx, p.storageEngine, addr) + } + w := getsvc.NewSimpleObjectWriter() + var headPrm getsvc.HeadPrm + headPrm.WithAddress(addr) + headPrm.SetHeaderWriter(w) + headPrm.SetCommonParameters(&util.CommonPrm{}) // default values are ok + if err := p.getSvc.Head(ctx, headPrm); err != nil { + return nil, err + } + return w.Object(), nil } -func NewStorageEngineHeaderProvider(e *engine.StorageEngine) HeaderProvider { +func NewStorageEngineHeaderProvider(e *engine.StorageEngine, s *getsvc.Service) HeaderProvider { return storageEngineHeaderProvider{ storageEngine: e, + getSvc: s, } }