[#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,
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,
)

View file

@ -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
}
}

View file

@ -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"
@ -18,19 +20,21 @@ import (
type checkerImpl struct {
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,
frostFSIDClient: frostFSIDClient,
nm: nm,
cnrSource: cnrSource,
nodePK: nodePK,
}
}

View file

@ -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)
})
}

View file

@ -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 {

View file

@ -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)
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,
}
}