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"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"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"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"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.
|
||||
//
|
||||
// nolint: funlen
|
||||
func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
||||
basicACL := reqInfo.BasicACL()
|
||||
if !basicACL.Extendable() {
|
||||
|
@ -154,6 +153,44 @@ func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
|||
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 = append(hdrSrcOpts,
|
||||
|
@ -175,34 +212,9 @@ func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
|
|||
|
||||
hdrSrc, err := eaclV2.NewMessageHeaderSource(hdrSrcOpts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse headers: %w", err)
|
||||
return nil, fmt.Errorf("can't parse headers: %w", err)
|
||||
}
|
||||
|
||||
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
|
||||
return hdrSrc, nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
// nolint: funlen
|
||||
func (h *cfg) readObjectHeaders(dst *headerSource) error {
|
||||
switch m := h.msg.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||
case requestXHeaderSource:
|
||||
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(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 h.readObjectHeadersFromRequestXHeaderSource(m, dst)
|
||||
case responseXHeaderSource:
|
||||
switch resp := m.resp.(type) {
|
||||
default:
|
||||
objectHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||
return h.readObjectHeadersResponseXHeaderSource(m, dst)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
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
|
||||
}
|
||||
|
||||
dst.objectHeaders = headersFromObject(object.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
case *objectV2.HeadResponse:
|
||||
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)
|
||||
|
||||
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)
|
||||
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)}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -443,7 +443,6 @@ func (b Service) GetRangeHash(
|
|||
return b.next.GetRangeHash(ctx, request)
|
||||
}
|
||||
|
||||
// nolint: funlen
|
||||
func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
||||
body := request.GetBody()
|
||||
if body == nil {
|
||||
|
@ -482,27 +481,9 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
|
|||
}
|
||||
|
||||
var sTok *sessionSDK.Object
|
||||
|
||||
if tokV2 := request.GetMetaHeader().GetSessionToken(); tokV2 != nil {
|
||||
sTok = new(sessionSDK.Object)
|
||||
|
||||
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
|
||||
}
|
||||
sTok, err = p.readSessionToken(cnr, obj, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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.
|
||||
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
||||
//nolint:exhaustive
|
||||
switch op {
|
||||
case acl.OpObjectPut:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete)
|
||||
|
|
Loading…
Reference in a new issue