Refactor ACL service #207
4 changed files with 156 additions and 129 deletions
|
@ -14,6 +14,7 @@ import (
|
||||||
bearerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
bearerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
@ -118,8 +119,6 @@ func (c *Checker) StickyBitCheck(info v2.RequestInfo, owner user.ID) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckEACL is a main check function for extended ACL.
|
// CheckEACL is a main check function for extended ACL.
|
||||||
//
|
|
||||||
// nolint: funlen
|
|
||||||
func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
||||||
basicACL := reqInfo.BasicACL()
|
basicACL := reqInfo.BasicACL()
|
||||||
if !basicACL.Extendable() {
|
if !basicACL.Extendable() {
|
||||||
|
@ -154,6 +153,44 @@ func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hdrSrc, err := c.getHeaderSource(cnr, msg, reqInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
eaclRole := getRole(reqInfo)
|
||||||
|
|
||||||
|
action, _ := c.validator.CalculateAction(new(eaclSDK.ValidationUnit).
|
||||||
|
WithRole(eaclRole).
|
||||||
|
WithOperation(eaclSDK.Operation(reqInfo.Operation())).
|
||||||
|
WithContainerID(&cnr).
|
||||||
|
WithSenderKey(reqInfo.SenderKey()).
|
||||||
|
WithHeaderSource(hdrSrc).
|
||||||
|
WithEACLTable(&table),
|
||||||
|
)
|
||||||
|
|
||||||
|
if action != eaclSDK.ActionAllow {
|
||||||
|
return errEACLDeniedByRule
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRole(reqInfo v2.RequestInfo) eaclSDK.Role {
|
||||||
|
var eaclRole eaclSDK.Role
|
||||||
|
switch op := reqInfo.RequestRole(); op {
|
||||||
|
default:
|
||||||
|
eaclRole = eaclSDK.Role(op)
|
||||||
|
case acl.RoleOwner:
|
||||||
|
eaclRole = eaclSDK.RoleUser
|
||||||
|
case acl.RoleInnerRing, acl.RoleContainer:
|
||||||
|
eaclRole = eaclSDK.RoleSystem
|
||||||
|
case acl.RoleOthers:
|
||||||
|
eaclRole = eaclSDK.RoleOthers
|
||||||
|
}
|
||||||
|
return eaclRole
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) getHeaderSource(cnr cid.ID, msg any, reqInfo v2.RequestInfo) (eaclSDK.TypedHeaderSource, error) {
|
||||||
hdrSrcOpts := make([]eaclV2.Option, 0, 3)
|
hdrSrcOpts := make([]eaclV2.Option, 0, 3)
|
||||||
|
|
||||||
hdrSrcOpts = append(hdrSrcOpts,
|
hdrSrcOpts = append(hdrSrcOpts,
|
||||||
|
@ -175,34 +212,9 @@ func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
||||||
|
|
||||||
hdrSrc, err := eaclV2.NewMessageHeaderSource(hdrSrcOpts...)
|
hdrSrc, err := eaclV2.NewMessageHeaderSource(hdrSrcOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't parse headers: %w", err)
|
return nil, fmt.Errorf("can't parse headers: %w", err)
|
||||||
}
|
}
|
||||||
|
return hdrSrc, nil
|
||||||
var eaclRole eaclSDK.Role
|
|
||||||
switch op := reqInfo.RequestRole(); op {
|
|
||||||
default:
|
|
||||||
eaclRole = eaclSDK.Role(op)
|
|
||||||
case acl.RoleOwner:
|
|
||||||
eaclRole = eaclSDK.RoleUser
|
|
||||||
case acl.RoleInnerRing, acl.RoleContainer:
|
|
||||||
eaclRole = eaclSDK.RoleSystem
|
|
||||||
case acl.RoleOthers:
|
|
||||||
eaclRole = eaclSDK.RoleOthers
|
|
||||||
}
|
|
||||||
|
|
||||||
action, _ := c.validator.CalculateAction(new(eaclSDK.ValidationUnit).
|
|
||||||
WithRole(eaclRole).
|
|
||||||
WithOperation(eaclSDK.Operation(reqInfo.Operation())).
|
|
||||||
WithContainerID(&cnr).
|
|
||||||
WithSenderKey(reqInfo.SenderKey()).
|
|
||||||
WithHeaderSource(hdrSrc).
|
|
||||||
WithEACLTable(&table),
|
|
||||||
)
|
|
||||||
|
|
||||||
if action != eaclSDK.ActionAllow {
|
|
||||||
return errEACLDeniedByRule
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidBearer checks whether bearer token was correctly signed by authorized
|
// isValidBearer checks whether bearer token was correctly signed by authorized
|
||||||
|
|
|
@ -101,96 +101,103 @@ func requestHeaders(msg xHeaderSource) []eaclSDK.Header {
|
||||||
|
|
||||||
var errMissingOID = errors.New("object ID is missing")
|
var errMissingOID = errors.New("object ID is missing")
|
||||||
|
|
||||||
// nolint: funlen
|
|
||||||
func (h *cfg) readObjectHeaders(dst *headerSource) error {
|
func (h *cfg) readObjectHeaders(dst *headerSource) error {
|
||||||
switch m := h.msg.(type) {
|
switch m := h.msg.(type) {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||||
case requestXHeaderSource:
|
case requestXHeaderSource:
|
||||||
switch req := m.req.(type) {
|
return h.readObjectHeadersFromRequestXHeaderSource(m, dst)
|
||||||
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(object.NewFromV2(oV2), 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)}
|
|
||||||
}
|
|
||||||
case responseXHeaderSource:
|
case responseXHeaderSource:
|
||||||
switch resp := m.resp.(type) {
|
return h.readObjectHeadersResponseXHeaderSource(m, dst)
|
||||||
default:
|
}
|
||||||
objectHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
}
|
||||||
|
|
||||||
dst.objectHeaders = objectHeaders
|
func (h *cfg) readObjectHeadersFromRequestXHeaderSource(m requestXHeaderSource, dst *headerSource) error {
|
||||||
dst.incompleteObjectHeaders = !completed
|
switch req := m.req.(type) {
|
||||||
case *objectV2.GetResponse:
|
case
|
||||||
if v, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
|
*objectV2.GetRequest,
|
||||||
oV2 := new(objectV2.Object)
|
*objectV2.HeadRequest:
|
||||||
oV2.SetObjectID(v.GetObjectID())
|
if h.obj == nil {
|
||||||
oV2.SetHeader(v.GetHeader())
|
return errMissingOID
|
||||||
|
}
|
||||||
|
|
||||||
dst.objectHeaders = headersFromObject(object.NewFromV2(oV2), h.cnr, h.obj)
|
objHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||||
}
|
|
||||||
case *objectV2.HeadResponse:
|
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 := new(objectV2.Object)
|
||||||
|
oV2.SetObjectID(v.GetObjectID())
|
||||||
var hdr *objectV2.Header
|
oV2.SetHeader(v.GetHeader())
|
||||||
|
|
||||||
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(object.NewFromV2(oV2), h.cnr, h.obj)
|
dst.objectHeaders = headersFromObject(object.NewFromV2(oV2), 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(object.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(object.NewFromV2(oV2), h.cnr, h.obj)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -443,7 +443,6 @@ func (b Service) GetRangeHash(
|
||||||
return b.next.GetRangeHash(ctx, request)
|
return b.next.GetRangeHash(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: funlen
|
|
||||||
func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
||||||
body := request.GetBody()
|
body := request.GetBody()
|
||||||
if body == nil {
|
if body == nil {
|
||||||
|
@ -482,27 +481,9 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
|
||||||
}
|
}
|
||||||
|
|
||||||
var sTok *sessionSDK.Object
|
var sTok *sessionSDK.Object
|
||||||
|
sTok, err = p.readSessionToken(cnr, obj, request)
|
||||||
if tokV2 := request.GetMetaHeader().GetSessionToken(); tokV2 != nil {
|
if err != nil {
|
||||||
sTok = new(sessionSDK.Object)
|
return err
|
||||||
|
|
||||||
err = sTok.ReadFromV2(*tokV2)
|
|
||||||
if err != nil {
|
|
||||||
return 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 err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
bTok, err := originalBearerToken(request.GetMetaHeader())
|
||||||
|
@ -534,6 +515,34 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
|
||||||
return p.next.Send(ctx, request)
|
return p.next.Send(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p putStreamBasicChecker) readSessionToken(cnr cid.ID, obj *oid.ID, request *objectV2.PutRequest) (*sessionSDK.Object, error) {
|
||||||
|
var sTok *sessionSDK.Object
|
||||||
|
|
||||||
|
if tokV2 := request.GetMetaHeader().GetSessionToken(); 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) {
|
func (p putStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PutResponse, error) {
|
||||||
return p.next.CloseAndRecv(ctx)
|
return p.next.CloseAndRecv(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,6 @@ func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
|
||||||
|
|
||||||
// assertVerb checks that token verb corresponds to op.
|
// assertVerb checks that token verb corresponds to op.
|
||||||
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
||||||
//nolint:exhaustive
|
|
||||||
switch op {
|
switch op {
|
||||||
case acl.OpObjectPut:
|
case acl.OpObjectPut:
|
||||||
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete)
|
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete)
|
||||||
|
|
Loading…
Reference in a new issue