* Move some helpers from `acl/v2` package to `ape`. Also move errors; * Introduce `Metadata`, `RequestInfo` types; * Introduce `RequestInfoExtractor` interface and its implementation. The extractor's purpose is to extract request info based on request metadata. It also validates session token; * Refactor ape service - each handler forms request info and pass necessary fields to checker. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
172 lines
4.8 KiB
Go
172 lines
4.8 KiB
Go
package ape
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
|
"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/util/logger"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
|
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
)
|
|
|
|
type Metadata struct {
|
|
Container cid.ID
|
|
Object *oid.ID
|
|
MetaHeader *session.RequestMetaHeader
|
|
VerificationHeader *session.RequestVerificationHeader
|
|
SessionToken *sessionSDK.Object
|
|
BearerToken *bearer.Token
|
|
}
|
|
|
|
func (m Metadata) RequestOwner() (*user.ID, *keys.PublicKey, error) {
|
|
if m.VerificationHeader == nil {
|
|
return nil, nil, errEmptyVerificationHeader
|
|
}
|
|
|
|
if m.BearerToken != nil && m.BearerToken.Impersonate() {
|
|
return unmarshalPublicKeyWithOwner(m.BearerToken.SigningKeyBytes())
|
|
}
|
|
|
|
// if session token is presented, use it as truth source
|
|
if m.SessionToken != nil {
|
|
// verify signature of session token
|
|
return ownerFromToken(m.SessionToken)
|
|
}
|
|
|
|
// otherwise get original body signature
|
|
bodySignature := originalBodySignature(m.VerificationHeader)
|
|
if bodySignature == nil {
|
|
return nil, nil, errEmptyBodySig
|
|
}
|
|
|
|
return unmarshalPublicKeyWithOwner(bodySignature.GetKey())
|
|
}
|
|
|
|
// RequestInfo contains request information extracted by request metadata.
|
|
type RequestInfo struct {
|
|
// Role defines under which role this request is executed.
|
|
// It must be represented only as a constant represented in native schema.
|
|
Role string
|
|
|
|
ContainerOwner user.ID
|
|
|
|
// Namespace defines to which namespace a container is belonged.
|
|
Namespace string
|
|
|
|
// HEX-encoded sender key.
|
|
SenderKey string
|
|
}
|
|
|
|
type RequestInfoExtractor interface {
|
|
GetRequestInfo(context.Context, Metadata, string) (RequestInfo, error)
|
|
}
|
|
|
|
type extractor struct {
|
|
containers container.Source
|
|
|
|
nm netmap.Source
|
|
|
|
classifier objectCore.SenderClassifier
|
|
}
|
|
|
|
func NewRequestInfoExtractor(log *logger.Logger, containers container.Source, irFetcher InnerRingFetcher, nm netmap.Source) RequestInfoExtractor {
|
|
return &extractor{
|
|
containers: containers,
|
|
nm: nm,
|
|
classifier: objectCore.NewSenderClassifier(irFetcher, nm, log),
|
|
}
|
|
}
|
|
|
|
func (e *extractor) verifySessionToken(ctx context.Context, sessionToken *sessionSDK.Object, method string) error {
|
|
currentEpoch, err := e.nm.Epoch(ctx)
|
|
if err != nil {
|
|
return errors.New("can't fetch current epoch")
|
|
}
|
|
if sessionToken.ExpiredAt(currentEpoch) {
|
|
return new(apistatus.SessionTokenExpired)
|
|
}
|
|
if sessionToken.InvalidAt(currentEpoch) {
|
|
return fmt.Errorf("malformed request: token is invalid at %d epoch)", currentEpoch)
|
|
}
|
|
if !assertVerb(*sessionToken, method) {
|
|
return errInvalidVerb
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *extractor) GetRequestInfo(ctx context.Context, m Metadata, method string) (ri RequestInfo, err error) {
|
|
cnr, err := e.containers.Get(ctx, m.Container)
|
|
if err != nil {
|
|
return ri, err
|
|
}
|
|
|
|
if m.SessionToken != nil {
|
|
if err = e.verifySessionToken(ctx, m.SessionToken, method); err != nil {
|
|
return ri, err
|
|
}
|
|
}
|
|
|
|
ownerID, ownerKey, err := m.RequestOwner()
|
|
if err != nil {
|
|
return ri, err
|
|
}
|
|
res, err := e.classifier.Classify(ctx, ownerID, ownerKey, m.Container, cnr.Value)
|
|
if err != nil {
|
|
return ri, err
|
|
}
|
|
|
|
ri.Role = nativeSchemaRole(res.Role)
|
|
ri.ContainerOwner = cnr.Value.Owner()
|
|
|
|
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
|
if hasNamespace {
|
|
ri.Namespace = cnrNamespace
|
|
}
|
|
|
|
// it is assumed that at the moment the key will be valid,
|
|
// otherwise the request would not pass validation
|
|
ri.SenderKey = hex.EncodeToString(res.Key)
|
|
|
|
return ri, nil
|
|
}
|
|
|
|
func readSessionToken(cnr cid.ID, obj *oid.ID, tokV2 *session.Token) (*sessionSDK.Object, error) {
|
|
var sTok *sessionSDK.Object
|
|
|
|
if tokV2 != nil {
|
|
sTok = new(sessionSDK.Object)
|
|
|
|
err := sTok.ReadFromV2(*tokV2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid session token: %w", err)
|
|
}
|
|
|
|
if sTok.AssertVerb(sessionSDK.VerbObjectDelete) {
|
|
// if session relates to object's removal, we don't check
|
|
// relation of the tombstone to the session here since user
|
|
// can't predict tomb's ID.
|
|
err = assertSessionRelation(*sTok, cnr, nil)
|
|
} else {
|
|
err = assertSessionRelation(*sTok, cnr, obj)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return sTok, nil
|
|
}
|