frostfs-node/pkg/services/object/acl/classifier.go
Alex Vanin 4a8de3263d [#32] Use less v2 specific structures in basic ACL checker
Signed-off-by: Alex Vanin <alexey@nspcc.ru>
2020-10-02 11:25:36 +03:00

203 lines
4.8 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/v2/session"
crypto "github.com/nspcc-dev/neofs-crypto"
core "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
"github.com/pkg/errors"
)
type (
InnerRingFetcher interface {
InnerRingKeys() ([][]byte, error)
}
RequestV2 interface {
GetMetaHeader() *session.RequestMetaHeader
GetVerificationHeader() *session.RequestVerificationHeader
}
SenderClassifier struct {
innerRing InnerRingFetcher
netmap core.Source
}
)
// fixme: update classifier constructor
func NewSenderClassifier(ir InnerRingFetcher, nm core.Source) SenderClassifier {
return SenderClassifier{
innerRing: ir,
netmap: nm,
}
}
func (c SenderClassifier) Classify(
req RequestV2,
cid *container.ID,
cnr *container.Container) acl.Role {
if cid == nil || req == nil {
// log there
return acl.RoleUnknown
}
ownerID, ownerKey, err := requestOwner(req)
if err != nil || ownerID == nil || ownerKey == nil {
// log there
return acl.RoleUnknown
}
// todo: get owner from neofs.id if present
// if request owner is the same as container owner, return RoleUser
if bytes.Equal(cnr.GetOwnerID().GetValue(), ownerID.ToV2().GetValue()) {
return acl.RoleUser
}
ownerKeyInBytes := crypto.MarshalPublicKey(ownerKey)
isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes)
if err != nil {
// log there
return acl.RoleUnknown
} else if isInnerRingNode {
return acl.RoleSystem
}
isContainerNode, err := c.isContainerKey(ownerKeyInBytes, cid.ToV2().GetValue(), cnr)
if err != nil {
// log there
return acl.RoleUnknown
} else if isContainerNode {
return acl.RoleSystem
}
// if none of above, return RoleOthers
return acl.RoleOthers
}
func requestOwner(req RequestV2) (*owner.ID, *ecdsa.PublicKey, error) {
var (
meta = req.GetMetaHeader()
verify = req.GetVerificationHeader()
)
if meta == nil || verify == nil {
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil at meta or verify header")
}
// if session token is presented, use it as truth source
if token := meta.GetSessionToken(); token != nil {
body := token.GetBody()
if body == nil {
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil at session token body")
}
signature := token.GetSignature()
if signature == nil {
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil at signature")
}
return owner.NewIDFromV2(body.GetOwnerID()), crypto.UnmarshalPublicKey(signature.GetKey()), nil
}
// otherwise get original body signature
bodySignature := originalBodySignature(verify)
if bodySignature == nil {
return nil, nil, errors.Wrap(ErrMalformedRequest, "nil at body signature")
}
key := crypto.UnmarshalPublicKey(bodySignature.GetKey())
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) {
// first check current netmap
nm, err := core.GetLatestNetworkMap(c.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.GetPlacementPolicy(), 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].InfoV2.GetPublicKey(), owner) {
return true, nil
}
}
return false, nil
}