[#1125] objectSvc: Add EC header APE check

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
Dmitrii Stepanov 2024-05-08 10:02:54 +03:00
parent 368218f0cc
commit 0144117cc9
6 changed files with 272 additions and 23 deletions

View file

@ -445,8 +445,11 @@ func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *object
c.log, c.log,
objectAPE.NewChecker( objectAPE.NewChecker(
c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter, c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter,
objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage), objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage, c.cfgObject.getSvc),
c.shared.frostfsidClient, c.shared.frostfsidClient,
c.netMapSource,
c.cfgObject.cnrSource,
c.binPublicKey,
), ),
splitSvc, splitSvc,
) )

View file

@ -122,7 +122,7 @@ func (c SenderClassifier) isContainerKey(
return false, err return false, err
} }
in, err := lookupKeyInContainer(nm, owner, idCnr, cnr) in, err := LookupKeyInContainer(nm, owner, idCnr, cnr)
if err != nil { if err != nil {
return false, err return false, err
} else if in { } else if in {
@ -136,12 +136,12 @@ func (c SenderClassifier) isContainerKey(
return false, err return false, err
} }
return lookupKeyInContainer(nm, owner, idCnr, cnr) return LookupKeyInContainer(nm, owner, idCnr, cnr)
} }
func lookupKeyInContainer( func LookupKeyInContainer(
nm *netmap.NetMap, nm *netmap.NetMap,
owner, idCnr []byte, pkey, idCnr []byte,
cnr container.Container, cnr container.Container,
) (bool, error) { ) (bool, error) {
cnrVectors, err := nm.ContainerNodes(cnr.PlacementPolicy(), idCnr) cnrVectors, err := nm.ContainerNodes(cnr.PlacementPolicy(), idCnr)
@ -151,7 +151,7 @@ func lookupKeyInContainer(
for i := range cnrVectors { for i := range cnrVectors {
for j := range cnrVectors[i] { for j := range cnrVectors[i] {
if bytes.Equal(cnrVectors[i][j].PublicKey(), owner) { if bytes.Equal(cnrVectors[i][j].PublicKey(), pkey) {
return true, nil return true, nil
} }
} }

View file

@ -6,7 +6,9 @@ import (
"fmt" "fmt"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" 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" 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" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
@ -18,19 +20,21 @@ import (
type checkerImpl struct { type checkerImpl struct {
chainRouter policyengine.ChainRouter chainRouter policyengine.ChainRouter
headerProvider HeaderProvider headerProvider HeaderProvider
frostFSIDClient frostfsidcore.SubjectProvider 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{ return &checkerImpl{
chainRouter: chainRouter, chainRouter: chainRouter,
headerProvider: headerProvider, headerProvider: headerProvider,
frostFSIDClient: frostFSIDClient, frostFSIDClient: frostFSIDClient,
nm: nm,
cnrSource: cnrSource,
nodePK: nodePK,
} }
} }

View file

@ -3,15 +3,22 @@ package ape
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"testing" "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-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" "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" 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" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" 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/user"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "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 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 var addr oid.Address
addr.SetContainer(c) addr.SetContainer(c)
addr.SetObject(o) addr.SetObject(o)
@ -404,7 +411,7 @@ func TestAPECheck(t *testing.T) {
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls) router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
checker := NewChecker(router, headerProvider, frostfsidProvider) checker := NewChecker(router, headerProvider, frostfsidProvider, nil, nil, nil)
prm := Prm{ prm := Prm{
Method: method, 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)
})
}

View file

@ -2,11 +2,14 @@ package ape
import ( import (
"context" "context"
"crypto/sha256"
"fmt" "fmt"
"strconv" "strconv"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" 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" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 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 { if prm.Header != nil {
header = prm.Header header = prm.Header
} else if prm.Object != nil && !prm.WithoutHeaderRequest { } 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 { if err == nil {
header = headerObjSDK.ToV2().GetHeader() header = headerObjSDK.ToV2().GetHeader()
} }
} }
header = c.fillHeaderWithECParent(ctx, prm, header)
reqProps := map[string]string{ reqProps := map[string]string{
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey, nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
nativeschema.PropertyKeyActorRole: prm.Role, nativeschema.PropertyKeyActorRole: prm.Role,
@ -136,6 +139,65 @@ func (c *checkerImpl) newAPERequest(ctx context.Context, prm Prm) (aperequest.Re
), nil ), 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. // 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) { func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm) (map[string]string, error) {
if reqProps == nil { if reqProps == nil {

View file

@ -10,6 +10,8 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
objectSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object" 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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
@ -31,23 +33,36 @@ type Service struct {
var _ objectSvc.ServiceServer = (*Service)(nil) var _ objectSvc.ServiceServer = (*Service)(nil)
type HeaderProvider interface { 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 { type storageEngineHeaderProvider struct {
storageEngine *engine.StorageEngine 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 var addr oid.Address
addr.SetContainer(cnr) addr.SetContainer(cnr)
addr.SetObject(objID) addr.SetObject(objID)
if local {
return engine.Head(ctx, p.storageEngine, addr) 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{ return storageEngineHeaderProvider{
storageEngine: e, storageEngine: e,
getSvc: s,
} }
} }