2022-02-11 12:25:05 +00:00
|
|
|
package v2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
|
|
|
refsV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
|
|
|
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
|
|
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"
|
2022-02-11 12:25:05 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
|
|
)
|
|
|
|
|
2022-05-12 16:37:46 +00:00
|
|
|
var errMissingContainerID = errors.New("missing container ID")
|
|
|
|
|
2023-02-21 11:42:45 +00:00
|
|
|
func getContainerIDFromRequest(req any) (cid.ID, error) {
|
2022-05-12 16:37:46 +00:00
|
|
|
var idV2 *refsV2.ContainerID
|
2022-05-31 17:00:41 +00:00
|
|
|
var id cid.ID
|
2022-05-12 16:37:46 +00:00
|
|
|
|
2022-02-11 12:25:05 +00:00
|
|
|
switch v := req.(type) {
|
|
|
|
case *objectV2.GetRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.PutRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
part, ok := v.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit)
|
|
|
|
if !ok {
|
2022-05-31 17:00:41 +00:00
|
|
|
return cid.ID{}, errors.New("can't get container ID in chunk")
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = part.GetHeader().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.HeadRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.SearchRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.DeleteRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.GetRangeRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
case *objectV2.GetRangeHashRequest:
|
2022-05-12 16:37:46 +00:00
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2023-07-03 08:36:20 +00:00
|
|
|
case *objectV2.PutSingleRequest:
|
|
|
|
idV2 = v.GetBody().GetObject().GetHeader().GetContainerID()
|
2024-08-14 11:38:01 +00:00
|
|
|
case *objectV2.PatchRequest:
|
|
|
|
idV2 = v.GetBody().GetAddress().GetContainerID()
|
2022-02-11 12:25:05 +00:00
|
|
|
default:
|
2022-05-31 17:00:41 +00:00
|
|
|
return cid.ID{}, errors.New("unknown request type")
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
2022-05-12 16:37:46 +00:00
|
|
|
|
|
|
|
if idV2 == nil {
|
2022-05-31 17:00:41 +00:00
|
|
|
return cid.ID{}, errMissingContainerID
|
2022-05-12 16:37:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return id, id.ReadFromV2(*idV2)
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// originalBearerToken goes down to original request meta header and fetches
|
|
|
|
// bearer token from there.
|
2022-06-08 08:53:15 +00:00
|
|
|
func originalBearerToken(header *sessionV2.RequestMetaHeader) (*bearer.Token, error) {
|
2022-02-11 12:25:05 +00:00
|
|
|
for header.GetOrigin() != nil {
|
|
|
|
header = header.GetOrigin()
|
|
|
|
}
|
|
|
|
|
2022-05-12 07:22:02 +00:00
|
|
|
tokV2 := header.GetBearerToken()
|
|
|
|
if tokV2 == nil {
|
2022-06-08 08:53:15 +00:00
|
|
|
return nil, nil
|
2022-05-12 07:22:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var tok bearer.Token
|
2022-06-08 08:53:15 +00:00
|
|
|
return &tok, tok.ReadFromV2(*tokV2)
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// originalSessionToken goes down to original request meta header and fetches
|
|
|
|
// session token from there.
|
2022-05-18 15:20:08 +00:00
|
|
|
func originalSessionToken(header *sessionV2.RequestMetaHeader) (*sessionSDK.Object, error) {
|
2022-02-11 12:25:05 +00:00
|
|
|
for header.GetOrigin() != nil {
|
|
|
|
header = header.GetOrigin()
|
|
|
|
}
|
|
|
|
|
2022-05-18 15:20:08 +00:00
|
|
|
tokV2 := header.GetSessionToken()
|
|
|
|
if tokV2 == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var tok sessionSDK.Object
|
|
|
|
|
|
|
|
err := tok.ReadFromV2(*tokV2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid session token: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &tok, nil
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-09-16 10:22:09 +00:00
|
|
|
// getObjectIDFromRequestBody decodes oid.ID from the common interface of the
|
|
|
|
// object reference's holders. Returns an error if object ID is missing in the request.
|
|
|
|
func getObjectIDFromRequestBody(body interface{ GetAddress() *refsV2.Address }) (*oid.ID, error) {
|
|
|
|
idV2 := body.GetAddress().GetObjectID()
|
2023-07-03 08:36:20 +00:00
|
|
|
return getObjectIDFromRefObjectID(idV2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getObjectIDFromRefObjectID(idV2 *refsV2.ObjectID) (*oid.ID, error) {
|
2022-05-12 16:37:46 +00:00
|
|
|
if idV2 == nil {
|
2022-09-16 10:22:09 +00:00
|
|
|
return nil, errors.New("missing object ID")
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
2022-05-12 16:37:46 +00:00
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
var id oid.ID
|
2022-05-12 16:37:46 +00:00
|
|
|
|
|
|
|
err := id.ReadFromV2(*idV2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &id, nil
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-18 15:20:08 +00:00
|
|
|
func ownerFromToken(token *sessionSDK.Object) (*user.ID, *keys.PublicKey, error) {
|
2022-02-11 12:25:05 +00:00
|
|
|
// 1. First check signature of session token.
|
|
|
|
if !token.VerifySignature() {
|
2022-11-10 17:46:52 +00:00
|
|
|
return nil, nil, errInvalidSessionSig
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Then check if session token owner issued the session token
|
2023-06-26 13:18:39 +00:00
|
|
|
// TODO(@cthulhu-rider): #468 implement and use another approach to avoid conversion
|
2022-05-18 15:20:08 +00:00
|
|
|
var tokV2 sessionV2.Token
|
|
|
|
token.WriteToV2(&tokV2)
|
|
|
|
|
2022-05-17 13:59:46 +00:00
|
|
|
tokenIssuerKey, err := unmarshalPublicKey(tokV2.GetSignature().GetKey())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("invalid key in session token signature: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-05-25 16:09:12 +00:00
|
|
|
tokenIssuer := token.Issuer()
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
if !isOwnerFromKey(tokenIssuer, tokenIssuerKey) {
|
2022-12-23 17:35:35 +00:00
|
|
|
// TODO: #767 in this case we can issue all owner keys from frostfs.id and check once again
|
2022-11-10 17:46:52 +00:00
|
|
|
return nil, nil, errInvalidSessionOwner
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-25 16:09:12 +00:00
|
|
|
return &tokenIssuer, tokenIssuerKey, nil
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 13:15:31 +00:00
|
|
|
func originalBodySignature(v *sessionV2.RequestVerificationHeader) *refsV2.Signature {
|
2022-02-11 12:25:05 +00:00
|
|
|
if v == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for v.GetOrigin() != nil {
|
|
|
|
v = v.GetOrigin()
|
|
|
|
}
|
|
|
|
|
2022-05-16 13:15:31 +00:00
|
|
|
return v.GetBodySignature()
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-17 13:59:46 +00:00
|
|
|
func unmarshalPublicKey(bs []byte) (*keys.PublicKey, error) {
|
|
|
|
return keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
|
|
|
|
if key == nil {
|
2022-02-11 12:25:05 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-05-17 13:59:46 +00:00
|
|
|
var id2 user.ID
|
|
|
|
user.IDFromKey(&id2, (ecdsa.PublicKey)(*key))
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
return id2.Equals(id)
|
2022-02-11 12:25:05 +00:00
|
|
|
}
|
2022-02-28 12:35:10 +00:00
|
|
|
|
2022-05-18 15:20:08 +00:00
|
|
|
// assertVerb checks that token verb corresponds to op.
|
2022-06-17 13:40:51 +00:00
|
|
|
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
2022-05-18 15:20:08 +00:00
|
|
|
switch op {
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectPut:
|
2024-08-12 14:11:10 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectPatch)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectDelete:
|
2022-05-18 15:20:08 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectDelete)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectGet:
|
2022-05-18 15:20:08 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectGet)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectHead:
|
2022-05-18 15:20:08 +00:00
|
|
|
return tok.AssertVerb(
|
|
|
|
sessionSDK.VerbObjectHead,
|
|
|
|
sessionSDK.VerbObjectGet,
|
|
|
|
sessionSDK.VerbObjectDelete,
|
|
|
|
sessionSDK.VerbObjectRange,
|
2024-08-12 14:11:10 +00:00
|
|
|
sessionSDK.VerbObjectRangeHash,
|
|
|
|
sessionSDK.VerbObjectPatch,
|
|
|
|
)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectSearch:
|
2022-05-18 15:20:08 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectRange:
|
2024-08-12 14:11:10 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectPatch)
|
2022-06-17 13:40:51 +00:00
|
|
|
case acl.OpObjectHash:
|
2022-05-18 15:20:08 +00:00
|
|
|
return tok.AssertVerb(sessionSDK.VerbObjectRangeHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2022-02-28 12:35:10 +00:00
|
|
|
}
|
2022-09-16 10:22:09 +00:00
|
|
|
|
2023-02-05 15:59:38 +00:00
|
|
|
// assertSessionRelation checks if given token describing the FrostFS session
|
2022-09-16 10:22:09 +00:00
|
|
|
// relates to the given container and optional object. Missing object
|
2023-02-05 15:59:38 +00:00
|
|
|
// means that the context isn't bound to any FrostFS object in the container.
|
2022-09-16 10:22:09 +00:00
|
|
|
// 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
|
|
|
|
}
|