From ae81d6660adee15617c10354ad5a17a75382ba5d Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Fri, 28 Jul 2023 15:44:35 +0300 Subject: [PATCH] [#529] objectcore: Fix object content validation There are old objects where the owner of the object may not match the one who issued the token. Signed-off-by: Dmitrii Stepanov --- cmd/frostfs-node/config.go | 7 +- cmd/frostfs-node/config/object/config.go | 5 + cmd/frostfs-node/config/object/config_test.go | 2 + cmd/frostfs-node/object.go | 14 +- config/example/node.env | 1 + config/example/node.json | 3 +- config/example/node.yaml | 1 + pkg/core/object/fmt.go | 154 +++++++- pkg/core/object/fmt_test.go | 364 ++++++++++++++++-- pkg/services/object/put/service.go | 26 +- 10 files changed, 535 insertions(+), 42 deletions(-) diff --git a/cmd/frostfs-node/config.go b/cmd/frostfs-node/config.go index 808a78d28..8e103b527 100644 --- a/cmd/frostfs-node/config.go +++ b/cmd/frostfs-node/config.go @@ -511,6 +511,8 @@ type cfgObject struct { cfgLocalStorage cfgLocalStorage tombstoneLifetime uint64 + + skipSessionTokenIssuerVerification bool } type cfgNotifications struct { @@ -677,8 +679,9 @@ func initCfgGRPC() cfgGRPC { func initCfgObject(appCfg *config.Config) cfgObject { return cfgObject{ - pool: initObjectPool(appCfg), - tombstoneLifetime: objectconfig.TombstoneLifetime(appCfg), + pool: initObjectPool(appCfg), + tombstoneLifetime: objectconfig.TombstoneLifetime(appCfg), + skipSessionTokenIssuerVerification: objectconfig.Put(appCfg).SkipSessionTokenIssuerVerification(), } } diff --git a/cmd/frostfs-node/config/object/config.go b/cmd/frostfs-node/config/object/config.go index cd969852b..f7a33b5e0 100644 --- a/cmd/frostfs-node/config/object/config.go +++ b/cmd/frostfs-node/config/object/config.go @@ -51,3 +51,8 @@ func (g PutConfig) PoolSizeLocal() int { return PutPoolSizeDefault } + +// SkipSessionTokenIssuerVerification returns the value of "skip_session_token_issuer_verification" config parameter or `falseā€œ if is not defined. +func (g PutConfig) SkipSessionTokenIssuerVerification() bool { + return config.BoolSafe(g.cfg, "skip_session_token_issuer_verification") +} diff --git a/cmd/frostfs-node/config/object/config_test.go b/cmd/frostfs-node/config/object/config_test.go index 81c1ccd57..513b6e9c5 100644 --- a/cmd/frostfs-node/config/object/config_test.go +++ b/cmd/frostfs-node/config/object/config_test.go @@ -16,6 +16,7 @@ func TestObjectSection(t *testing.T) { require.Equal(t, objectconfig.PutPoolSizeDefault, objectconfig.Put(empty).PoolSizeRemote()) require.Equal(t, objectconfig.PutPoolSizeDefault, objectconfig.Put(empty).PoolSizeLocal()) require.EqualValues(t, objectconfig.DefaultTombstoneLifetime, objectconfig.TombstoneLifetime(empty)) + require.False(t, objectconfig.Put(empty).SkipSessionTokenIssuerVerification()) }) const path = "../../../../config/example/node" @@ -24,6 +25,7 @@ func TestObjectSection(t *testing.T) { require.Equal(t, 100, objectconfig.Put(c).PoolSizeRemote()) require.Equal(t, 200, objectconfig.Put(c).PoolSizeLocal()) require.EqualValues(t, 10, objectconfig.TombstoneLifetime(c)) + require.True(t, objectconfig.Put(c).SkipSessionTokenIssuerVerification()) } configtest.ForEachFileType(path, fileConfigTest) diff --git a/cmd/frostfs-node/object.go b/cmd/frostfs-node/object.go index 84411d31b..34847e36f 100644 --- a/cmd/frostfs-node/object.go +++ b/cmd/frostfs-node/object.go @@ -160,8 +160,9 @@ func initObjectService(c *cfg) { addPolicer(c, keyStorage, c.bgClientCache) traverseGen := util.NewTraverserGenerator(c.netMapSource, c.cfgObject.cnrSource, c) + irFetcher := newCachedIRFetcher(createInnerRingFetcher(c)) - sPut := createPutSvc(c, keyStorage) + sPut := createPutSvc(c, keyStorage, &irFetcher) sPutV2 := createPutSvcV2(sPut, keyStorage) @@ -184,7 +185,7 @@ func initObjectService(c *cfg) { splitSvc := createSplitService(c, sPutV2, sGetV2, sSearchV2, sDeleteV2) - aclSvc := createACLServiceV2(c, splitSvc) + aclSvc := createACLServiceV2(c, splitSvc, &irFetcher) var commonSvc objectService.Common commonSvc.Init(&c.internals, aclSvc) @@ -295,7 +296,7 @@ func createReplicator(c *cfg, keyStorage *util.KeyStorage, cache *cache.ClientCa ) } -func createPutSvc(c *cfg, keyStorage *util.KeyStorage) *putsvc.Service { +func createPutSvc(c *cfg, keyStorage *util.KeyStorage, irFetcher *cachedIRFetcher) *putsvc.Service { ls := c.cfgObject.cfgLocalStorage.localStorage var os putsvc.ObjectStorage = engineWithoutNotifications{ @@ -320,8 +321,10 @@ func createPutSvc(c *cfg, keyStorage *util.KeyStorage) *putsvc.Service { c.netMapSource, c, c.cfgNetmap.state, + irFetcher, putsvc.WithWorkerPools(c.cfgObject.pool.putRemote, c.cfgObject.pool.putLocal), putsvc.WithLogger(c.log), + putsvc.WithVerifySessionTokenIssuer(!c.cfgObject.skipSessionTokenIssuerVerification), ) } @@ -405,14 +408,13 @@ func createSplitService(c *cfg, sPutV2 *putsvcV2.Service, sGetV2 *getsvcV2.Servi ) } -func createACLServiceV2(c *cfg, splitSvc *objectService.TransportSplitter) v2.Service { +func createACLServiceV2(c *cfg, splitSvc *objectService.TransportSplitter, irFetcher *cachedIRFetcher) v2.Service { ls := c.cfgObject.cfgLocalStorage.localStorage - irFetcher := createInnerRingFetcher(c) return v2.New( splitSvc, c.netMapSource, - newCachedIRFetcher(irFetcher), + irFetcher, acl.NewChecker( c.cfgNetmap.state, c.cfgObject.eaclSource, diff --git a/config/example/node.env b/config/example/node.env index 7ed818ef9..fde65173b 100644 --- a/config/example/node.env +++ b/config/example/node.env @@ -86,6 +86,7 @@ FROSTFS_REPLICATOR_POOL_SIZE=10 # Object service section FROSTFS_OBJECT_PUT_POOL_SIZE_REMOTE=100 FROSTFS_OBJECT_PUT_POOL_SIZE_LOCAL=200 +FROSTFS_OBJECT_PUT_SKIP_SESSION_TOKEN_ISSUER_VERIFICATION=true FROSTFS_OBJECT_DELETE_TOMBSTONE_LIFETIME=10 # Storage engine section diff --git a/config/example/node.json b/config/example/node.json index a480c4a0e..e8455ee55 100644 --- a/config/example/node.json +++ b/config/example/node.json @@ -130,7 +130,8 @@ }, "put": { "pool_size_remote": 100, - "pool_size_local": 200 + "pool_size_local": 200, + "skip_session_token_issuer_verification": true } }, "storage": { diff --git a/config/example/node.yaml b/config/example/node.yaml index 39fb7714d..2ca1b426c 100644 --- a/config/example/node.yaml +++ b/config/example/node.yaml @@ -110,6 +110,7 @@ object: put: pool_size_remote: 100 # number of async workers for remote PUT operations pool_size_local: 200 # number of async workers for local PUT operations + skip_session_token_issuer_verification: true # session token issuer verification will be skipped if true storage: # note: shard configuration can be omitted for relay node (see `node.relay`) diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go index fe6654517..90365cead 100644 --- a/pkg/core/object/fmt.go +++ b/pkg/core/object/fmt.go @@ -1,20 +1,26 @@ package object import ( + "bytes" "context" "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" "errors" "fmt" "strconv" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" + 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" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) // FormatValidator represents an object format validator. @@ -26,8 +32,16 @@ type FormatValidator struct { type FormatValidatorOption func(*cfg) type cfg struct { - netState netmap.State - e LockSource + netState netmap.State + e LockSource + ir InnerRing + netmap netmap.Source + containers container.Source + verifyTokenIssuer bool +} + +type InnerRing interface { + InnerRingKeys() ([][]byte, error) } // DeleteHandler is an interface of delete queue processor. @@ -159,13 +173,117 @@ func (v *FormatValidator) validateSignatureKey(obj *objectSDK.Object) error { return v.checkOwnerKey(ownerID, key) } - if !token.Issuer().Equals(ownerID) { - return fmt.Errorf("(%T) different token issuer and object owner identifiers %s/%s", v, token.Issuer(), ownerID) + if v.verifyTokenIssuer { + signerIsIROrContainerNode, err := v.isIROrContainerNode(obj, binKey) + if err != nil { + return err + } + + if signerIsIROrContainerNode { + return nil + } + + if !token.Issuer().Equals(ownerID) { + return fmt.Errorf("(%T) different token issuer and object owner identifiers %s/%s", v, token.Issuer(), ownerID) + } + return nil } return nil } +func (v *FormatValidator) isIROrContainerNode(obj *objectSDK.Object, signerKey []byte) (bool, error) { + pKey, err := keys.NewPublicKeyFromBytes(signerKey, elliptic.P256()) + if err != nil { + return false, fmt.Errorf("(%T) failed to unmarshal signer public key: %w", v, err) + } + + isIR, err := v.isInnerRingKey(pKey.Bytes()) + if err != nil { + return false, fmt.Errorf("(%T) failed to check if signer is inner ring node: %w", v, err) + } + if isIR { + return true, nil + } + + isContainerNode, err := v.isContainerNode(pKey.Bytes(), obj) + if err != nil { + return false, fmt.Errorf("(%T) failed to check if signer is container node: %w", v, err) + } + return isContainerNode, nil +} + +func (v *FormatValidator) isInnerRingKey(key []byte) (bool, error) { + innerRingKeys, err := v.ir.InnerRingKeys() + if err != nil { + return false, err + } + + for i := range innerRingKeys { + if bytes.Equal(innerRingKeys[i], key) { + return true, nil + } + } + + return false, nil +} + +func (v *FormatValidator) isContainerNode(key []byte, obj *objectSDK.Object) (bool, error) { + cnrID, containerIDSet := obj.ContainerID() + if !containerIDSet { + return false, errNilCID + } + + cnrIDBin := make([]byte, sha256.Size) + cnrID.Encode(cnrIDBin) + + cnr, err := v.containers.Get(cnrID) + if err != nil { + return false, fmt.Errorf("failed to get container (id=%s): %w", cnrID.EncodeToString(), err) + } + + lastNetmap, err := netmap.GetLatestNetworkMap(v.netmap) + if err != nil { + return false, fmt.Errorf("failed to get latest netmap: %w", err) + } + + isContainerNode, err := v.isContainerNodeKey(lastNetmap, cnr, cnrIDBin, key) + if err != nil { + return false, fmt.Errorf("failed to check latest netmap for container nodes: %w", err) + } + if isContainerNode { + return true, nil + } + + previousNetmap, err := netmap.GetPreviousNetworkMap(v.netmap) + if err != nil { + return false, fmt.Errorf("failed to get previous netmap: %w", err) + } + + isContainerNode, err = v.isContainerNodeKey(previousNetmap, cnr, cnrIDBin, key) + if err != nil { + return false, fmt.Errorf("failed to check previous netmap for container nodes: %w", err) + } + return isContainerNode, nil +} + +func (v *FormatValidator) isContainerNodeKey(nm *netmapSDK.NetMap, cnr *container.Container, cnrIDBin, key []byte) (bool, error) { + cnrVectors, err := nm.ContainerNodes(cnr.Value.PlacementPolicy(), cnrIDBin) + if err != nil { + return false, err + } + + for i := range cnrVectors { + for j := range cnrVectors[i] { + if bytes.Equal(cnrVectors[i][j].PublicKey(), key) { + return true, nil + } + } + } + + return false, nil +} + func (v *FormatValidator) checkOwnerKey(id user.ID, key frostfsecdsa.PublicKey) error { var id2 user.ID user.IDFromKey(&id2, (ecdsa.PublicKey)(key)) @@ -387,3 +505,31 @@ func WithLockSource(e LockSource) FormatValidatorOption { c.e = e } } + +// WithInnerRing return option to set Inner Ring source. +func WithInnerRing(ir InnerRing) FormatValidatorOption { + return func(c *cfg) { + c.ir = ir + } +} + +// WithNetmapSource return option to set Netmap source. +func WithNetmapSource(ns netmap.Source) FormatValidatorOption { + return func(c *cfg) { + c.netmap = ns + } +} + +// WithContainersSource return option to set Containers source. +func WithContainersSource(cs container.Source) FormatValidatorOption { + return func(c *cfg) { + c.containers = cs + } +} + +// WithVerifySessionTokenIssuer return option to set verify session token issuer value. +func WithVerifySessionTokenIssuer(verifySessionTokenIssuer bool) FormatValidatorOption { + return func(c *cfg) { + c.verifyTokenIssuer = verifySessionTokenIssuer + } +} diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go index 392ecf605..fbd82906d 100644 --- a/pkg/core/object/fmt_test.go +++ b/pkg/core/object/fmt_test.go @@ -3,12 +3,17 @@ package object import ( "context" "crypto/ecdsa" + "fmt" "strconv" "testing" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" + "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" @@ -106,34 +111,6 @@ func TestFormatValidator_Validate(t *testing.T) { require.NoError(t, v.Validate(context.Background(), obj, false)) }) - t.Run("invalid w/ session token", func(t *testing.T) { - var idOwner user.ID - user.IDFromKey(&idOwner, ownerKey.PrivateKey.PublicKey) - - var randomUserID user.ID - randPrivKey, err := keys.NewPrivateKey() - require.NoError(t, err) - user.IDFromKey(&randomUserID, randPrivKey.PrivateKey.PublicKey) - - tok := sessiontest.Object() - fsPubKey := frostfsecdsa.PublicKey(*ownerKey.PublicKey()) - tok.SetID(uuid.New()) - tok.SetAuthKey(&fsPubKey) - tok.SetExp(100500) - tok.SetIat(1) - tok.SetNbf(1) - err = tok.Sign(ownerKey.PrivateKey) - require.NoError(t, err) - - obj := objectSDK.New() - obj.SetContainerID(cidtest.ID()) - obj.SetSessionToken(tok) - obj.SetOwnerID(&randomUserID) - require.NoError(t, objectSDK.SetIDWithSignature(ownerKey.PrivateKey, obj)) - - require.Error(t, v.Validate(context.Background(), obj, false)) //invalid owner - }) - t.Run("correct w/o session token", func(t *testing.T) { obj := blankValidObject(&ownerKey.PrivateKey) @@ -284,3 +261,334 @@ func TestFormatValidator_Validate(t *testing.T) { }) }) } + +func TestFormatValidator_ValidateTokenIssuer(t *testing.T) { + const curEpoch = 13 + + ls := testLockSource{ + m: make(map[oid.Address]bool), + } + + signer, err := keys.NewPrivateKey() + require.NoError(t, err) + + var owner user.ID + ownerPrivKey, err := keys.NewPrivateKey() + require.NoError(t, err) + user.IDFromKey(&owner, ownerPrivKey.PrivateKey.PublicKey) + + t.Run("different issuer and owner, verify issuer disabled", func(t *testing.T) { + t.Parallel() + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + WithLockSource(ls), + WithVerifySessionTokenIssuer(false), + ) + + tok := sessiontest.Object() + fsPubKey := frostfsecdsa.PublicKey(*signer.PublicKey()) + tok.SetID(uuid.New()) + tok.SetAuthKey(&fsPubKey) + tok.SetExp(100500) + tok.SetIat(1) + tok.SetNbf(1) + require.NoError(t, tok.Sign(signer.PrivateKey)) + + obj := objectSDK.New() + obj.SetContainerID(cidtest.ID()) + obj.SetSessionToken(tok) + obj.SetOwnerID(&owner) + require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) + + require.NoError(t, v.Validate(context.Background(), obj, false)) + }) + + t.Run("different issuer and owner, issuer is IR node, verify issuer enabled", func(t *testing.T) { + t.Parallel() + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + WithLockSource(ls), + WithVerifySessionTokenIssuer(true), + WithInnerRing(&testIRSource{ + irNodes: [][]byte{signer.PublicKey().Bytes()}, + }), + ) + + tok := sessiontest.Object() + fsPubKey := frostfsecdsa.PublicKey(*signer.PublicKey()) + tok.SetID(uuid.New()) + tok.SetAuthKey(&fsPubKey) + tok.SetExp(100500) + tok.SetIat(1) + tok.SetNbf(1) + require.NoError(t, tok.Sign(signer.PrivateKey)) + + obj := objectSDK.New() + obj.SetContainerID(cidtest.ID()) + obj.SetSessionToken(tok) + obj.SetOwnerID(&owner) + require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) + + require.NoError(t, v.Validate(context.Background(), obj, false)) + }) + + t.Run("different issuer and owner, issuer is container node in current epoch, verify issuer enabled", func(t *testing.T) { + t.Parallel() + + tok := sessiontest.Object() + fsPubKey := frostfsecdsa.PublicKey(*signer.PublicKey()) + tok.SetID(uuid.New()) + tok.SetAuthKey(&fsPubKey) + tok.SetExp(100500) + tok.SetIat(1) + tok.SetNbf(1) + require.NoError(t, tok.Sign(signer.PrivateKey)) + + cnrID := cidtest.ID() + cont := containerSDK.Container{} + cont.Init() + pp := netmap.PlacementPolicy{} + require.NoError(t, pp.DecodeString("REP 1")) + cont.SetPlacementPolicy(pp) + + var node netmap.NodeInfo + node.SetPublicKey(signer.PublicKey().Bytes()) + currentEpochNM := &netmap.NetMap{} + currentEpochNM.SetEpoch(curEpoch) + currentEpochNM.SetNodes([]netmap.NodeInfo{node}) + + obj := objectSDK.New() + obj.SetContainerID(cnrID) + obj.SetSessionToken(tok) + obj.SetOwnerID(&owner) + require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) + + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + WithLockSource(ls), + WithVerifySessionTokenIssuer(true), + WithInnerRing(&testIRSource{ + irNodes: [][]byte{}, + }), + WithContainersSource( + &testContainerSource{ + containers: map[cid.ID]*container.Container{ + cnrID: { + Value: cont, + }, + }, + }, + ), + WithNetmapSource( + &testNetmapSource{ + netmaps: map[uint64]*netmap.NetMap{ + curEpoch: currentEpochNM, + }, + currentEpoch: curEpoch, + }, + ), + ) + + require.NoError(t, v.Validate(context.Background(), obj, false)) + }) + + t.Run("different issuer and owner, issuer is container node in previous epoch, verify issuer enabled", func(t *testing.T) { + t.Parallel() + + tok := sessiontest.Object() + fsPubKey := frostfsecdsa.PublicKey(*signer.PublicKey()) + tok.SetID(uuid.New()) + tok.SetAuthKey(&fsPubKey) + tok.SetExp(100500) + tok.SetIat(1) + tok.SetNbf(1) + require.NoError(t, tok.Sign(signer.PrivateKey)) + + cnrID := cidtest.ID() + cont := containerSDK.Container{} + cont.Init() + pp := netmap.PlacementPolicy{} + require.NoError(t, pp.DecodeString("REP 1")) + cont.SetPlacementPolicy(pp) + + var issuerNode netmap.NodeInfo + issuerNode.SetPublicKey(signer.PublicKey().Bytes()) + + var nonIssuerNode netmap.NodeInfo + nonIssuerKey, err := keys.NewPrivateKey() + require.NoError(t, err) + nonIssuerNode.SetPublicKey(nonIssuerKey.PublicKey().Bytes()) + + currentEpochNM := &netmap.NetMap{} + currentEpochNM.SetEpoch(curEpoch) + currentEpochNM.SetNodes([]netmap.NodeInfo{nonIssuerNode}) + + previousEpochNM := &netmap.NetMap{} + previousEpochNM.SetEpoch(curEpoch - 1) + previousEpochNM.SetNodes([]netmap.NodeInfo{issuerNode}) + + obj := objectSDK.New() + obj.SetContainerID(cnrID) + obj.SetSessionToken(tok) + obj.SetOwnerID(&owner) + require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) + + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + WithLockSource(ls), + WithVerifySessionTokenIssuer(true), + WithInnerRing(&testIRSource{ + irNodes: [][]byte{}, + }), + WithContainersSource( + &testContainerSource{ + containers: map[cid.ID]*container.Container{ + cnrID: { + Value: cont, + }, + }, + }, + ), + WithNetmapSource( + &testNetmapSource{ + netmaps: map[uint64]*netmap.NetMap{ + curEpoch: currentEpochNM, + curEpoch - 1: previousEpochNM, + }, + currentEpoch: curEpoch, + }, + ), + ) + + require.NoError(t, v.Validate(context.Background(), obj, false)) + }) + + t.Run("different issuer and owner, issuer is unknown, verify issuer enabled", func(t *testing.T) { + t.Parallel() + + tok := sessiontest.Object() + fsPubKey := frostfsecdsa.PublicKey(*signer.PublicKey()) + tok.SetID(uuid.New()) + tok.SetAuthKey(&fsPubKey) + tok.SetExp(100500) + tok.SetIat(1) + tok.SetNbf(1) + require.NoError(t, tok.Sign(signer.PrivateKey)) + + cnrID := cidtest.ID() + cont := containerSDK.Container{} + cont.Init() + pp := netmap.PlacementPolicy{} + require.NoError(t, pp.DecodeString("REP 1")) + cont.SetPlacementPolicy(pp) + + var nonIssuerNode1 netmap.NodeInfo + nonIssuerKey1, err := keys.NewPrivateKey() + require.NoError(t, err) + nonIssuerNode1.SetPublicKey(nonIssuerKey1.PublicKey().Bytes()) + + var nonIssuerNode2 netmap.NodeInfo + nonIssuerKey2, err := keys.NewPrivateKey() + require.NoError(t, err) + nonIssuerNode2.SetPublicKey(nonIssuerKey2.PublicKey().Bytes()) + + currentEpochNM := &netmap.NetMap{} + currentEpochNM.SetEpoch(curEpoch) + currentEpochNM.SetNodes([]netmap.NodeInfo{nonIssuerNode1}) + + previousEpochNM := &netmap.NetMap{} + previousEpochNM.SetEpoch(curEpoch - 1) + previousEpochNM.SetNodes([]netmap.NodeInfo{nonIssuerNode2}) + + obj := objectSDK.New() + obj.SetContainerID(cnrID) + obj.SetSessionToken(tok) + obj.SetOwnerID(&owner) + require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) + + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + WithLockSource(ls), + WithVerifySessionTokenIssuer(true), + WithInnerRing(&testIRSource{ + irNodes: [][]byte{}, + }), + WithContainersSource( + &testContainerSource{ + containers: map[cid.ID]*container.Container{ + cnrID: { + Value: cont, + }, + }, + }, + ), + WithNetmapSource( + &testNetmapSource{ + netmaps: map[uint64]*netmap.NetMap{ + curEpoch: currentEpochNM, + curEpoch - 1: previousEpochNM, + }, + currentEpoch: curEpoch, + }, + ), + ) + + require.Error(t, v.Validate(context.Background(), obj, false)) + }) +} + +type testIRSource struct { + irNodes [][]byte +} + +func (s *testIRSource) InnerRingKeys() ([][]byte, error) { + return s.irNodes, 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 +} + +type testNetmapSource struct { + netmaps map[uint64]*netmap.NetMap + currentEpoch uint64 +} + +func (s *testNetmapSource) GetNetMap(diff uint64) (*netmap.NetMap, error) { + if diff >= s.currentEpoch { + return nil, fmt.Errorf("invalid diff") + } + return s.GetNetMapByEpoch(s.currentEpoch - diff) +} + +func (s *testNetmapSource) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) { + if nm, found := s.netmaps[epoch]; found { + return nm, nil + } + return nil, fmt.Errorf("netmap not found") +} + +func (s *testNetmapSource) Epoch() (uint64, error) { + return s.currentEpoch, nil +} diff --git a/pkg/services/object/put/service.go b/pkg/services/object/put/service.go index 7f2600f9c..4095cf124 100644 --- a/pkg/services/object/put/service.go +++ b/pkg/services/object/put/service.go @@ -29,6 +29,14 @@ type ClientConstructor interface { Get(client.NodeInfo) (client.MultiAddressClient, error) } +type InnerRing interface { + InnerRingKeys() ([][]byte, error) +} + +type FormatValidatorConfig interface { + VerifySessionTokenIssuer() bool +} + type cfg struct { keyStorage *objutil.KeyStorage @@ -51,6 +59,8 @@ type cfg struct { clientConstructor ClientConstructor log *logger.Logger + + verifySessionTokenIssuer bool } func NewService(ks *objutil.KeyStorage, @@ -61,6 +71,7 @@ func NewService(ks *objutil.KeyStorage, ns netmap.Source, nk netmap.AnnouncedKeys, nst netmap.State, + ir InnerRing, opts ...Option) *Service { c := &cfg{ remotePool: util.NewPseudoWorkerPool(), @@ -80,7 +91,14 @@ func NewService(ks *objutil.KeyStorage, opts[i](c) } - c.fmtValidator = object.NewFormatValidator(object.WithLockSource(os), object.WithNetState(nst)) + c.fmtValidator = object.NewFormatValidator( + object.WithLockSource(os), + object.WithNetState(nst), + object.WithInnerRing(ir), + object.WithNetmapSource(ns), + object.WithContainersSource(cs), + object.WithVerifySessionTokenIssuer(c.verifySessionTokenIssuer), + ) return &Service{ cfg: c, @@ -104,3 +122,9 @@ func WithLogger(l *logger.Logger) Option { c.log = l } } + +func WithVerifySessionTokenIssuer(v bool) Option { + return func(c *cfg) { + c.verifySessionTokenIssuer = v + } +}