[#1052] object: Nuke out acl
middleware
Some checks failed
Tests and linters / Run gofumpt (push) Successful in 1m43s
Build / Build Components (push) Successful in 2m3s
Pre-commit hooks / Pre-commit (push) Successful in 2m12s
Tests and linters / Lint (push) Successful in 2m48s
Tests and linters / Staticcheck (push) Successful in 2m47s
Tests and linters / Tests (push) Successful in 1m36s
Tests and linters / gopls check (push) Successful in 4m43s
Tests and linters / Tests with -race (push) Successful in 5m16s
Vulncheck / Vulncheck (push) Failing after 11m48s
OCI image / Build container images (push) Successful in 4m32s
Some checks failed
Tests and linters / Run gofumpt (push) Successful in 1m43s
Build / Build Components (push) Successful in 2m3s
Pre-commit hooks / Pre-commit (push) Successful in 2m12s
Tests and linters / Lint (push) Successful in 2m48s
Tests and linters / Staticcheck (push) Successful in 2m47s
Tests and linters / Tests (push) Successful in 1m36s
Tests and linters / gopls check (push) Successful in 4m43s
Tests and linters / Tests with -race (push) Successful in 5m16s
Vulncheck / Vulncheck (push) Failing after 11m48s
OCI image / Build container images (push) Successful in 4m32s
* Remove `acl` package as it's no longer used; * Remove `RequestContext`; * Fix `cmd/frostfs-node`. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
73e35bc885
commit
ccdd6cb767
15 changed files with 2 additions and 2115 deletions
|
@ -16,7 +16,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
|
||||
objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc"
|
||||
objectService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
||||
v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2"
|
||||
objectAPE "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/ape"
|
||||
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
||||
deletesvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/delete"
|
||||
|
@ -174,10 +173,8 @@ func initObjectService(c *cfg) {
|
|||
|
||||
apeSvc := createAPEService(c, &irFetcher, splitSvc)
|
||||
|
||||
aclSvc := createACLServiceV2(c, apeSvc, &irFetcher)
|
||||
|
||||
var commonSvc objectService.Common
|
||||
commonSvc.Init(&c.internals, aclSvc)
|
||||
commonSvc.Init(&c.internals, apeSvc)
|
||||
|
||||
respSvc := objectService.NewResponseService(
|
||||
&commonSvc,
|
||||
|
@ -284,7 +281,7 @@ func addPolicer(c *cfg, keyStorage *util.KeyStorage, clientConstructor *cache.Cl
|
|||
})
|
||||
}
|
||||
|
||||
func createInnerRingFetcher(c *cfg) v2.InnerRingFetcher {
|
||||
func createInnerRingFetcher(c *cfg) objectAPE.InnerRingFetcher {
|
||||
return &innerRingFetcherWithNotary{
|
||||
sidechain: c.cfgMorph.client,
|
||||
}
|
||||
|
@ -429,16 +426,6 @@ func createSplitService(c *cfg, sPutV2 *putsvcV2.Service, sGetV2 *getsvcV2.Servi
|
|||
)
|
||||
}
|
||||
|
||||
func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) v2.Service {
|
||||
return v2.New(
|
||||
apeSvc,
|
||||
c.netMapSource,
|
||||
irFetcher,
|
||||
c.cfgObject.cnrSource,
|
||||
v2.WithLogger(c.log),
|
||||
)
|
||||
}
|
||||
|
||||
func createAPEService(c *cfg, irFetcher *cachedIRFetcher, splitSvc *objectService.TransportSplitter) *objectAPE.Service {
|
||||
return objectAPE.NewService(
|
||||
objectAPE.NewChecker(
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
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/api/session"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testLocalStorage struct {
|
||||
t *testing.T
|
||||
|
||||
expAddr oid.Address
|
||||
|
||||
obj *objectSDK.Object
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *testLocalStorage) Head(ctx context.Context, addr oid.Address) (*objectSDK.Object, error) {
|
||||
require.True(s.t, addr.Container().Equals(s.expAddr.Container()))
|
||||
require.True(s.t, addr.Object().Equals(s.expAddr.Object()))
|
||||
|
||||
return s.obj, s.err
|
||||
}
|
||||
|
||||
func testXHeaders(strs ...string) []session.XHeader {
|
||||
res := make([]session.XHeader, len(strs)/2)
|
||||
|
||||
for i := 0; i < len(strs); i += 2 {
|
||||
res[i/2].SetKey(strs[i])
|
||||
res[i/2].SetValue(strs[i+1])
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func TestHeadRequest(t *testing.T) {
|
||||
req := new(objectV2.HeadRequest)
|
||||
|
||||
meta := new(session.RequestMetaHeader)
|
||||
req.SetMetaHeader(meta)
|
||||
|
||||
body := new(objectV2.HeadRequestBody)
|
||||
req.SetBody(body)
|
||||
|
||||
addr := oidtest.Address()
|
||||
|
||||
var addrV2 refs.Address
|
||||
addr.WriteToV2(&addrV2)
|
||||
|
||||
body.SetAddress(&addrV2)
|
||||
|
||||
xKey := "x-key"
|
||||
xVal := "x-val"
|
||||
xHdrs := testXHeaders(
|
||||
xKey, xVal,
|
||||
)
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj := objectSDK.New()
|
||||
|
||||
attrKey := "attr_key"
|
||||
attrVal := "attr_val"
|
||||
var attr objectSDK.Attribute
|
||||
attr.SetKey(attrKey)
|
||||
attr.SetValue(attrVal)
|
||||
obj.SetAttributes(attr)
|
||||
|
||||
table := new(eaclSDK.Table)
|
||||
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
senderKey := priv.PublicKey()
|
||||
|
||||
r := eaclSDK.NewRecord()
|
||||
r.SetOperation(eaclSDK.OperationHead)
|
||||
r.SetAction(eaclSDK.ActionDeny)
|
||||
r.AddFilter(eaclSDK.HeaderFromObject, eaclSDK.MatchStringEqual, attrKey, attrVal)
|
||||
r.AddFilter(eaclSDK.HeaderFromRequest, eaclSDK.MatchStringEqual, xKey, xVal)
|
||||
eaclSDK.AddFormedTarget(r, eaclSDK.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
|
||||
|
||||
table.AddRecord(r)
|
||||
|
||||
lStorage := &testLocalStorage{
|
||||
t: t,
|
||||
expAddr: addr,
|
||||
obj: obj,
|
||||
}
|
||||
|
||||
id := addr.Object()
|
||||
|
||||
newSource := func(t *testing.T) eaclSDK.TypedHeaderSource {
|
||||
hdrSrc, err := NewMessageHeaderSource(
|
||||
lStorage,
|
||||
NewRequestXHeaderSource(req),
|
||||
addr.Container(),
|
||||
WithOID(&id))
|
||||
require.NoError(t, err)
|
||||
return hdrSrc
|
||||
}
|
||||
|
||||
cnr := addr.Container()
|
||||
|
||||
unit := new(eaclSDK.ValidationUnit).
|
||||
WithContainerID(&cnr).
|
||||
WithOperation(eaclSDK.OperationHead).
|
||||
WithSenderKey(senderKey.Bytes()).
|
||||
WithEACLTable(table)
|
||||
|
||||
validator := eaclSDK.NewValidator()
|
||||
|
||||
checkAction(t, eaclSDK.ActionDeny, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
meta.SetXHeaders(nil)
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj.SetAttributes()
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
lStorage.err = errors.New("any error")
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
r.SetAction(eaclSDK.ActionAllow)
|
||||
|
||||
rID := eaclSDK.NewRecord()
|
||||
rID.SetOperation(eaclSDK.OperationHead)
|
||||
rID.SetAction(eaclSDK.ActionDeny)
|
||||
rID.AddObjectIDFilter(eaclSDK.MatchStringEqual, addr.Object())
|
||||
eaclSDK.AddFormedTarget(rID, eaclSDK.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
|
||||
|
||||
table = eaclSDK.NewTable()
|
||||
table.AddRecord(r)
|
||||
table.AddRecord(rID)
|
||||
|
||||
unit.WithEACLTable(table)
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
}
|
||||
|
||||
func checkAction(t *testing.T, expected eaclSDK.Action, v *eaclSDK.Validator, u *eaclSDK.ValidationUnit) {
|
||||
actual, fromRule := v.CalculateAction(u)
|
||||
require.True(t, fromRule)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func checkDefaultAction(t *testing.T, v *eaclSDK.Validator, u *eaclSDK.ValidationUnit) {
|
||||
actual, fromRule := v.CalculateAction(u)
|
||||
require.False(t, fromRule)
|
||||
require.Equal(t, eaclSDK.ActionAllow, actual)
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
refsV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
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"
|
||||
)
|
||||
|
||||
type Option func(*cfg)
|
||||
|
||||
type cfg struct {
|
||||
storage ObjectStorage
|
||||
|
||||
msg XHeaderSource
|
||||
|
||||
cnr cid.ID
|
||||
obj *oid.ID
|
||||
}
|
||||
|
||||
type ObjectStorage interface {
|
||||
Head(context.Context, oid.Address) (*objectSDK.Object, error)
|
||||
}
|
||||
|
||||
type Request interface {
|
||||
GetMetaHeader() *session.RequestMetaHeader
|
||||
}
|
||||
|
||||
type Response interface {
|
||||
GetMetaHeader() *session.ResponseMetaHeader
|
||||
}
|
||||
|
||||
type headerSource struct {
|
||||
requestHeaders []eaclSDK.Header
|
||||
objectHeaders []eaclSDK.Header
|
||||
|
||||
incompleteObjectHeaders bool
|
||||
}
|
||||
|
||||
func NewMessageHeaderSource(os ObjectStorage, xhs XHeaderSource, cnrID cid.ID, opts ...Option) (eaclSDK.TypedHeaderSource, error) {
|
||||
cfg := &cfg{
|
||||
storage: os,
|
||||
cnr: cnrID,
|
||||
msg: xhs,
|
||||
}
|
||||
|
||||
for i := range opts {
|
||||
opts[i](cfg)
|
||||
}
|
||||
|
||||
if cfg.msg == nil {
|
||||
return nil, errors.New("message is not provided")
|
||||
}
|
||||
|
||||
var res headerSource
|
||||
|
||||
err := cfg.readObjectHeaders(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.requestHeaders = cfg.msg.GetXHeaders()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eaclSDK.Header, bool) {
|
||||
switch typ {
|
||||
default:
|
||||
return nil, true
|
||||
case eaclSDK.HeaderFromRequest:
|
||||
return h.requestHeaders, true
|
||||
case eaclSDK.HeaderFromObject:
|
||||
return h.objectHeaders, !h.incompleteObjectHeaders
|
||||
}
|
||||
}
|
||||
|
||||
type xHeader session.XHeader
|
||||
|
||||
func (x xHeader) Key() string {
|
||||
return (*session.XHeader)(&x).GetKey()
|
||||
}
|
||||
|
||||
func (x xHeader) Value() string {
|
||||
return (*session.XHeader)(&x).GetValue()
|
||||
}
|
||||
|
||||
var errMissingOID = errors.New("object ID is missing")
|
||||
|
||||
func (h *cfg) readObjectHeaders(dst *headerSource) error {
|
||||
switch m := h.msg.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||
case requestXHeaderSource:
|
||||
return h.readObjectHeadersFromRequestXHeaderSource(m, dst)
|
||||
case responseXHeaderSource:
|
||||
return h.readObjectHeadersResponseXHeaderSource(m, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *cfg) readObjectHeadersFromRequestXHeaderSource(m requestXHeaderSource, dst *headerSource) error {
|
||||
switch req := m.req.(type) {
|
||||
case
|
||||
*objectV2.GetRequest,
|
||||
*objectV2.HeadRequest:
|
||||
if h.obj == nil {
|
||||
return errMissingOID
|
||||
}
|
||||
|
||||
objHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||
|
||||
dst.objectHeaders = objHeaders
|
||||
dst.incompleteObjectHeaders = !completed
|
||||
case
|
||||
*objectV2.GetRangeRequest,
|
||||
*objectV2.GetRangeHashRequest,
|
||||
*objectV2.DeleteRequest:
|
||||
if h.obj == nil {
|
||||
return errMissingOID
|
||||
}
|
||||
|
||||
dst.objectHeaders = addressHeaders(h.cnr, h.obj)
|
||||
case *objectV2.PutRequest:
|
||||
if v, ok := req.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
case *objectV2.PutSingleRequest:
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(req.GetBody().GetObject()), h.cnr, h.obj)
|
||||
case *objectV2.SearchRequest:
|
||||
cnrV2 := req.GetBody().GetContainerID()
|
||||
var cnr cid.ID
|
||||
|
||||
if cnrV2 != nil {
|
||||
if err := cnr.ReadFromV2(*cnrV2); err != nil {
|
||||
return fmt.Errorf("can't parse container ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
dst.objectHeaders = []eaclSDK.Header{cidHeader(cnr)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *cfg) readObjectHeadersResponseXHeaderSource(m responseXHeaderSource, dst *headerSource) error {
|
||||
switch resp := m.resp.(type) {
|
||||
default:
|
||||
objectHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||
|
||||
dst.objectHeaders = objectHeaders
|
||||
dst.incompleteObjectHeaders = !completed
|
||||
case *objectV2.GetResponse:
|
||||
if v, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
case *objectV2.HeadResponse:
|
||||
oV2 := new(objectV2.Object)
|
||||
|
||||
var hdr *objectV2.Header
|
||||
|
||||
switch v := resp.GetBody().GetHeaderPart().(type) {
|
||||
case *objectV2.ShortHeader:
|
||||
hdr = new(objectV2.Header)
|
||||
|
||||
var idV2 refsV2.ContainerID
|
||||
h.cnr.WriteToV2(&idV2)
|
||||
|
||||
hdr.SetContainerID(&idV2)
|
||||
hdr.SetVersion(v.GetVersion())
|
||||
hdr.SetCreationEpoch(v.GetCreationEpoch())
|
||||
hdr.SetOwnerID(v.GetOwnerID())
|
||||
hdr.SetObjectType(v.GetObjectType())
|
||||
hdr.SetPayloadLength(v.GetPayloadLength())
|
||||
case *objectV2.HeaderWithSignature:
|
||||
hdr = v.GetHeader()
|
||||
}
|
||||
|
||||
oV2.SetHeader(hdr)
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *cfg) localObjectHeaders(cnr cid.ID, idObj *oid.ID) ([]eaclSDK.Header, bool) {
|
||||
if idObj != nil {
|
||||
var addr oid.Address
|
||||
addr.SetContainer(cnr)
|
||||
addr.SetObject(*idObj)
|
||||
|
||||
obj, err := h.storage.Head(context.TODO(), addr)
|
||||
if err == nil {
|
||||
return headersFromObject(obj, cnr, idObj), true
|
||||
}
|
||||
}
|
||||
|
||||
return addressHeaders(cnr, idObj), false
|
||||
}
|
||||
|
||||
func cidHeader(idCnr cid.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectContainerID,
|
||||
v: idCnr.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func oidHeader(obj oid.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectID,
|
||||
v: obj.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func ownerIDHeader(ownerID user.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectOwnerID,
|
||||
v: ownerID.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func addressHeaders(cnr cid.ID, oid *oid.ID) []eaclSDK.Header {
|
||||
hh := make([]eaclSDK.Header, 0, 2)
|
||||
hh = append(hh, cidHeader(cnr))
|
||||
|
||||
if oid != nil {
|
||||
hh = append(hh, oidHeader(*oid))
|
||||
}
|
||||
|
||||
return hh
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
type sysObjHdr struct {
|
||||
k, v string
|
||||
}
|
||||
|
||||
func (s sysObjHdr) Key() string {
|
||||
return s.k
|
||||
}
|
||||
|
||||
func (s sysObjHdr) Value() string {
|
||||
return s.v
|
||||
}
|
||||
|
||||
func u64Value(v uint64) string {
|
||||
return strconv.FormatUint(v, 10)
|
||||
}
|
||||
|
||||
func headersFromObject(obj *objectSDK.Object, cnr cid.ID, oid *oid.ID) []eaclSDK.Header {
|
||||
var count int
|
||||
for obj := obj; obj != nil; obj = obj.Parent() {
|
||||
count += 9 + len(obj.Attributes())
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, count)
|
||||
for ; obj != nil; obj = obj.Parent() {
|
||||
res = append(res,
|
||||
cidHeader(cnr),
|
||||
// creation epoch
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectCreationEpoch,
|
||||
v: u64Value(obj.CreationEpoch()),
|
||||
},
|
||||
// payload size
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectPayloadLength,
|
||||
v: u64Value(obj.PayloadSize()),
|
||||
},
|
||||
// object version
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectVersion,
|
||||
v: obj.Version().String(),
|
||||
},
|
||||
// object type
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectType,
|
||||
v: obj.Type().String(),
|
||||
},
|
||||
)
|
||||
|
||||
if oid != nil {
|
||||
res = append(res, oidHeader(*oid))
|
||||
}
|
||||
|
||||
if idOwner := obj.OwnerID(); !idOwner.IsEmpty() {
|
||||
res = append(res, ownerIDHeader(idOwner))
|
||||
}
|
||||
|
||||
cs, ok := obj.PayloadChecksum()
|
||||
if ok {
|
||||
res = append(res, sysObjHdr{
|
||||
k: acl.FilterObjectPayloadHash,
|
||||
v: cs.String(),
|
||||
})
|
||||
}
|
||||
|
||||
cs, ok = obj.PayloadHomomorphicHash()
|
||||
if ok {
|
||||
res = append(res, sysObjHdr{
|
||||
k: acl.FilterObjectHomomorphicHash,
|
||||
v: cs.String(),
|
||||
})
|
||||
}
|
||||
|
||||
attrs := obj.Attributes()
|
||||
for i := range attrs {
|
||||
res = append(res, &attrs[i]) // only pointer attrs can implement eaclSDK.Header interface
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
func WithOID(v *oid.ID) Option {
|
||||
return func(c *cfg) {
|
||||
c.obj = v
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
)
|
||||
|
||||
type XHeaderSource interface {
|
||||
GetXHeaders() []eaclSDK.Header
|
||||
}
|
||||
|
||||
type requestXHeaderSource struct {
|
||||
req Request
|
||||
}
|
||||
|
||||
func NewRequestXHeaderSource(req Request) XHeaderSource {
|
||||
return requestXHeaderSource{req: req}
|
||||
}
|
||||
|
||||
type responseXHeaderSource struct {
|
||||
resp Response
|
||||
|
||||
req Request
|
||||
}
|
||||
|
||||
func NewResponseXHeaderSource(resp Response, req Request) XHeaderSource {
|
||||
return responseXHeaderSource{resp: resp, req: req}
|
||||
}
|
||||
|
||||
func (s requestXHeaderSource) GetXHeaders() []eaclSDK.Header {
|
||||
ln := 0
|
||||
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
ln += len(meta.GetXHeaders())
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, ln)
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
for i := range x {
|
||||
res = append(res, (xHeader)(x[i]))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s responseXHeaderSource) GetXHeaders() []eaclSDK.Header {
|
||||
ln := 0
|
||||
xHdrs := make([][]session.XHeader, 0)
|
||||
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
|
||||
ln += len(x)
|
||||
|
||||
xHdrs = append(xHdrs, x)
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, ln)
|
||||
|
||||
for i := range xHdrs {
|
||||
for j := range xHdrs[i] {
|
||||
res = append(res, xHeader(xHdrs[i][j]))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const invalidRequestMessage = "malformed request"
|
||||
|
||||
func malformedRequestError(reason string) error {
|
||||
return fmt.Errorf("%s: %s", invalidRequestMessage, reason)
|
||||
}
|
||||
|
||||
var (
|
||||
errEmptyBody = malformedRequestError("empty body")
|
||||
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")
|
||||
)
|
|
@ -1,12 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
)
|
||||
|
||||
// WithLogger returns option to set logger.
|
||||
func WithLogger(v *logger.Logger) Option {
|
||||
return func(c *cfg) {
|
||||
c.log = v
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
// RequestInfo groups parsed version-independent (from SDK library)
|
||||
// request information and raw API request.
|
||||
type RequestInfo struct {
|
||||
basicACL acl.Basic
|
||||
requestRole acl.Role
|
||||
operation acl.Op // put, get, head, etc.
|
||||
cnrOwner user.ID // container owner
|
||||
|
||||
// cnrNamespace defined to which namespace a container is belonged.
|
||||
cnrNamespace string
|
||||
|
||||
idCnr cid.ID
|
||||
|
||||
// optional for some request
|
||||
// e.g. Put, Search
|
||||
obj *oid.ID
|
||||
|
||||
senderKey []byte
|
||||
|
||||
bearer *bearer.Token // bearer token of request
|
||||
|
||||
srcRequest any
|
||||
}
|
||||
|
||||
func (r *RequestInfo) SetBasicACL(basicACL acl.Basic) {
|
||||
r.basicACL = basicACL
|
||||
}
|
||||
|
||||
func (r *RequestInfo) SetRequestRole(requestRole acl.Role) {
|
||||
r.requestRole = requestRole
|
||||
}
|
||||
|
||||
func (r *RequestInfo) SetSenderKey(senderKey []byte) {
|
||||
r.senderKey = senderKey
|
||||
}
|
||||
|
||||
// Request returns raw API request.
|
||||
func (r RequestInfo) Request() any {
|
||||
return r.srcRequest
|
||||
}
|
||||
|
||||
// ContainerOwner returns owner if the container.
|
||||
func (r RequestInfo) ContainerOwner() user.ID {
|
||||
return r.cnrOwner
|
||||
}
|
||||
|
||||
func (r RequestInfo) ContainerNamespace() string {
|
||||
return r.cnrNamespace
|
||||
}
|
||||
|
||||
// ObjectID return object ID.
|
||||
func (r RequestInfo) ObjectID() *oid.ID {
|
||||
return r.obj
|
||||
}
|
||||
|
||||
// ContainerID return container ID.
|
||||
func (r RequestInfo) ContainerID() cid.ID {
|
||||
return r.idCnr
|
||||
}
|
||||
|
||||
// CleanBearer forces cleaning bearer token information.
|
||||
func (r *RequestInfo) CleanBearer() {
|
||||
r.bearer = nil
|
||||
}
|
||||
|
||||
// Bearer returns bearer token of the request.
|
||||
func (r RequestInfo) Bearer() *bearer.Token {
|
||||
return r.bearer
|
||||
}
|
||||
|
||||
// BasicACL returns basic ACL of the container.
|
||||
func (r RequestInfo) BasicACL() acl.Basic {
|
||||
return r.basicACL
|
||||
}
|
||||
|
||||
// SenderKey returns public key of the request's sender.
|
||||
func (r RequestInfo) SenderKey() []byte {
|
||||
return r.senderKey
|
||||
}
|
||||
|
||||
// Operation returns request's operation.
|
||||
func (r RequestInfo) Operation() acl.Op {
|
||||
return r.operation
|
||||
}
|
||||
|
||||
// RequestRole returns request sender's role.
|
||||
func (r RequestInfo) RequestRole() acl.Role {
|
||||
return r.requestRole
|
||||
}
|
||||
|
||||
// MetaWithToken groups session and bearer tokens,
|
||||
// verification header and raw API request.
|
||||
type MetaWithToken struct {
|
||||
vheader *sessionV2.RequestVerificationHeader
|
||||
token *sessionSDK.Object
|
||||
bearer *bearer.Token
|
||||
src any
|
||||
}
|
||||
|
||||
// RequestOwner returns ownerID and its public key
|
||||
// according to internal meta information.
|
||||
func (r MetaWithToken) RequestOwner() (*user.ID, *keys.PublicKey, error) {
|
||||
if r.vheader == nil {
|
||||
return nil, nil, errEmptyVerificationHeader
|
||||
}
|
||||
|
||||
if r.bearer != nil && r.bearer.Impersonate() {
|
||||
return unmarshalPublicKeyWithOwner(r.bearer.SigningKeyBytes())
|
||||
}
|
||||
|
||||
// if session token is presented, use it as truth source
|
||||
if r.token != nil {
|
||||
// verify signature of session token
|
||||
return ownerFromToken(r.token)
|
||||
}
|
||||
|
||||
// otherwise get original body signature
|
||||
bodySignature := originalBodySignature(r.vheader)
|
||||
if bodySignature == nil {
|
||||
return nil, nil, errEmptyBodySig
|
||||
}
|
||||
|
||||
return unmarshalPublicKeyWithOwner(bodySignature.GetKey())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
package v2
|
||||
|
||||
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 := MetaWithToken{}
|
||||
checkOwner(t, req, nil, errEmptyVerificationHeader)
|
||||
})
|
||||
t.Run("empty verification header signature", func(t *testing.T) {
|
||||
req := MetaWithToken{
|
||||
vheader: new(sessionV2.RequestVerificationHeader),
|
||||
}
|
||||
checkOwner(t, req, nil, errEmptyBodySig)
|
||||
})
|
||||
t.Run("no tokens", func(t *testing.T) {
|
||||
req := MetaWithToken{
|
||||
vheader: vh,
|
||||
}
|
||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
||||
})
|
||||
|
||||
t.Run("bearer without impersonate, no session", func(t *testing.T) {
|
||||
req := MetaWithToken{
|
||||
vheader: vh,
|
||||
bearer: newBearer(t, containerOwner, userID, false),
|
||||
}
|
||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
||||
})
|
||||
t.Run("bearer with impersonate, no session", func(t *testing.T) {
|
||||
req := MetaWithToken{
|
||||
vheader: vh,
|
||||
bearer: 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 := MetaWithToken{
|
||||
vheader: vh,
|
||||
bearer: newBearer(t, containerOwner, userID, true),
|
||||
token: newSession(t, pk),
|
||||
}
|
||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
||||
})
|
||||
t.Run("with session", func(t *testing.T) {
|
||||
req := MetaWithToken{
|
||||
vheader: vh,
|
||||
token: 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 := MetaWithToken{
|
||||
vheader: vh,
|
||||
token: &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 MetaWithToken, 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)
|
||||
}
|
|
@ -1,779 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/services/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Service checks basic ACL rules.
|
||||
type Service struct {
|
||||
*cfg
|
||||
|
||||
c objectCore.SenderClassifier
|
||||
}
|
||||
|
||||
type putStreamBasicChecker struct {
|
||||
source *Service
|
||||
next object.PutObjectStream
|
||||
}
|
||||
|
||||
type patchStreamBasicChecker struct {
|
||||
source *Service
|
||||
next object.PatchObjectStream
|
||||
nonFirstSend bool
|
||||
}
|
||||
|
||||
// Option represents Service constructor option.
|
||||
type Option func(*cfg)
|
||||
|
||||
type cfg struct {
|
||||
log *logger.Logger
|
||||
|
||||
containers container.Source
|
||||
|
||||
irFetcher InnerRingFetcher
|
||||
|
||||
nm netmap.Source
|
||||
|
||||
next object.ServiceServer
|
||||
}
|
||||
|
||||
// New is a constructor for object ACL checking service.
|
||||
func New(next object.ServiceServer,
|
||||
nm netmap.Source,
|
||||
irf InnerRingFetcher,
|
||||
cs container.Source,
|
||||
opts ...Option,
|
||||
) Service {
|
||||
cfg := &cfg{
|
||||
log: logger.NewLoggerWrapper(zap.L()),
|
||||
next: next,
|
||||
nm: nm,
|
||||
irFetcher: irf,
|
||||
containers: cs,
|
||||
}
|
||||
|
||||
for i := range opts {
|
||||
opts[i](cfg)
|
||||
}
|
||||
|
||||
return Service{
|
||||
cfg: cfg,
|
||||
c: objectCore.NewSenderClassifier(cfg.irFetcher, cfg.nm, cfg.log),
|
||||
}
|
||||
}
|
||||
|
||||
// wrappedGetObjectStream propagates RequestContext into GetObjectStream's context.
|
||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
||||
type wrappedGetObjectStream struct {
|
||||
object.GetObjectStream
|
||||
|
||||
requestInfo RequestInfo
|
||||
}
|
||||
|
||||
func (w *wrappedGetObjectStream) Context() context.Context {
|
||||
return context.WithValue(w.GetObjectStream.Context(), object.RequestContextKey, &object.RequestContext{
|
||||
Namespace: w.requestInfo.ContainerNamespace(),
|
||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
||||
SenderKey: w.requestInfo.SenderKey(),
|
||||
Role: w.requestInfo.RequestRole(),
|
||||
BearerToken: w.requestInfo.Bearer(),
|
||||
})
|
||||
}
|
||||
|
||||
func newWrappedGetObjectStreamStream(getObjectStream object.GetObjectStream, reqInfo RequestInfo) object.GetObjectStream {
|
||||
return &wrappedGetObjectStream{
|
||||
GetObjectStream: getObjectStream,
|
||||
requestInfo: reqInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// wrappedRangeStream propagates RequestContext into GetObjectRangeStream's context.
|
||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
||||
type wrappedRangeStream struct {
|
||||
object.GetObjectRangeStream
|
||||
|
||||
requestInfo RequestInfo
|
||||
}
|
||||
|
||||
func (w *wrappedRangeStream) Context() context.Context {
|
||||
return context.WithValue(w.GetObjectRangeStream.Context(), object.RequestContextKey, &object.RequestContext{
|
||||
Namespace: w.requestInfo.ContainerNamespace(),
|
||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
||||
SenderKey: w.requestInfo.SenderKey(),
|
||||
Role: w.requestInfo.RequestRole(),
|
||||
BearerToken: w.requestInfo.Bearer(),
|
||||
})
|
||||
}
|
||||
|
||||
func newWrappedRangeStream(rangeStream object.GetObjectRangeStream, reqInfo RequestInfo) object.GetObjectRangeStream {
|
||||
return &wrappedRangeStream{
|
||||
GetObjectRangeStream: rangeStream,
|
||||
requestInfo: reqInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// wrappedSearchStream propagates RequestContext into SearchStream's context.
|
||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
||||
type wrappedSearchStream struct {
|
||||
object.SearchStream
|
||||
|
||||
requestInfo RequestInfo
|
||||
}
|
||||
|
||||
func (w *wrappedSearchStream) Context() context.Context {
|
||||
return context.WithValue(w.SearchStream.Context(), object.RequestContextKey, &object.RequestContext{
|
||||
Namespace: w.requestInfo.ContainerNamespace(),
|
||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
||||
SenderKey: w.requestInfo.SenderKey(),
|
||||
Role: w.requestInfo.RequestRole(),
|
||||
BearerToken: w.requestInfo.Bearer(),
|
||||
})
|
||||
}
|
||||
|
||||
func newWrappedSearchStream(searchStream object.SearchStream, reqInfo RequestInfo) object.SearchStream {
|
||||
return &wrappedSearchStream{
|
||||
SearchStream: searchStream,
|
||||
requestInfo: reqInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Get implements ServiceServer interface, makes ACL checks and calls
|
||||
// next Get method in the ServiceServer pipeline.
|
||||
func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream) error {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, cnr, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectGet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Get(request, newWrappedGetObjectStreamStream(stream, reqInfo))
|
||||
}
|
||||
|
||||
func (b Service) Put(ctx context.Context) (object.PutObjectStream, error) {
|
||||
streamer, err := b.next.Put(ctx)
|
||||
|
||||
return putStreamBasicChecker{
|
||||
source: &b,
|
||||
next: streamer,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (b Service) Patch(ctx context.Context) (object.PatchObjectStream, error) {
|
||||
streamer, err := b.next.Patch(ctx)
|
||||
|
||||
return &patchStreamBasicChecker{
|
||||
source: &b,
|
||||
next: streamer,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (b Service) Head(
|
||||
ctx context.Context,
|
||||
request *objectV2.HeadRequest,
|
||||
) (*objectV2.HeadResponse, error) {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, cnr, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHead)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Head(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStream) error {
|
||||
id, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, id, acl.OpObjectSearch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.next.Search(request, newWrappedSearchStream(stream, reqInfo))
|
||||
}
|
||||
|
||||
func (b Service) Delete(
|
||||
ctx context.Context,
|
||||
request *objectV2.DeleteRequest,
|
||||
) (*objectV2.DeleteResponse, error) {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, cnr, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectDelete)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Delete(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetObjectRangeStream) error {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, cnr, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectRange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.GetRange(request, newWrappedRangeStream(stream, reqInfo))
|
||||
}
|
||||
|
||||
func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context {
|
||||
return context.WithValue(ctx, object.RequestContextKey, &object.RequestContext{
|
||||
Namespace: reqInfo.ContainerNamespace(),
|
||||
ContainerOwner: reqInfo.ContainerOwner(),
|
||||
SenderKey: reqInfo.SenderKey(),
|
||||
Role: reqInfo.RequestRole(),
|
||||
BearerToken: reqInfo.Bearer(),
|
||||
})
|
||||
}
|
||||
|
||||
func (b Service) GetRangeHash(
|
||||
ctx context.Context,
|
||||
request *objectV2.GetRangeHashRequest,
|
||||
) (*objectV2.GetRangeHashResponse, error) {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sTok != nil {
|
||||
err = assertSessionRelation(*sTok, cnr, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.GetRangeHash(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
func (b Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequest) (*objectV2.PutSingleResponse, error) {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idV2 := request.GetBody().GetObject().GetHeader().GetOwnerID()
|
||||
if idV2 == nil {
|
||||
return nil, errors.New("missing object owner")
|
||||
}
|
||||
|
||||
var idOwner user.ID
|
||||
|
||||
err = idOwner.ReadFromV2(*idV2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid object owner: %w", err)
|
||||
}
|
||||
|
||||
obj, err := getObjectIDFromRefObjectID(request.GetBody().GetObject().GetObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sTok *sessionSDK.Object
|
||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.PutSingle(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
||||
body := request.GetBody()
|
||||
if body == nil {
|
||||
return errEmptyBody
|
||||
}
|
||||
|
||||
part := body.GetObjectPart()
|
||||
if part, ok := part.(*objectV2.PutObjectPartInit); ok {
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idV2 := part.GetHeader().GetOwnerID()
|
||||
if idV2 == nil {
|
||||
return errors.New("missing object owner")
|
||||
}
|
||||
|
||||
var idOwner user.ID
|
||||
|
||||
err = idOwner.ReadFromV2(*idV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid object owner: %w", err)
|
||||
}
|
||||
|
||||
objV2 := part.GetObjectID()
|
||||
var obj *oid.ID
|
||||
|
||||
if objV2 != nil {
|
||||
obj = new(oid.ID)
|
||||
|
||||
err = obj.ReadFromV2(*objV2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var sTok *sessionSDK.Object
|
||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := p.source.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
ctx = requestContext(ctx, reqInfo)
|
||||
}
|
||||
|
||||
return p.next.Send(ctx, request)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (p putStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PutResponse, error) {
|
||||
return p.next.CloseAndRecv(ctx)
|
||||
}
|
||||
|
||||
func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.PatchRequest) error {
|
||||
body := request.GetBody()
|
||||
if body == nil {
|
||||
return errEmptyBody
|
||||
}
|
||||
|
||||
if !p.nonFirstSend {
|
||||
p.nonFirstSend = true
|
||||
|
||||
cnr, err := getContainerIDFromRequest(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objV2 := request.GetBody().GetAddress().GetObjectID()
|
||||
if objV2 == nil {
|
||||
return errors.New("missing oid")
|
||||
}
|
||||
obj := new(oid.ID)
|
||||
err = obj.ReadFromV2(*objV2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sTok *sessionSDK.Object
|
||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := MetaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := p.source.findRequestInfoWithoutACLOperationAssert(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
ctx = requestContext(ctx, reqInfo)
|
||||
}
|
||||
|
||||
return p.next.Send(ctx, request)
|
||||
}
|
||||
|
||||
func (p patchStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PatchResponse, error) {
|
||||
return p.next.CloseAndRecv(ctx)
|
||||
}
|
||||
|
||||
func (b Service) findRequestInfo(ctx context.Context, req MetaWithToken, idCnr cid.ID, op acl.Op) (info RequestInfo, err error) {
|
||||
cnr, err := b.containers.Get(ctx, idCnr) // fetch actual container
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if req.token != nil {
|
||||
currentEpoch, err := b.nm.Epoch(ctx)
|
||||
if err != nil {
|
||||
return info, errors.New("can't fetch current epoch")
|
||||
}
|
||||
if req.token.ExpiredAt(currentEpoch) {
|
||||
return info, new(apistatus.SessionTokenExpired)
|
||||
}
|
||||
if req.token.InvalidAt(currentEpoch) {
|
||||
return info, fmt.Errorf("%s: token is invalid at %d epoch)",
|
||||
invalidRequestMessage, currentEpoch)
|
||||
}
|
||||
|
||||
if !assertVerb(*req.token, op) {
|
||||
return info, errInvalidVerb
|
||||
}
|
||||
}
|
||||
|
||||
// find request role and key
|
||||
ownerID, ownerKey, err := req.RequestOwner()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
res, err := b.c.Classify(ctx, ownerID, ownerKey, idCnr, cnr.Value)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
info.basicACL = cnr.Value.BasicACL()
|
||||
info.requestRole = res.Role
|
||||
info.operation = op
|
||||
info.cnrOwner = cnr.Value.Owner()
|
||||
info.idCnr = idCnr
|
||||
|
||||
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
||||
if hasNamespace {
|
||||
info.cnrNamespace = cnrNamespace
|
||||
}
|
||||
|
||||
// it is assumed that at the moment the key will be valid,
|
||||
// otherwise the request would not pass validation
|
||||
info.senderKey = res.Key
|
||||
|
||||
// add bearer token if it is present in request
|
||||
info.bearer = req.bearer
|
||||
|
||||
info.srcRequest = req.src
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// findRequestInfoWithoutACLOperationAssert is findRequestInfo without session token verb assert.
|
||||
func (b Service) findRequestInfoWithoutACLOperationAssert(ctx context.Context, req MetaWithToken, idCnr cid.ID) (info RequestInfo, err error) {
|
||||
cnr, err := b.containers.Get(ctx, idCnr) // fetch actual container
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if req.token != nil {
|
||||
currentEpoch, err := b.nm.Epoch(ctx)
|
||||
if err != nil {
|
||||
return info, errors.New("can't fetch current epoch")
|
||||
}
|
||||
if req.token.ExpiredAt(currentEpoch) {
|
||||
return info, new(apistatus.SessionTokenExpired)
|
||||
}
|
||||
if req.token.InvalidAt(currentEpoch) {
|
||||
return info, fmt.Errorf("%s: token is invalid at %d epoch)",
|
||||
invalidRequestMessage, currentEpoch)
|
||||
}
|
||||
}
|
||||
|
||||
// find request role and key
|
||||
ownerID, ownerKey, err := req.RequestOwner()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
res, err := b.c.Classify(ctx, ownerID, ownerKey, idCnr, cnr.Value)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
info.basicACL = cnr.Value.BasicACL()
|
||||
info.requestRole = res.Role
|
||||
info.cnrOwner = cnr.Value.Owner()
|
||||
info.idCnr = idCnr
|
||||
|
||||
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
||||
if hasNamespace {
|
||||
info.cnrNamespace = cnrNamespace
|
||||
}
|
||||
|
||||
// it is assumed that at the moment the key will be valid,
|
||||
// otherwise the request would not pass validation
|
||||
info.senderKey = res.Key
|
||||
|
||||
// add bearer token if it is present in request
|
||||
info.bearer = req.bearer
|
||||
|
||||
info.srcRequest = req.src
|
||||
|
||||
return info, nil
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package v2
|
||||
|
||||
import "context"
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
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"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
var errMissingContainerID = errors.New("missing container ID")
|
||||
|
||||
func getContainerIDFromRequest(req any) (cid.ID, error) {
|
||||
var idV2 *refsV2.ContainerID
|
||||
var id cid.ID
|
||||
|
||||
switch v := req.(type) {
|
||||
case *objectV2.GetRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
case *objectV2.PutRequest:
|
||||
part, ok := v.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit)
|
||||
if !ok {
|
||||
return cid.ID{}, errors.New("can't get container ID in chunk")
|
||||
}
|
||||
|
||||
idV2 = part.GetHeader().GetContainerID()
|
||||
case *objectV2.HeadRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
case *objectV2.SearchRequest:
|
||||
idV2 = v.GetBody().GetContainerID()
|
||||
case *objectV2.DeleteRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
case *objectV2.GetRangeRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
case *objectV2.GetRangeHashRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
case *objectV2.PutSingleRequest:
|
||||
idV2 = v.GetBody().GetObject().GetHeader().GetContainerID()
|
||||
case *objectV2.PatchRequest:
|
||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
||||
default:
|
||||
return cid.ID{}, errors.New("unknown request type")
|
||||
}
|
||||
|
||||
if idV2 == nil {
|
||||
return cid.ID{}, errMissingContainerID
|
||||
}
|
||||
|
||||
return id, id.ReadFromV2(*idV2)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// originalSessionToken goes down to original request meta header and fetches
|
||||
// session token from there.
|
||||
func originalSessionToken(header *sessionV2.RequestMetaHeader) (*sessionSDK.Object, error) {
|
||||
for header.GetOrigin() != nil {
|
||||
header = header.GetOrigin()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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()
|
||||
return getObjectIDFromRefObjectID(idV2)
|
||||
}
|
||||
|
||||
func getObjectIDFromRefObjectID(idV2 *refsV2.ObjectID) (*oid.ID, error) {
|
||||
if idV2 == nil {
|
||||
return nil, errors.New("missing object ID")
|
||||
}
|
||||
|
||||
var id oid.ID
|
||||
|
||||
err := id.ReadFromV2(*idV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
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 op.
|
||||
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
||||
switch op {
|
||||
case acl.OpObjectPut:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectPatch)
|
||||
case acl.OpObjectDelete:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectDelete)
|
||||
case acl.OpObjectGet:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectGet)
|
||||
case acl.OpObjectHead:
|
||||
return tok.AssertVerb(
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectPatch,
|
||||
)
|
||||
case acl.OpObjectSearch:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete)
|
||||
case acl.OpObjectRange:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectPatch)
|
||||
case acl.OpObjectHash:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRangeHash)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
|
||||
aclsdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
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"
|
||||
sessiontest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOriginalTokens(t *testing.T) {
|
||||
sToken := sessiontest.ObjectSigned()
|
||||
bToken := bearertest.Token()
|
||||
|
||||
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, bToken.Sign(*pk))
|
||||
|
||||
var bTokenV2 acl.BearerToken
|
||||
bToken.WriteToV2(&bTokenV2)
|
||||
// This line is needed because SDK uses some custom format for
|
||||
// reserved filters, so `cid.ID` is not converted to string immediately.
|
||||
require.NoError(t, bToken.ReadFromV2(bTokenV2))
|
||||
|
||||
var sTokenV2 session.Token
|
||||
sToken.WriteToV2(&sTokenV2)
|
||||
|
||||
for i := range 10 {
|
||||
metaHeaders := testGenerateMetaHeader(uint32(i), &bTokenV2, &sTokenV2)
|
||||
res, err := originalSessionToken(metaHeaders)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sToken, res, i)
|
||||
|
||||
bTok, err := originalBearerToken(metaHeaders)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &bToken, bTok, i)
|
||||
}
|
||||
}
|
||||
|
||||
func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Token) *session.RequestMetaHeader {
|
||||
metaHeader := new(session.RequestMetaHeader)
|
||||
metaHeader.SetBearerToken(b)
|
||||
metaHeader.SetSessionToken(s)
|
||||
|
||||
for range depth {
|
||||
link := metaHeader
|
||||
metaHeader = new(session.RequestMetaHeader)
|
||||
metaHeader.SetOrigin(link)
|
||||
}
|
||||
|
||||
return metaHeader
|
||||
}
|
||||
|
||||
func TestIsVerbCompatible(t *testing.T) {
|
||||
// Source: https://nspcc.ru/upload/frostfs-spec-latest.pdf#page=28
|
||||
table := map[aclsdk.Op][]sessionSDK.ObjectVerb{
|
||||
aclsdk.OpObjectPut: {sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete},
|
||||
aclsdk.OpObjectDelete: {sessionSDK.VerbObjectDelete},
|
||||
aclsdk.OpObjectGet: {sessionSDK.VerbObjectGet},
|
||||
aclsdk.OpObjectHead: {
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
},
|
||||
aclsdk.OpObjectRange: {sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash},
|
||||
aclsdk.OpObjectHash: {sessionSDK.VerbObjectRangeHash},
|
||||
aclsdk.OpObjectSearch: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete},
|
||||
}
|
||||
|
||||
verbs := []sessionSDK.ObjectVerb{
|
||||
sessionSDK.VerbObjectPut,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectSearch,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
)
|
||||
|
||||
type RequestContextKeyT struct{}
|
||||
|
||||
var RequestContextKey = RequestContextKeyT{}
|
||||
|
||||
// RequestContext is a context passed between middleware handlers.
|
||||
type RequestContext struct {
|
||||
Namespace string
|
||||
|
||||
SenderKey []byte
|
||||
|
||||
ContainerOwner user.ID
|
||||
|
||||
Role acl.Role
|
||||
|
||||
BearerToken *bearer.Token
|
||||
}
|
Loading…
Add table
Reference in a new issue