[#1052] object: Make ape
middleware form request info
* 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>
This commit is contained in:
parent
eed0824590
commit
73e35bc885
8 changed files with 759 additions and 193 deletions
|
@ -172,7 +172,7 @@ func initObjectService(c *cfg) {
|
|||
|
||||
splitSvc := createSplitService(c, sPutV2, sGetV2, sSearchV2, sDeleteV2, sPatch)
|
||||
|
||||
apeSvc := createAPEService(c, splitSvc)
|
||||
apeSvc := createAPEService(c, &irFetcher, splitSvc)
|
||||
|
||||
aclSvc := createACLServiceV2(c, apeSvc, &irFetcher)
|
||||
|
||||
|
@ -439,7 +439,7 @@ func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFe
|
|||
)
|
||||
}
|
||||
|
||||
func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *objectAPE.Service {
|
||||
func createAPEService(c *cfg, irFetcher *cachedIRFetcher, splitSvc *objectService.TransportSplitter) *objectAPE.Service {
|
||||
return objectAPE.NewService(
|
||||
objectAPE.NewChecker(
|
||||
c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.LocalStorage(),
|
||||
|
@ -451,6 +451,7 @@ func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *object
|
|||
c.cfgObject.cnrSource,
|
||||
c.binPublicKey,
|
||||
),
|
||||
objectAPE.NewRequestInfoExtractor(c.log, c.cfgObject.cnrSource, irFetcher, c.netMapSource),
|
||||
splitSvc,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,21 @@ import (
|
|||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingContainerID = malformedRequestError("missing container ID")
|
||||
errEmptyVerificationHeader = malformedRequestError("empty verification header")
|
||||
errEmptyBodySig = malformedRequestError("empty at body signature")
|
||||
errInvalidSessionSig = malformedRequestError("invalid session token signature")
|
||||
errInvalidSessionOwner = malformedRequestError("invalid session token owner")
|
||||
errInvalidVerb = malformedRequestError("session token verb is invalid")
|
||||
)
|
||||
|
||||
func malformedRequestError(reason string) error {
|
||||
invalidArgErr := &apistatus.InvalidArgument{}
|
||||
invalidArgErr.SetMessage(reason)
|
||||
return invalidArgErr
|
||||
}
|
||||
|
||||
func toStatusErr(err error) error {
|
||||
var chRouterErr *checkercore.ChainRouterError
|
||||
if !errors.As(err, &chRouterErr) {
|
||||
|
|
172
pkg/services/object/ape/metadata.go
Normal file
172
pkg/services/object/ape/metadata.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
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
|
||||
}
|
164
pkg/services/object/ape/metadata_test.go
Normal file
164
pkg/services/object/ape/metadata_test.go
Normal file
|
@ -0,0 +1,164 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
sigutilV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/util/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRequestOwner(t *testing.T) {
|
||||
containerOwner, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
userPk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
var userID user.ID
|
||||
user.IDFromKey(&userID, userPk.PrivateKey.PublicKey)
|
||||
|
||||
var userSignature refs.Signature
|
||||
userSignature.SetKey(userPk.PublicKey().Bytes())
|
||||
|
||||
vh := new(sessionV2.RequestVerificationHeader)
|
||||
vh.SetBodySignature(&userSignature)
|
||||
|
||||
t.Run("empty verification header", func(t *testing.T) {
|
||||
req := Metadata{}
|
||||
checkOwner(t, req, nil, errEmptyVerificationHeader)
|
||||
})
|
||||
t.Run("empty verification header signature", func(t *testing.T) {
|
||||
req := Metadata{
|
||||
VerificationHeader: new(sessionV2.RequestVerificationHeader),
|
||||
}
|
||||
checkOwner(t, req, nil, errEmptyBodySig)
|
||||
})
|
||||
t.Run("no tokens", func(t *testing.T) {
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
}
|
||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
||||
})
|
||||
|
||||
t.Run("bearer without impersonate, no session", func(t *testing.T) {
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
BearerToken: newBearer(t, containerOwner, userID, false),
|
||||
}
|
||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
||||
})
|
||||
t.Run("bearer with impersonate, no session", func(t *testing.T) {
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
BearerToken: newBearer(t, containerOwner, userID, true),
|
||||
}
|
||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
||||
})
|
||||
t.Run("bearer with impersonate, with session", func(t *testing.T) {
|
||||
// To check that bearer token takes priority, use different key to sign session token.
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
BearerToken: newBearer(t, containerOwner, userID, true),
|
||||
SessionToken: newSession(t, pk),
|
||||
}
|
||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
||||
})
|
||||
t.Run("with session", func(t *testing.T) {
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
SessionToken: newSession(t, containerOwner),
|
||||
}
|
||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
||||
})
|
||||
t.Run("malformed session token", func(t *testing.T) {
|
||||
// This test is tricky: session token has issuer field and signature, which must correspond to each other.
|
||||
// SDK prevents constructing such token in the first place, but it is still possible via API.
|
||||
// Thus, construct v2 token, convert it to SDK one and pass to our function.
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
var user1 user.ID
|
||||
user.IDFromKey(&user1, pk.PrivateKey.PublicKey)
|
||||
|
||||
var id refs.OwnerID
|
||||
id.SetValue(user1.WalletBytes())
|
||||
|
||||
raw, err := uuid.New().MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
|
||||
var cidV2 refs.ContainerID
|
||||
cidtest.ID().WriteToV2(&cidV2)
|
||||
|
||||
sessionCtx := new(sessionV2.ObjectSessionContext)
|
||||
sessionCtx.SetTarget(&cidV2)
|
||||
|
||||
var body sessionV2.TokenBody
|
||||
body.SetOwnerID(&id)
|
||||
body.SetID(raw)
|
||||
body.SetLifetime(new(sessionV2.TokenLifetime))
|
||||
body.SetSessionKey(pk.PublicKey().Bytes())
|
||||
body.SetContext(sessionCtx)
|
||||
|
||||
var tokV2 sessionV2.Token
|
||||
tokV2.SetBody(&body)
|
||||
require.NoError(t, sigutilV2.SignData(&containerOwner.PrivateKey, smWrapper{Token: &tokV2}))
|
||||
require.NoError(t, sigutilV2.VerifyData(smWrapper{Token: &tokV2}))
|
||||
|
||||
var tok sessionSDK.Object
|
||||
require.NoError(t, tok.ReadFromV2(tokV2))
|
||||
|
||||
req := Metadata{
|
||||
VerificationHeader: vh,
|
||||
SessionToken: &tok,
|
||||
}
|
||||
checkOwner(t, req, nil, errInvalidSessionOwner)
|
||||
})
|
||||
}
|
||||
|
||||
type smWrapper struct {
|
||||
*sessionV2.Token
|
||||
}
|
||||
|
||||
func (s smWrapper) ReadSignedData(data []byte) ([]byte, error) {
|
||||
return s.Token.GetBody().StableMarshal(data), nil
|
||||
}
|
||||
|
||||
func (s smWrapper) SignedDataSize() int {
|
||||
return s.Token.GetBody().StableSize()
|
||||
}
|
||||
|
||||
func newSession(t *testing.T, pk *keys.PrivateKey) *sessionSDK.Object {
|
||||
var tok sessionSDK.Object
|
||||
require.NoError(t, tok.Sign(pk.PrivateKey))
|
||||
return &tok
|
||||
}
|
||||
|
||||
func newBearer(t *testing.T, pk *keys.PrivateKey, user user.ID, impersonate bool) *bearer.Token {
|
||||
var tok bearer.Token
|
||||
tok.SetImpersonate(impersonate)
|
||||
tok.ForUser(user)
|
||||
require.NoError(t, tok.Sign(pk.PrivateKey))
|
||||
return &tok
|
||||
}
|
||||
|
||||
func checkOwner(t *testing.T, req Metadata, expected *keys.PublicKey, expectedErr error) {
|
||||
_, actual, err := req.RequestOwner()
|
||||
if expectedErr != nil {
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
|
@ -2,9 +2,6 @@ package ape
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||
objectSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
||||
|
@ -12,19 +9,18 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/util"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
)
|
||||
|
||||
var errFailedToCastToRequestContext = errors.New("failed cast to RequestContext")
|
||||
|
||||
type Service struct {
|
||||
apeChecker Checker
|
||||
|
||||
extractor RequestInfoExtractor
|
||||
|
||||
next objectSvc.ServiceServer
|
||||
}
|
||||
|
||||
|
@ -64,9 +60,10 @@ func NewStorageEngineHeaderProvider(e *engine.StorageEngine, s *getsvc.Service)
|
|||
}
|
||||
}
|
||||
|
||||
func NewService(apeChecker Checker, next objectSvc.ServiceServer) *Service {
|
||||
func NewService(apeChecker Checker, extractor RequestInfoExtractor, next objectSvc.ServiceServer) *Service {
|
||||
return &Service{
|
||||
apeChecker: apeChecker,
|
||||
extractor: extractor,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
@ -76,15 +73,9 @@ type getStreamBasicChecker struct {
|
|||
|
||||
apeChecker Checker
|
||||
|
||||
namespace string
|
||||
metadata Metadata
|
||||
|
||||
senderKey []byte
|
||||
|
||||
containerOwner user.ID
|
||||
|
||||
role string
|
||||
|
||||
bearerToken *bearer.Token
|
||||
reqInfo RequestInfo
|
||||
}
|
||||
|
||||
func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
|
||||
|
@ -95,15 +86,15 @@ func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
|
|||
}
|
||||
|
||||
prm := Prm{
|
||||
Namespace: g.namespace,
|
||||
Namespace: g.reqInfo.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Header: partInit.GetHeader(),
|
||||
Method: nativeschema.MethodGetObject,
|
||||
SenderKey: hex.EncodeToString(g.senderKey),
|
||||
ContainerOwner: g.containerOwner,
|
||||
Role: g.role,
|
||||
BearerToken: g.bearerToken,
|
||||
SenderKey: g.reqInfo.SenderKey,
|
||||
ContainerOwner: g.reqInfo.ContainerOwner,
|
||||
Role: g.reqInfo.Role,
|
||||
BearerToken: g.metadata.BearerToken,
|
||||
XHeaders: resp.GetMetaHeader().GetXHeaders(),
|
||||
}
|
||||
|
||||
|
@ -114,69 +105,53 @@ func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
|
|||
return g.GetObjectStream.Send(resp)
|
||||
}
|
||||
|
||||
func requestContext(ctx context.Context) (*objectSvc.RequestContext, error) {
|
||||
untyped := ctx.Value(objectSvc.RequestContextKey)
|
||||
if untyped == nil {
|
||||
return nil, fmt.Errorf("no key %s in context", objectSvc.RequestContextKey)
|
||||
}
|
||||
rc, ok := untyped.(*objectSvc.RequestContext)
|
||||
if !ok {
|
||||
return nil, errFailedToCastToRequestContext
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (c *Service) Get(request *objectV2.GetRequest, stream objectSvc.GetObjectStream) error {
|
||||
reqCtx, err := requestContext(stream.Context())
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
reqInfo, err := c.extractor.GetRequestInfo(stream.Context(), md, nativeschema.MethodGetObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.next.Get(request, &getStreamBasicChecker{
|
||||
GetObjectStream: stream,
|
||||
apeChecker: c.apeChecker,
|
||||
namespace: reqCtx.Namespace,
|
||||
senderKey: reqCtx.SenderKey,
|
||||
containerOwner: reqCtx.ContainerOwner,
|
||||
role: nativeSchemaRole(reqCtx.Role),
|
||||
bearerToken: reqCtx.BearerToken,
|
||||
metadata: md,
|
||||
reqInfo: reqInfo,
|
||||
})
|
||||
}
|
||||
|
||||
type putStreamBasicChecker struct {
|
||||
apeChecker Checker
|
||||
|
||||
extractor RequestInfoExtractor
|
||||
|
||||
next objectSvc.PutObjectStream
|
||||
}
|
||||
|
||||
func (p *putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
if partInit, ok := request.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
||||
reqCtx, err := requestContext(ctx)
|
||||
md, err := newMetadata(request, partInit.GetHeader().GetContainerID(), partInit.GetObjectID())
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(partInit.GetHeader().GetContainerID(), partInit.GetObjectID())
|
||||
reqInfo, err := p.extractor.GetRequestInfo(ctx, md, nativeschema.MethodPutObject)
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
prm := Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Header: partInit.GetHeader(),
|
||||
Method: nativeschema.MethodPutObject,
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
Role: reqInfo.Role,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
}
|
||||
|
||||
if err := p.apeChecker.CheckAPE(ctx, prm); err != nil {
|
||||
|
@ -196,6 +171,7 @@ func (c *Service) Put(ctx context.Context) (objectSvc.PutObjectStream, error) {
|
|||
|
||||
return &putStreamBasicChecker{
|
||||
apeChecker: c.apeChecker,
|
||||
extractor: c.extractor,
|
||||
next: streamer,
|
||||
}, err
|
||||
}
|
||||
|
@ -203,40 +179,36 @@ func (c *Service) Put(ctx context.Context) (objectSvc.PutObjectStream, error) {
|
|||
type patchStreamBasicChecker struct {
|
||||
apeChecker Checker
|
||||
|
||||
extractor RequestInfoExtractor
|
||||
|
||||
next objectSvc.PatchObjectStream
|
||||
|
||||
nonFirstSend bool
|
||||
}
|
||||
|
||||
func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.PatchRequest) error {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
if !p.nonFirstSend {
|
||||
p.nonFirstSend = true
|
||||
|
||||
reqCtx, err := requestContext(ctx)
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
reqInfo, err := p.extractor.GetRequestInfo(ctx, md, nativeschema.MethodPatchObject)
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
prm := Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Method: nativeschema.MethodPatchObject,
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
Role: reqInfo.Role,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
}
|
||||
|
||||
if err := p.apeChecker.CheckAPE(ctx, prm); err != nil {
|
||||
|
@ -256,22 +228,17 @@ func (c *Service) Patch(ctx context.Context) (objectSvc.PatchObjectStream, error
|
|||
|
||||
return &patchStreamBasicChecker{
|
||||
apeChecker: c.apeChecker,
|
||||
extractor: c.extractor,
|
||||
next: streamer,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*objectV2.HeadResponse, error) {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(ctx)
|
||||
reqInfo, err := c.extractor.GetRequestInfo(ctx, md, nativeschema.MethodHeadObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -285,7 +252,7 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
|
|||
switch headerPart := resp.GetBody().GetHeaderPart().(type) {
|
||||
case *objectV2.ShortHeader:
|
||||
cidV2 := new(refs.ContainerID)
|
||||
cnrID.WriteToV2(cidV2)
|
||||
md.Container.WriteToV2(cidV2)
|
||||
header.SetContainerID(cidV2)
|
||||
header.SetVersion(headerPart.GetVersion())
|
||||
header.SetCreationEpoch(headerPart.GetCreationEpoch())
|
||||
|
@ -301,16 +268,16 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
|
|||
}
|
||||
|
||||
err = c.apeChecker.CheckAPE(ctx, Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Header: header,
|
||||
Method: nativeschema.MethodHeadObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, toStatusErr(err)
|
||||
|
@ -319,32 +286,24 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
|
|||
}
|
||||
|
||||
func (c *Service) Search(request *objectV2.SearchRequest, stream objectSvc.SearchStream) error {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
var cnrID cid.ID
|
||||
if cnrV2 := request.GetBody().GetContainerID(); cnrV2 != nil {
|
||||
if err := cnrID.ReadFromV2(*cnrV2); err != nil {
|
||||
return toStatusErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(stream.Context())
|
||||
md, err := newMetadata(request, request.GetBody().GetContainerID(), nil)
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
reqInfo, err := c.extractor.GetRequestInfo(stream.Context(), md, nativeschema.MethodSearchObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.apeChecker.CheckAPE(stream.Context(), Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Method: nativeschema.MethodSearchObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
})
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
|
@ -354,31 +313,25 @@ func (c *Service) Search(request *objectV2.SearchRequest, stream objectSvc.Searc
|
|||
}
|
||||
|
||||
func (c *Service) Delete(ctx context.Context, request *objectV2.DeleteRequest) (*objectV2.DeleteResponse, error) {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(ctx)
|
||||
reqInfo, err := c.extractor.GetRequestInfo(ctx, md, nativeschema.MethodDeleteObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.apeChecker.CheckAPE(ctx, Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Method: nativeschema.MethodDeleteObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, toStatusErr(err)
|
||||
|
@ -393,31 +346,25 @@ func (c *Service) Delete(ctx context.Context, request *objectV2.DeleteRequest) (
|
|||
}
|
||||
|
||||
func (c *Service) GetRange(request *objectV2.GetRangeRequest, stream objectSvc.GetObjectRangeStream) error {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(stream.Context())
|
||||
reqInfo, err := c.extractor.GetRequestInfo(stream.Context(), md, nativeschema.MethodRangeObject)
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.apeChecker.CheckAPE(stream.Context(), Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Method: nativeschema.MethodRangeObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
})
|
||||
if err != nil {
|
||||
return toStatusErr(err)
|
||||
|
@ -427,31 +374,25 @@ func (c *Service) GetRange(request *objectV2.GetRangeRequest, stream objectSvc.G
|
|||
}
|
||||
|
||||
func (c *Service) GetRangeHash(ctx context.Context, request *objectV2.GetRangeHashRequest) (*objectV2.GetRangeHashResponse, error) {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
md, err := newMetadata(request, request.GetBody().GetAddress().GetContainerID(), request.GetBody().GetAddress().GetObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(ctx)
|
||||
reqInfo, err := c.extractor.GetRequestInfo(ctx, md, nativeschema.MethodHashObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prm := Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Method: nativeschema.MethodHashObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
}
|
||||
|
||||
resp, err := c.next.GetRangeHash(ctx, request)
|
||||
|
@ -466,32 +407,26 @@ func (c *Service) GetRangeHash(ctx context.Context, request *objectV2.GetRangeHa
|
|||
}
|
||||
|
||||
func (c *Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequest) (*objectV2.PutSingleResponse, error) {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
cnrID, objID, err := getAddressParamsSDK(request.GetBody().GetObject().GetHeader().GetContainerID(), request.GetBody().GetObject().GetObjectID())
|
||||
md, err := newMetadata(request, request.GetBody().GetObject().GetHeader().GetContainerID(), request.GetBody().GetObject().GetObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqCtx, err := requestContext(ctx)
|
||||
reqInfo, err := c.extractor.GetRequestInfo(ctx, md, nativeschema.MethodPutObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prm := Prm{
|
||||
Namespace: reqCtx.Namespace,
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
Namespace: reqInfo.Namespace,
|
||||
Container: md.Container,
|
||||
Object: md.Object,
|
||||
Header: request.GetBody().GetObject().GetHeader(),
|
||||
Method: nativeschema.MethodPutObject,
|
||||
Role: nativeSchemaRole(reqCtx.Role),
|
||||
SenderKey: hex.EncodeToString(reqCtx.SenderKey),
|
||||
ContainerOwner: reqCtx.ContainerOwner,
|
||||
BearerToken: reqCtx.BearerToken,
|
||||
XHeaders: meta.GetXHeaders(),
|
||||
Role: reqInfo.Role,
|
||||
SenderKey: reqInfo.SenderKey,
|
||||
ContainerOwner: reqInfo.ContainerOwner,
|
||||
BearerToken: md.BearerToken,
|
||||
XHeaders: md.MetaHeader.GetXHeaders(),
|
||||
}
|
||||
|
||||
if err = c.apeChecker.CheckAPE(ctx, prm); err != nil {
|
||||
|
@ -501,18 +436,36 @@ func (c *Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequ
|
|||
return c.next.PutSingle(ctx, request)
|
||||
}
|
||||
|
||||
func getAddressParamsSDK(cidV2 *refs.ContainerID, objV2 *refs.ObjectID) (cnrID cid.ID, objID *oid.ID, err error) {
|
||||
if cidV2 != nil {
|
||||
if err = cnrID.ReadFromV2(*cidV2); err != nil {
|
||||
return
|
||||
}
|
||||
type request interface {
|
||||
GetMetaHeader() *session.RequestMetaHeader
|
||||
GetVerificationHeader() *session.RequestVerificationHeader
|
||||
}
|
||||
|
||||
func newMetadata(request request, cnrV2 *refs.ContainerID, objV2 *refs.ObjectID) (md Metadata, err error) {
|
||||
meta := request.GetMetaHeader()
|
||||
for origin := meta.GetOrigin(); origin != nil; origin = meta.GetOrigin() {
|
||||
meta = origin
|
||||
}
|
||||
|
||||
if objV2 != nil {
|
||||
objID = new(oid.ID)
|
||||
if err = objID.ReadFromV2(*objV2); err != nil {
|
||||
return
|
||||
}
|
||||
cnrID, objID, err := getAddressParamsSDK(cnrV2, objV2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session, err := readSessionToken(cnrID, objID, meta.GetSessionToken())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bearer, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
md = Metadata{
|
||||
Container: cnrID,
|
||||
Object: objID,
|
||||
VerificationHeader: request.GetVerificationHeader(),
|
||||
SessionToken: session,
|
||||
BearerToken: bearer,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,3 +7,11 @@ import "context"
|
|||
type Checker interface {
|
||||
CheckAPE(context.Context, Prm) error
|
||||
}
|
||||
|
||||
// InnerRingFetcher is an interface that must provide
|
||||
// Inner Ring information.
|
||||
type InnerRingFetcher interface {
|
||||
// InnerRingKeys must return list of public keys of
|
||||
// the actual inner ring.
|
||||
InnerRingKeys(ctx context.Context) ([][]byte, error)
|
||||
}
|
||||
|
|
169
pkg/services/object/ape/util.go
Normal file
169
pkg/services/object/ape/util.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
refsV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
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"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
func getAddressParamsSDK(cidV2 *refsV2.ContainerID, objV2 *refsV2.ObjectID) (cnrID cid.ID, objID *oid.ID, err error) {
|
||||
if cidV2 != nil {
|
||||
if err = cnrID.ReadFromV2(*cidV2); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = errMissingContainerID
|
||||
return
|
||||
}
|
||||
|
||||
if objV2 != nil {
|
||||
objID = new(oid.ID)
|
||||
if err = objID.ReadFromV2(*objV2); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// originalBearerToken goes down to original request meta header and fetches
|
||||
// bearer token from there.
|
||||
func originalBearerToken(header *sessionV2.RequestMetaHeader) (*bearer.Token, error) {
|
||||
for header.GetOrigin() != nil {
|
||||
header = header.GetOrigin()
|
||||
}
|
||||
|
||||
tokV2 := header.GetBearerToken()
|
||||
if tokV2 == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var tok bearer.Token
|
||||
return &tok, tok.ReadFromV2(*tokV2)
|
||||
}
|
||||
|
||||
func ownerFromToken(token *sessionSDK.Object) (*user.ID, *keys.PublicKey, error) {
|
||||
// 1. First check signature of session token.
|
||||
if !token.VerifySignature() {
|
||||
return nil, nil, errInvalidSessionSig
|
||||
}
|
||||
|
||||
// 2. Then check if session token owner issued the session token
|
||||
// TODO(@cthulhu-rider): #468 implement and use another approach to avoid conversion
|
||||
var tokV2 sessionV2.Token
|
||||
token.WriteToV2(&tokV2)
|
||||
|
||||
tokenIssuerKey, err := unmarshalPublicKey(tokV2.GetSignature().GetKey())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid key in session token signature: %w", err)
|
||||
}
|
||||
|
||||
tokenIssuer := token.Issuer()
|
||||
|
||||
if !isOwnerFromKey(tokenIssuer, tokenIssuerKey) {
|
||||
// TODO: #767 in this case we can issue all owner keys from frostfs.id and check once again
|
||||
return nil, nil, errInvalidSessionOwner
|
||||
}
|
||||
|
||||
return &tokenIssuer, tokenIssuerKey, nil
|
||||
}
|
||||
|
||||
func originalBodySignature(v *sessionV2.RequestVerificationHeader) *refsV2.Signature {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for v.GetOrigin() != nil {
|
||||
v = v.GetOrigin()
|
||||
}
|
||||
|
||||
return v.GetBodySignature()
|
||||
}
|
||||
|
||||
func unmarshalPublicKey(bs []byte) (*keys.PublicKey, error) {
|
||||
return keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||
}
|
||||
|
||||
func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
|
||||
if key == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var id2 user.ID
|
||||
user.IDFromKey(&id2, (ecdsa.PublicKey)(*key))
|
||||
|
||||
return id2.Equals(id)
|
||||
}
|
||||
|
||||
// assertVerb checks that token verb corresponds to the method.
|
||||
func assertVerb(tok sessionSDK.Object, method string) bool {
|
||||
switch method {
|
||||
case nativeschema.MethodPutObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectPatch)
|
||||
case nativeschema.MethodDeleteObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectDelete)
|
||||
case nativeschema.MethodGetObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectGet)
|
||||
case nativeschema.MethodHeadObject:
|
||||
return tok.AssertVerb(
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectPatch,
|
||||
)
|
||||
case nativeschema.MethodSearchObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete)
|
||||
case nativeschema.MethodRangeObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectPatch)
|
||||
case nativeschema.MethodHashObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRangeHash)
|
||||
case nativeschema.MethodPatchObject:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectPatch)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// assertSessionRelation checks if given token describing the FrostFS session
|
||||
// relates to the given container and optional object. Missing object
|
||||
// means that the context isn't bound to any FrostFS object in the container.
|
||||
// Returns no error iff relation is correct. Criteria:
|
||||
//
|
||||
// session is bound to the given container
|
||||
// object is not specified or session is bound to this object
|
||||
//
|
||||
// Session MUST be bound to the particular container, otherwise behavior is undefined.
|
||||
func assertSessionRelation(tok sessionSDK.Object, cnr cid.ID, obj *oid.ID) error {
|
||||
if !tok.AssertContainer(cnr) {
|
||||
return errors.New("requested container is not related to the session")
|
||||
}
|
||||
|
||||
if obj != nil && !tok.AssertObject(*obj) {
|
||||
return errors.New("requested object is not related to the session")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalPublicKeyWithOwner(rawKey []byte) (*user.ID, *keys.PublicKey, error) {
|
||||
key, err := unmarshalPublicKey(rawKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid signature key: %w", err)
|
||||
}
|
||||
|
||||
var idSender user.ID
|
||||
user.IDFromKey(&idSender, (ecdsa.PublicKey)(*key))
|
||||
|
||||
return &idSender, key, nil
|
||||
}
|
84
pkg/services/object/ape/util_test.go
Normal file
84
pkg/services/object/ape/util_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsVerbCompatible(t *testing.T) {
|
||||
table := map[string][]sessionSDK.ObjectVerb{
|
||||
nativeschema.MethodPutObject: {sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectPatch},
|
||||
nativeschema.MethodDeleteObject: {sessionSDK.VerbObjectDelete},
|
||||
nativeschema.MethodGetObject: {sessionSDK.VerbObjectGet},
|
||||
nativeschema.MethodHeadObject: {
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectPatch,
|
||||
},
|
||||
nativeschema.MethodRangeObject: {sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectPatch},
|
||||
nativeschema.MethodHashObject: {sessionSDK.VerbObjectRangeHash},
|
||||
nativeschema.MethodSearchObject: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete},
|
||||
nativeschema.MethodPatchObject: {sessionSDK.VerbObjectPatch},
|
||||
}
|
||||
|
||||
verbs := []sessionSDK.ObjectVerb{
|
||||
sessionSDK.VerbObjectPut,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectSearch,
|
||||
sessionSDK.VerbObjectPatch,
|
||||
}
|
||||
|
||||
var tok sessionSDK.Object
|
||||
|
||||
for op, list := range table {
|
||||
for _, verb := range verbs {
|
||||
contains := slices.Contains(list, verb)
|
||||
|
||||
tok.ForVerb(verb)
|
||||
|
||||
require.Equal(t, contains, assertVerb(tok, op),
|
||||
"%v in token, %s executing", verb, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssertSessionRelation(t *testing.T) {
|
||||
var tok sessionSDK.Object
|
||||
cnr := cidtest.ID()
|
||||
cnrOther := cidtest.ID()
|
||||
obj := oidtest.ID()
|
||||
objOther := oidtest.ID()
|
||||
|
||||
// make sure ids differ, otherwise test won't work correctly
|
||||
require.False(t, cnrOther.Equals(cnr))
|
||||
require.False(t, objOther.Equals(obj))
|
||||
|
||||
// bind session to the container (required)
|
||||
tok.BindContainer(cnr)
|
||||
|
||||
// test container-global session
|
||||
require.NoError(t, assertSessionRelation(tok, cnr, nil))
|
||||
require.NoError(t, assertSessionRelation(tok, cnr, &obj))
|
||||
require.Error(t, assertSessionRelation(tok, cnrOther, nil))
|
||||
require.Error(t, assertSessionRelation(tok, cnrOther, &obj))
|
||||
|
||||
// limit the session to the particular object
|
||||
tok.LimitByObjects(obj)
|
||||
|
||||
// test fixed object session (here obj arg must be non-nil everywhere)
|
||||
require.NoError(t, assertSessionRelation(tok, cnr, &obj))
|
||||
require.Error(t, assertSessionRelation(tok, cnr, &objOther))
|
||||
}
|
Loading…
Add table
Reference in a new issue