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 }