forked from TrueCloudLab/frostfs-node
[#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 <d.stepanov@yadro.com>
This commit is contained in:
parent
ab2614ec2d
commit
ae81d6660a
10 changed files with 535 additions and 42 deletions
|
@ -511,6 +511,8 @@ type cfgObject struct {
|
||||||
cfgLocalStorage cfgLocalStorage
|
cfgLocalStorage cfgLocalStorage
|
||||||
|
|
||||||
tombstoneLifetime uint64
|
tombstoneLifetime uint64
|
||||||
|
|
||||||
|
skipSessionTokenIssuerVerification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type cfgNotifications struct {
|
type cfgNotifications struct {
|
||||||
|
@ -677,8 +679,9 @@ func initCfgGRPC() cfgGRPC {
|
||||||
|
|
||||||
func initCfgObject(appCfg *config.Config) cfgObject {
|
func initCfgObject(appCfg *config.Config) cfgObject {
|
||||||
return cfgObject{
|
return cfgObject{
|
||||||
pool: initObjectPool(appCfg),
|
pool: initObjectPool(appCfg),
|
||||||
tombstoneLifetime: objectconfig.TombstoneLifetime(appCfg),
|
tombstoneLifetime: objectconfig.TombstoneLifetime(appCfg),
|
||||||
|
skipSessionTokenIssuerVerification: objectconfig.Put(appCfg).SkipSessionTokenIssuerVerification(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,3 +51,8 @@ func (g PutConfig) PoolSizeLocal() int {
|
||||||
|
|
||||||
return PutPoolSizeDefault
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -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).PoolSizeRemote())
|
||||||
require.Equal(t, objectconfig.PutPoolSizeDefault, objectconfig.Put(empty).PoolSizeLocal())
|
require.Equal(t, objectconfig.PutPoolSizeDefault, objectconfig.Put(empty).PoolSizeLocal())
|
||||||
require.EqualValues(t, objectconfig.DefaultTombstoneLifetime, objectconfig.TombstoneLifetime(empty))
|
require.EqualValues(t, objectconfig.DefaultTombstoneLifetime, objectconfig.TombstoneLifetime(empty))
|
||||||
|
require.False(t, objectconfig.Put(empty).SkipSessionTokenIssuerVerification())
|
||||||
})
|
})
|
||||||
|
|
||||||
const path = "../../../../config/example/node"
|
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, 100, objectconfig.Put(c).PoolSizeRemote())
|
||||||
require.Equal(t, 200, objectconfig.Put(c).PoolSizeLocal())
|
require.Equal(t, 200, objectconfig.Put(c).PoolSizeLocal())
|
||||||
require.EqualValues(t, 10, objectconfig.TombstoneLifetime(c))
|
require.EqualValues(t, 10, objectconfig.TombstoneLifetime(c))
|
||||||
|
require.True(t, objectconfig.Put(c).SkipSessionTokenIssuerVerification())
|
||||||
}
|
}
|
||||||
|
|
||||||
configtest.ForEachFileType(path, fileConfigTest)
|
configtest.ForEachFileType(path, fileConfigTest)
|
||||||
|
|
|
@ -160,8 +160,9 @@ func initObjectService(c *cfg) {
|
||||||
addPolicer(c, keyStorage, c.bgClientCache)
|
addPolicer(c, keyStorage, c.bgClientCache)
|
||||||
|
|
||||||
traverseGen := util.NewTraverserGenerator(c.netMapSource, c.cfgObject.cnrSource, c)
|
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)
|
sPutV2 := createPutSvcV2(sPut, keyStorage)
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ func initObjectService(c *cfg) {
|
||||||
|
|
||||||
splitSvc := createSplitService(c, sPutV2, sGetV2, sSearchV2, sDeleteV2)
|
splitSvc := createSplitService(c, sPutV2, sGetV2, sSearchV2, sDeleteV2)
|
||||||
|
|
||||||
aclSvc := createACLServiceV2(c, splitSvc)
|
aclSvc := createACLServiceV2(c, splitSvc, &irFetcher)
|
||||||
|
|
||||||
var commonSvc objectService.Common
|
var commonSvc objectService.Common
|
||||||
commonSvc.Init(&c.internals, aclSvc)
|
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
|
ls := c.cfgObject.cfgLocalStorage.localStorage
|
||||||
|
|
||||||
var os putsvc.ObjectStorage = engineWithoutNotifications{
|
var os putsvc.ObjectStorage = engineWithoutNotifications{
|
||||||
|
@ -320,8 +321,10 @@ func createPutSvc(c *cfg, keyStorage *util.KeyStorage) *putsvc.Service {
|
||||||
c.netMapSource,
|
c.netMapSource,
|
||||||
c,
|
c,
|
||||||
c.cfgNetmap.state,
|
c.cfgNetmap.state,
|
||||||
|
irFetcher,
|
||||||
putsvc.WithWorkerPools(c.cfgObject.pool.putRemote, c.cfgObject.pool.putLocal),
|
putsvc.WithWorkerPools(c.cfgObject.pool.putRemote, c.cfgObject.pool.putLocal),
|
||||||
putsvc.WithLogger(c.log),
|
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
|
ls := c.cfgObject.cfgLocalStorage.localStorage
|
||||||
irFetcher := createInnerRingFetcher(c)
|
|
||||||
|
|
||||||
return v2.New(
|
return v2.New(
|
||||||
splitSvc,
|
splitSvc,
|
||||||
c.netMapSource,
|
c.netMapSource,
|
||||||
newCachedIRFetcher(irFetcher),
|
irFetcher,
|
||||||
acl.NewChecker(
|
acl.NewChecker(
|
||||||
c.cfgNetmap.state,
|
c.cfgNetmap.state,
|
||||||
c.cfgObject.eaclSource,
|
c.cfgObject.eaclSource,
|
||||||
|
|
|
@ -86,6 +86,7 @@ FROSTFS_REPLICATOR_POOL_SIZE=10
|
||||||
# Object service section
|
# Object service section
|
||||||
FROSTFS_OBJECT_PUT_POOL_SIZE_REMOTE=100
|
FROSTFS_OBJECT_PUT_POOL_SIZE_REMOTE=100
|
||||||
FROSTFS_OBJECT_PUT_POOL_SIZE_LOCAL=200
|
FROSTFS_OBJECT_PUT_POOL_SIZE_LOCAL=200
|
||||||
|
FROSTFS_OBJECT_PUT_SKIP_SESSION_TOKEN_ISSUER_VERIFICATION=true
|
||||||
FROSTFS_OBJECT_DELETE_TOMBSTONE_LIFETIME=10
|
FROSTFS_OBJECT_DELETE_TOMBSTONE_LIFETIME=10
|
||||||
|
|
||||||
# Storage engine section
|
# Storage engine section
|
||||||
|
|
|
@ -130,7 +130,8 @@
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"pool_size_remote": 100,
|
"pool_size_remote": 100,
|
||||||
"pool_size_local": 200
|
"pool_size_local": 200,
|
||||||
|
"skip_session_token_issuer_verification": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
|
|
|
@ -110,6 +110,7 @@ object:
|
||||||
put:
|
put:
|
||||||
pool_size_remote: 100 # number of async workers for remote PUT operations
|
pool_size_remote: 100 # number of async workers for remote PUT operations
|
||||||
pool_size_local: 200 # number of async workers for local 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:
|
storage:
|
||||||
# note: shard configuration can be omitted for relay node (see `node.relay`)
|
# note: shard configuration can be omitted for relay node (see `node.relay`)
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
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-api-go/v2/refs"
|
"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"
|
"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"
|
||||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
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"
|
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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatValidator represents an object format validator.
|
// FormatValidator represents an object format validator.
|
||||||
|
@ -26,8 +32,16 @@ type FormatValidator struct {
|
||||||
type FormatValidatorOption func(*cfg)
|
type FormatValidatorOption func(*cfg)
|
||||||
|
|
||||||
type cfg struct {
|
type cfg struct {
|
||||||
netState netmap.State
|
netState netmap.State
|
||||||
e LockSource
|
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.
|
// 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)
|
return v.checkOwnerKey(ownerID, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !token.Issuer().Equals(ownerID) {
|
if v.verifyTokenIssuer {
|
||||||
return fmt.Errorf("(%T) different token issuer and object owner identifiers %s/%s", v, token.Issuer(), ownerID)
|
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
|
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 {
|
func (v *FormatValidator) checkOwnerKey(id user.ID, key frostfsecdsa.PublicKey) error {
|
||||||
var id2 user.ID
|
var id2 user.ID
|
||||||
user.IDFromKey(&id2, (ecdsa.PublicKey)(key))
|
user.IDFromKey(&id2, (ecdsa.PublicKey)(key))
|
||||||
|
@ -387,3 +505,31 @@ func WithLockSource(e LockSource) FormatValidatorOption {
|
||||||
c.e = e
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,17 @@ package object
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
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"
|
||||||
|
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"
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
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"
|
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"
|
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))
|
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) {
|
t.Run("correct w/o session token", func(t *testing.T) {
|
||||||
obj := blankValidObject(&ownerKey.PrivateKey)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,14 @@ type ClientConstructor interface {
|
||||||
Get(client.NodeInfo) (client.MultiAddressClient, error)
|
Get(client.NodeInfo) (client.MultiAddressClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InnerRing interface {
|
||||||
|
InnerRingKeys() ([][]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormatValidatorConfig interface {
|
||||||
|
VerifySessionTokenIssuer() bool
|
||||||
|
}
|
||||||
|
|
||||||
type cfg struct {
|
type cfg struct {
|
||||||
keyStorage *objutil.KeyStorage
|
keyStorage *objutil.KeyStorage
|
||||||
|
|
||||||
|
@ -51,6 +59,8 @@ type cfg struct {
|
||||||
clientConstructor ClientConstructor
|
clientConstructor ClientConstructor
|
||||||
|
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
|
|
||||||
|
verifySessionTokenIssuer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(ks *objutil.KeyStorage,
|
func NewService(ks *objutil.KeyStorage,
|
||||||
|
@ -61,6 +71,7 @@ func NewService(ks *objutil.KeyStorage,
|
||||||
ns netmap.Source,
|
ns netmap.Source,
|
||||||
nk netmap.AnnouncedKeys,
|
nk netmap.AnnouncedKeys,
|
||||||
nst netmap.State,
|
nst netmap.State,
|
||||||
|
ir InnerRing,
|
||||||
opts ...Option) *Service {
|
opts ...Option) *Service {
|
||||||
c := &cfg{
|
c := &cfg{
|
||||||
remotePool: util.NewPseudoWorkerPool(),
|
remotePool: util.NewPseudoWorkerPool(),
|
||||||
|
@ -80,7 +91,14 @@ func NewService(ks *objutil.KeyStorage,
|
||||||
opts[i](c)
|
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{
|
return &Service{
|
||||||
cfg: c,
|
cfg: c,
|
||||||
|
@ -104,3 +122,9 @@ func WithLogger(l *logger.Logger) Option {
|
||||||
c.log = l
|
c.log = l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithVerifySessionTokenIssuer(v bool) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.verifySessionTokenIssuer = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue