forked from TrueCloudLab/frostfs-node
e252f93dbf
Classifier looks at list of inner ring nodes and container nodes from current and previous epoch to classify request. Sometimes these checks might return error. Consider there is a request from unknown key and container's placement policy valid for current epoch and invalid for past epoch. Classifier tries to find if key belongs to container node from current epoch -- it is not. Then it tries to find if key belongs to container node from past epoch and it throws error, because placement policy is invalid for past epoch. This is a legit case and classifier should ignore such errors to provide best effort in matching. The only error classifier should return is an error when request does not contain public key to classify it. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
218 lines
6 KiB
Go
218 lines
6 KiB
Go
package acl
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg"
|
|
acl "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
|
"github.com/nspcc-dev/neofs-api-go/util/signature"
|
|
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
|
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
core "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type (
|
|
InnerRingFetcher interface {
|
|
InnerRingKeys() ([][]byte, error)
|
|
}
|
|
|
|
metaWithToken struct {
|
|
vheader *session.RequestVerificationHeader
|
|
token *session.SessionToken
|
|
bearer *bearer.BearerToken
|
|
src interface{}
|
|
}
|
|
|
|
SenderClassifier struct {
|
|
log *zap.Logger
|
|
innerRing InnerRingFetcher
|
|
netmap core.Source
|
|
}
|
|
)
|
|
|
|
// fixme: update classifier constructor
|
|
func NewSenderClassifier(l *zap.Logger, ir InnerRingFetcher, nm core.Source) SenderClassifier {
|
|
return SenderClassifier{
|
|
log: l,
|
|
innerRing: ir,
|
|
netmap: nm,
|
|
}
|
|
}
|
|
|
|
func (c SenderClassifier) Classify(
|
|
req metaWithToken,
|
|
cid *container.ID,
|
|
cnr *container.Container) (role acl.Role, isIR bool, key []byte, err error) {
|
|
if cid == nil {
|
|
return 0, false, nil, errors.Wrap(ErrMalformedRequest, "container id is not set")
|
|
}
|
|
|
|
ownerID, ownerKey, err := requestOwner(req)
|
|
if err != nil {
|
|
return 0, false, nil, err
|
|
}
|
|
|
|
ownerKeyInBytes := crypto.MarshalPublicKey(ownerKey)
|
|
|
|
// todo: get owner from neofs.id if present
|
|
|
|
// if request owner is the same as container owner, return RoleUser
|
|
if bytes.Equal(cnr.OwnerID().ToV2().GetValue(), ownerID.ToV2().GetValue()) {
|
|
return acl.RoleUser, false, ownerKeyInBytes, nil
|
|
}
|
|
|
|
isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes)
|
|
if err != nil {
|
|
// do not throw error, try best case matching
|
|
c.log.Debug("can't check if request from inner ring",
|
|
zap.String("error", err.Error()))
|
|
} else if isInnerRingNode {
|
|
return acl.RoleSystem, true, ownerKeyInBytes, nil
|
|
}
|
|
|
|
isContainerNode, err := c.isContainerKey(ownerKeyInBytes, cid.ToV2().GetValue(), cnr)
|
|
if err != nil {
|
|
// error might happen if request has `RoleOther` key and placement
|
|
// is not possible for previous epoch, so
|
|
// do not throw error, try best case matching
|
|
c.log.Debug("can't check if request from container node",
|
|
zap.String("error", err.Error()))
|
|
} else if isContainerNode {
|
|
return acl.RoleSystem, false, ownerKeyInBytes, nil
|
|
}
|
|
|
|
// if none of above, return RoleOthers
|
|
return acl.RoleOthers, false, ownerKeyInBytes, nil
|
|
}
|
|
|
|
func requestOwner(req metaWithToken) (*owner.ID, *ecdsa.PublicKey, error) {
|
|
if req.vheader == nil {
|
|
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil verification header")
|
|
}
|
|
|
|
// if session token is presented, use it as truth source
|
|
if req.token.GetBody() != nil {
|
|
// verify signature of session token
|
|
return ownerFromToken(req.token)
|
|
}
|
|
|
|
// otherwise get original body signature
|
|
bodySignature := originalBodySignature(req.vheader)
|
|
if bodySignature == nil {
|
|
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil at body signature")
|
|
}
|
|
|
|
key := crypto.UnmarshalPublicKey(bodySignature.Key())
|
|
neo3wallet, err := owner.NEO3WalletFromPublicKey(key)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "can't create neo3 wallet")
|
|
}
|
|
|
|
// form user from public key
|
|
user := new(owner.ID)
|
|
user.SetNeo3Wallet(neo3wallet)
|
|
|
|
return user, key, nil
|
|
}
|
|
|
|
func originalBodySignature(v *session.RequestVerificationHeader) *pkg.Signature {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
for v.GetOrigin() != nil {
|
|
v = v.GetOrigin()
|
|
}
|
|
|
|
return pkg.NewSignatureFromV2(v.GetBodySignature())
|
|
}
|
|
|
|
func (c SenderClassifier) isInnerRingKey(owner []byte) (bool, error) {
|
|
innerRingKeys, err := c.innerRing.InnerRingKeys()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// if request owner key in the inner ring list, return RoleSystem
|
|
for i := range innerRingKeys {
|
|
if bytes.Equal(innerRingKeys[i], owner) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (c SenderClassifier) isContainerKey(
|
|
owner, cid []byte,
|
|
cnr *container.Container) (bool, error) {
|
|
nm, err := core.GetLatestNetworkMap(c.netmap) // first check current netmap
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
in, err := lookupKeyInContainer(nm, owner, cid, cnr)
|
|
if err != nil {
|
|
return false, err
|
|
} else if in {
|
|
return true, nil
|
|
}
|
|
|
|
// then check previous netmap, this can happen in-between epoch change
|
|
// when node migrates data from last epoch container
|
|
nm, err = core.GetPreviousNetworkMap(c.netmap)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return lookupKeyInContainer(nm, owner, cid, cnr)
|
|
}
|
|
|
|
func lookupKeyInContainer(
|
|
nm *netmap.Netmap,
|
|
owner, cid []byte,
|
|
cnr *container.Container) (bool, error) {
|
|
cnrNodes, err := nm.GetContainerNodes(cnr.PlacementPolicy(), cid)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
flatCnrNodes := cnrNodes.Flatten() // we need single array to iterate on
|
|
for i := range flatCnrNodes {
|
|
if bytes.Equal(flatCnrNodes[i].PublicKey(), owner) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func ownerFromToken(token *session.SessionToken) (*owner.ID, *ecdsa.PublicKey, error) {
|
|
// 1. First check signature of session token.
|
|
signWrapper := v2signature.StableMarshalerWrapper{SM: token.GetBody()}
|
|
if err := signature.VerifyDataWithSource(signWrapper, func() (key, sig []byte) {
|
|
tokenSignature := token.GetSignature()
|
|
return tokenSignature.GetKey(), tokenSignature.GetSign()
|
|
}); err != nil {
|
|
return nil, nil, errors.Wrap(ErrMalformedRequest, "invalid session token signature")
|
|
}
|
|
|
|
// 2. Then check if session token owner issued the session token
|
|
tokenIssuerKey := crypto.UnmarshalPublicKey(token.GetSignature().GetKey())
|
|
tokenOwner := owner.NewIDFromV2(token.GetBody().GetOwnerID())
|
|
|
|
if !isOwnerFromKey(tokenOwner, tokenIssuerKey) {
|
|
// todo: in this case we can issue all owner keys from neofs.id and check once again
|
|
return nil, nil, errors.Wrap(ErrMalformedRequest, "invalid session token owner")
|
|
}
|
|
|
|
return tokenOwner, tokenIssuerKey, nil
|
|
}
|