Validate token issuer #528

Merged
fyrchik merged 1 commit from dstepanov-yadro/frostfs-node:fix/check_session_issuer into master 2024-09-04 19:51:01 +00:00
5 changed files with 95 additions and 120 deletions

View file

@ -1,10 +1,8 @@
package object package object
import ( import (
"bytes"
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256" "crypto/sha256"
"errors" "errors"
"fmt" "fmt"
@ -14,18 +12,20 @@ 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/core/container" "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"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
"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"
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.
type FormatValidator struct { type FormatValidator struct {
*cfg *cfg
senderClassifier SenderClassifier
} }
// FormatValidatorOption represents a FormatValidator constructor option. // FormatValidatorOption represents a FormatValidator constructor option.
@ -37,13 +37,10 @@ type cfg struct {
ir InnerRing ir InnerRing
netmap netmap.Source netmap netmap.Source
containers container.Source containers container.Source
log *logger.Logger
verifyTokenIssuer bool verifyTokenIssuer bool
} }
type InnerRing interface {
InnerRingKeys() ([][]byte, error)
}
// DeleteHandler is an interface of delete queue processor. // DeleteHandler is an interface of delete queue processor.
type DeleteHandler interface { type DeleteHandler interface {
// DeleteObjects places objects to a removal queue. // DeleteObjects places objects to a removal queue.
@ -94,6 +91,7 @@ func NewFormatValidator(opts ...FormatValidatorOption) *FormatValidator {
return &FormatValidator{ return &FormatValidator{
cfg: cfg, cfg: cfg,
senderClassifier: NewSenderClassifier(cfg.ir, cfg.netmap, cfg.log),
} }
} }
@ -193,42 +191,6 @@ func (v *FormatValidator) validateSignatureKey(obj *objectSDK.Object) error {
} }
func (v *FormatValidator) isIROrContainerNode(obj *objectSDK.Object, signerKey []byte) (bool, error) { 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() cnrID, containerIDSet := obj.ContainerID()
if !containerIDSet { if !containerIDSet {
fyrchik marked this conversation as resolved Outdated

We already have role calculation in the acl service: https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/services/object/acl/v2/classifier.go#L27
Can we reuse it somehow?

We already have role calculation in the acl service: https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/services/object/acl/v2/classifier.go#L27 Can we reuse it somehow?

Done: sender_classifier moved to object/core package, ACL service and format validator now using it.

Done: `sender_classifier` moved to `object/core` package, ACL service and format validator now using it.
return false, errNilCID return false, errNilCID
@ -242,46 +204,11 @@ func (v *FormatValidator) isContainerNode(key []byte, obj *objectSDK.Object) (bo
return false, fmt.Errorf("failed to get container (id=%s): %w", cnrID.EncodeToString(), err) return false, fmt.Errorf("failed to get container (id=%s): %w", cnrID.EncodeToString(), err)
} }
lastNetmap, err := netmap.GetLatestNetworkMap(v.netmap) res, err := v.senderClassifier.IsInnerRingOrContainerNode(signerKey, cnrID, cnr.Value)
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 { if err != nil {
return false, err return false, err
} }
return res.Role == acl.RoleContainer || res.Role == acl.RoleInnerRing, nil
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 {
@ -533,3 +460,10 @@ func WithVerifySessionTokenIssuer(verifySessionTokenIssuer bool) FormatValidator
c.verifyTokenIssuer = verifySessionTokenIssuer c.verifyTokenIssuer = verifySessionTokenIssuer
} }
} }
// WithLogger return option to set logger.
func WithLogger(l *logger.Logger) FormatValidatorOption {
return func(c *cfg) {
c.log = l
}
}

View file

@ -9,6 +9,7 @@ import (
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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" 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"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
@ -22,6 +23,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
) )
func blankValidObject(key *ecdsa.PrivateKey) *objectSDK.Object { func blankValidObject(key *ecdsa.PrivateKey) *objectSDK.Object {
@ -63,6 +65,7 @@ func TestFormatValidator_Validate(t *testing.T) {
epoch: curEpoch, epoch: curEpoch,
}), }),
WithLockSource(ls), WithLockSource(ls),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
ownerKey, err := keys.NewPrivateKey() ownerKey, err := keys.NewPrivateKey()
@ -285,6 +288,7 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
}), }),
WithLockSource(ls), WithLockSource(ls),
WithVerifySessionTokenIssuer(false), WithVerifySessionTokenIssuer(false),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
tok := sessiontest.Object() tok := sessiontest.Object()
@ -307,6 +311,14 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
t.Run("different issuer and owner, issuer is IR node, verify issuer enabled", func(t *testing.T) { t.Run("different issuer and owner, issuer is IR node, verify issuer enabled", func(t *testing.T) {
t.Parallel() t.Parallel()
cnrID := cidtest.ID()
cont := containerSDK.Container{}
cont.Init()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
cont.SetPlacementPolicy(pp)
v := NewFormatValidator( v := NewFormatValidator(
WithNetState(testNetState{ WithNetState(testNetState{
epoch: curEpoch, epoch: curEpoch,
@ -316,6 +328,16 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
WithInnerRing(&testIRSource{ WithInnerRing(&testIRSource{
irNodes: [][]byte{signer.PublicKey().Bytes()}, irNodes: [][]byte{signer.PublicKey().Bytes()},
}), }),
WithContainersSource(
&testContainerSource{
containers: map[cid.ID]*container.Container{
cnrID: {
Value: cont,
},
},
},
),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
tok := sessiontest.Object() tok := sessiontest.Object()
@ -328,7 +350,7 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
require.NoError(t, tok.Sign(signer.PrivateKey)) require.NoError(t, tok.Sign(signer.PrivateKey))
obj := objectSDK.New() obj := objectSDK.New()
obj.SetContainerID(cidtest.ID()) obj.SetContainerID(cnrID)
obj.SetSessionToken(tok) obj.SetSessionToken(tok)
obj.SetOwnerID(&owner) obj.SetOwnerID(&owner)
require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj)) require.NoError(t, objectSDK.SetIDWithSignature(signer.PrivateKey, obj))
@ -393,6 +415,7 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
currentEpoch: curEpoch, currentEpoch: curEpoch,
}, },
), ),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
require.NoError(t, v.Validate(context.Background(), obj, false)) require.NoError(t, v.Validate(context.Background(), obj, false))
@ -466,6 +489,7 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
currentEpoch: curEpoch, currentEpoch: curEpoch,
}, },
), ),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
require.NoError(t, v.Validate(context.Background(), obj, false)) require.NoError(t, v.Validate(context.Background(), obj, false))
@ -541,6 +565,7 @@ func TestFormatValidator_ValidateTokenIssuer(t *testing.T) {
currentEpoch: curEpoch, currentEpoch: curEpoch,
}, },
), ),
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
) )
require.Error(t, v.Validate(context.Background(), obj, false)) require.Error(t, v.Validate(context.Background(), obj, false))

View file

@ -1,4 +1,4 @@
package v2 package object
import ( import (
"bytes" "bytes"
@ -11,50 +11,64 @@ import (
"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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"go.uber.org/zap" "go.uber.org/zap"
) )
type senderClassifier struct { type InnerRing interface {
InnerRingKeys() ([][]byte, error)
}
type SenderClassifier struct {
log *logger.Logger log *logger.Logger
innerRing InnerRingFetcher innerRing InnerRing
netmap core.Source netmap core.Source
} }
type classifyResult struct { func NewSenderClassifier(innerRing InnerRing, netmap core.Source, log *logger.Logger) SenderClassifier {
role acl.Role return SenderClassifier{
key []byte log: log,
innerRing: innerRing,
netmap: netmap,
}
} }
func (c senderClassifier) classify( type ClassifyResult struct {
req MetaWithToken, Role acl.Role
Key []byte
}
func (c SenderClassifier) Classify(
ownerID *user.ID,
ownerKey *keys.PublicKey,
idCnr cid.ID, idCnr cid.ID,
cnr container.Container) (res *classifyResult, err error) { cnr container.Container) (res *ClassifyResult, err error) {
ownerID, ownerKey, err := req.RequestOwner()
if err != nil {
return nil, err
}
ownerKeyInBytes := ownerKey.Bytes() ownerKeyInBytes := ownerKey.Bytes()
// TODO: #767 get owner from frostfs.id if present // TODO: #767 get owner from frostfs.id if present
// if request owner is the same as container owner, return RoleUser // if request owner is the same as container owner, return RoleUser
if ownerID.Equals(cnr.Owner()) { if ownerID.Equals(cnr.Owner()) {
return &classifyResult{ return &ClassifyResult{
role: acl.RoleOwner, Role: acl.RoleOwner,
key: ownerKeyInBytes, Key: ownerKeyInBytes,
}, nil }, nil
} }
return c.IsInnerRingOrContainerNode(ownerKeyInBytes, idCnr, cnr)
}
func (c SenderClassifier) IsInnerRingOrContainerNode(ownerKeyInBytes []byte, idCnr cid.ID, cnr container.Container) (*ClassifyResult, error) {
isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes) isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes)
if err != nil { if err != nil {
// do not throw error, try best case matching // do not throw error, try best case matching
c.log.Debug(logs.V2CantCheckIfRequestFromInnerRing, c.log.Debug(logs.V2CantCheckIfRequestFromInnerRing,
zap.String("error", err.Error())) zap.String("error", err.Error()))
} else if isInnerRingNode { } else if isInnerRingNode {
return &classifyResult{ return &ClassifyResult{
role: acl.RoleInnerRing, Role: acl.RoleInnerRing,
key: ownerKeyInBytes, Key: ownerKeyInBytes,
}, nil }, nil
} }
@ -69,20 +83,20 @@ func (c senderClassifier) classify(
c.log.Debug(logs.V2CantCheckIfRequestFromContainerNode, c.log.Debug(logs.V2CantCheckIfRequestFromContainerNode,
zap.String("error", err.Error())) zap.String("error", err.Error()))
} else if isContainerNode { } else if isContainerNode {
return &classifyResult{ return &ClassifyResult{
role: acl.RoleContainer, Role: acl.RoleContainer,
key: ownerKeyInBytes, Key: ownerKeyInBytes,
}, nil }, nil
} }
// if none of above, return RoleOthers // if none of above, return RoleOthers
return &classifyResult{ return &ClassifyResult{
role: acl.RoleOthers, Role: acl.RoleOthers,
key: ownerKeyInBytes, Key: ownerKeyInBytes,
}, nil }, nil
} }
func (c senderClassifier) isInnerRingKey(owner []byte) (bool, error) { func (c SenderClassifier) isInnerRingKey(owner []byte) (bool, error) {
innerRingKeys, err := c.innerRing.InnerRingKeys() innerRingKeys, err := c.innerRing.InnerRingKeys()
if err != nil { if err != nil {
return false, err return false, err
@ -98,7 +112,7 @@ func (c senderClassifier) isInnerRingKey(owner []byte) (bool, error) {
return false, nil return false, nil
} }
func (c senderClassifier) isContainerKey( func (c SenderClassifier) isContainerKey(
owner, idCnr []byte, owner, idCnr []byte,
cnr container.Container) (bool, error) { cnr container.Container) (bool, error) {
nm, err := core.GetLatestNetworkMap(c.netmap) // first check current netmap nm, err := core.GetLatestNetworkMap(c.netmap) // first check current netmap

View file

@ -9,6 +9,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "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"
objectCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
@ -24,7 +25,7 @@ import (
type Service struct { type Service struct {
*cfg *cfg
c senderClassifier c objectCore.SenderClassifier
} }
type putStreamBasicChecker struct { type putStreamBasicChecker struct {
@ -95,11 +96,7 @@ func New(next object.ServiceServer,
return Service{ return Service{
cfg: cfg, cfg: cfg,
c: senderClassifier{ c: objectCore.NewSenderClassifier(cfg.irFetcher, cfg.nm, cfg.log),
log: cfg.log,
innerRing: cfg.irFetcher,
netmap: cfg.nm,
},
} }
} }
@ -652,20 +649,24 @@ func (b Service) findRequestInfo(req MetaWithToken, idCnr cid.ID, op acl.Op) (in
} }
// find request role and key // find request role and key
res, err := b.c.classify(req, idCnr, cnr.Value) ownerID, ownerKey, err := req.RequestOwner()
if err != nil {
return info, err
}
res, err := b.c.Classify(ownerID, ownerKey, idCnr, cnr.Value)
if err != nil { if err != nil {
return info, err return info, err
} }
info.basicACL = cnr.Value.BasicACL() info.basicACL = cnr.Value.BasicACL()
info.requestRole = res.role info.requestRole = res.Role
info.operation = op info.operation = op
info.cnrOwner = cnr.Value.Owner() info.cnrOwner = cnr.Value.Owner()
info.idCnr = idCnr info.idCnr = idCnr
// it is assumed that at the moment the key will be valid, // it is assumed that at the moment the key will be valid,
// otherwise the request would not pass validation // otherwise the request would not pass validation
info.senderKey = res.key info.senderKey = res.Key
// add bearer token if it is present in request // add bearer token if it is present in request
info.bearer = req.bearer info.bearer = req.bearer

View file

@ -98,6 +98,7 @@ func NewService(ks *objutil.KeyStorage,
object.WithNetmapSource(ns), object.WithNetmapSource(ns),
object.WithContainersSource(cs), object.WithContainersSource(cs),
object.WithVerifySessionTokenIssuer(c.verifySessionTokenIssuer), object.WithVerifySessionTokenIssuer(c.verifySessionTokenIssuer),
object.WithLogger(c.log),
) )
return &Service{ return &Service{