[#1246] object/acl: Return more concise description for eACL errors

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-03-18 14:04:32 +03:00 committed by Alex Vanin
parent 800d01e28c
commit 2848001dfb
4 changed files with 59 additions and 43 deletions

View file

@ -57,6 +57,15 @@ type Checker struct {
state netmap.State state netmap.State
} }
// Various EACL check errors.
var (
errEACLDeniedByRule = errors.New("denied by rule")
errBearerExpired = errors.New("bearer token has expired")
errBearerInvalidSignature = errors.New("bearer token has invalid signature")
errBearerNotSignedByOwner = errors.New("bearer token is not signed by the container owner")
errBearerInvalidOwner = errors.New("bearer token owner differs from the request sender")
)
// NewChecker creates Checker. // NewChecker creates Checker.
// Panics if at least one of the parameter is nil. // Panics if at least one of the parameter is nil.
func NewChecker(prm *CheckerPrm) *Checker { func NewChecker(prm *CheckerPrm) *Checker {
@ -124,9 +133,9 @@ func (c *Checker) StickyBitCheck(info v2.RequestInfo, owner *owner.ID) bool {
} }
// CheckEACL is a main check function for extended ACL. // CheckEACL is a main check function for extended ACL.
func (c *Checker) CheckEACL(msg interface{}, reqInfo v2.RequestInfo) bool { func (c *Checker) CheckEACL(msg interface{}, reqInfo v2.RequestInfo) error {
if basicACLHelper(reqInfo.BasicACL()).Final() { if basicACLHelper(reqInfo.BasicACL()).Final() {
return true return nil
} }
// if bearer token is not allowed, then ignore it // if bearer token is not allowed, then ignore it
@ -142,15 +151,18 @@ func (c *Checker) CheckEACL(msg interface{}, reqInfo v2.RequestInfo) bool {
if reqInfo.Bearer().Empty() { if reqInfo.Bearer().Empty() {
table, err = c.eaclSrc.GetEACL(reqInfo.ContainerID()) table, err = c.eaclSrc.GetEACL(reqInfo.ContainerID())
if err != nil { if err != nil {
return errors.Is(err, container.ErrEACLNotFound) if errors.Is(err, container.ErrEACLNotFound) {
return nil
}
return err
} }
} else { } else {
table = reqInfo.Bearer().EACLTable() table = reqInfo.Bearer().EACLTable()
} }
// if bearer token is not present, isValidBearer returns true // if bearer token is not present, isValidBearer returns true
if !isValidBearer(reqInfo, c.state) { if err := isValidBearer(reqInfo, c.state); err != nil {
return false return err
} }
hdrSrcOpts := make([]eaclV2.Option, 0, 3) hdrSrcOpts := make([]eaclV2.Option, 0, 3)
@ -186,36 +198,39 @@ func (c *Checker) CheckEACL(msg interface{}, reqInfo v2.RequestInfo) bool {
WithEACLTable(table), WithEACLTable(table),
) )
return action == eaclSDK.ActionAllow if action != eaclSDK.ActionAllow {
return errEACLDeniedByRule
}
return nil
} }
// isValidBearer returns true if bearer token correctly signed by authorized // isValidBearer checks whether bearer token was correctly signed by authorized
// entity. This method might be defined on whole ACL service because it will // entity. This method might be defined on whole ACL service because it will
// require fetching current epoch to check lifetime. // require fetching current epoch to check lifetime.
func isValidBearer(reqInfo v2.RequestInfo, st netmap.State) bool { func isValidBearer(reqInfo v2.RequestInfo, st netmap.State) error {
token := reqInfo.Bearer() token := reqInfo.Bearer()
// 0. Check if bearer token is present in reqInfo. It might be non nil // 0. Check if bearer token is present in reqInfo. It might be non nil
// empty structure. // empty structure.
if token == nil || token.Empty() { if token == nil || token.Empty() {
return true return nil
} }
// 1. First check token lifetime. Simplest verification. // 1. First check token lifetime. Simplest verification.
if !isValidLifetime(token, st.CurrentEpoch()) { if !isValidLifetime(token, st.CurrentEpoch()) {
return false return errBearerExpired
} }
// 2. Then check if bearer token is signed correctly. // 2. Then check if bearer token is signed correctly.
if err := token.VerifySignature(); err != nil { if err := token.VerifySignature(); err != nil {
return false // invalid signature return errBearerInvalidSignature
} }
// 3. Then check if container owner signed this token. // 3. Then check if container owner signed this token.
tokenIssuerKey := unmarshalPublicKey(token.Signature().Key()) tokenIssuerKey := unmarshalPublicKey(token.Signature().Key())
if !isOwnerFromKey(reqInfo.ContainerOwner(), tokenIssuerKey) { if !isOwnerFromKey(reqInfo.ContainerOwner(), tokenIssuerKey) {
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again // TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
return false return errBearerNotSignedByOwner
} }
// 4. Then check if request sender has rights to use this token. // 4. Then check if request sender has rights to use this token.
@ -224,11 +239,11 @@ func isValidBearer(reqInfo v2.RequestInfo, st netmap.State) bool {
requestSenderKey := unmarshalPublicKey(reqInfo.SenderKey()) requestSenderKey := unmarshalPublicKey(reqInfo.SenderKey())
if !isOwnerFromKey(tokenOwnerField, requestSenderKey) { if !isOwnerFromKey(tokenOwnerField, requestSenderKey) {
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again // TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
return false return errBearerInvalidOwner
} }
} }
return true return nil
} }
func isValidLifetime(t *bearerSDK.BearerToken, epoch uint64) bool { func isValidLifetime(t *bearerSDK.BearerToken, epoch uint64) bool {

View file

@ -17,18 +17,19 @@ var (
ErrInvalidVerb = errors.New("session token verb is invalid") ErrInvalidVerb = errors.New("session token verb is invalid")
) )
const accessDeniedReasonFmt = "access to operation %v is denied by %s check" const accessDeniedACLReasonFmt = "access to operation %s is denied by basic ACL check"
const accessDeniedEACLReasonFmt = "access to operation %s is denied by extended ACL check: %v"
func basicACLErr(info RequestInfo) error { func basicACLErr(info RequestInfo) error {
var errAccessDenied apistatus.ObjectAccessDenied var errAccessDenied apistatus.ObjectAccessDenied
errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedReasonFmt, info.operation, "basic ACL")) errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedACLReasonFmt, info.operation))
return errAccessDenied return errAccessDenied
} }
func eACLErr(info RequestInfo) error { func eACLErr(info RequestInfo, err error) error {
var errAccessDenied apistatus.ObjectAccessDenied var errAccessDenied apistatus.ObjectAccessDenied
errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedReasonFmt, info.operation, "extended ACL")) errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedEACLReasonFmt, info.operation, err))
return errAccessDenied return errAccessDenied
} }

View file

@ -131,8 +131,8 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo) return basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo) return eACLErr(reqInfo, err)
} }
return b.next.Get(request, &getStreamBasicChecker{ return b.next.Get(request, &getStreamBasicChecker{
@ -178,14 +178,14 @@ func (b Service) Head(
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo) return nil, basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo) return nil, eACLErr(reqInfo, err)
} }
resp, err := b.next.Head(ctx, request) resp, err := b.next.Head(ctx, request)
if err == nil { if err == nil {
if !b.checker.CheckEACL(resp, reqInfo) { if err = b.checker.CheckEACL(resp, reqInfo); err != nil {
err = eACLErr(reqInfo) err = eACLErr(reqInfo, err)
} }
} }
@ -216,8 +216,8 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo) return basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo) return eACLErr(reqInfo, err)
} }
return b.next.Search(request, &searchStreamBasicChecker{ return b.next.Search(request, &searchStreamBasicChecker{
@ -254,8 +254,8 @@ func (b Service) Delete(
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo) return nil, basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo) return nil, eACLErr(reqInfo, err)
} }
return b.next.Delete(ctx, request) return b.next.Delete(ctx, request)
@ -286,8 +286,8 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo) return basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo) return eACLErr(reqInfo, err)
} }
return b.next.GetRange(request, &rangeStreamBasicChecker{ return b.next.GetRange(request, &rangeStreamBasicChecker{
@ -324,8 +324,8 @@ func (b Service) GetRangeHash(
if !b.checker.CheckBasicACL(reqInfo) { if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo) return nil, basicACLErr(reqInfo)
} else if !b.checker.CheckEACL(request, reqInfo) { } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo) return nil, eACLErr(reqInfo, err)
} }
return b.next.GetRangeHash(ctx, request) return b.next.GetRangeHash(ctx, request)
@ -368,8 +368,8 @@ func (p putStreamBasicChecker) Send(request *objectV2.PutRequest) error {
if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, ownerID) { if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, ownerID) {
return basicACLErr(reqInfo) return basicACLErr(reqInfo)
} else if !p.source.checker.CheckEACL(request, reqInfo) { } else if err := p.source.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo) return eACLErr(reqInfo, err)
} }
} }
@ -382,8 +382,8 @@ func (p putStreamBasicChecker) CloseAndRecv() (*objectV2.PutResponse, error) {
func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error { func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
if _, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok { if _, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
if !g.checker.CheckEACL(resp, g.info) { if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info) return eACLErr(g.info, err)
} }
} }
@ -391,16 +391,16 @@ func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
} }
func (g *rangeStreamBasicChecker) Send(resp *objectV2.GetRangeResponse) error { func (g *rangeStreamBasicChecker) Send(resp *objectV2.GetRangeResponse) error {
if !g.checker.CheckEACL(resp, g.info) { if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info) return eACLErr(g.info, err)
} }
return g.GetObjectRangeStream.Send(resp) return g.GetObjectRangeStream.Send(resp)
} }
func (g *searchStreamBasicChecker) Send(resp *objectV2.SearchResponse) error { func (g *searchStreamBasicChecker) Send(resp *objectV2.SearchResponse) error {
if !g.checker.CheckEACL(resp, g.info) { if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info) return eACLErr(g.info, err)
} }
return g.SearchStream.Send(resp) return g.SearchStream.Send(resp)

View file

@ -10,9 +10,9 @@ type ACLChecker interface {
// CheckBasicACL must return true only if request // CheckBasicACL must return true only if request
// passes basic ACL validation. // passes basic ACL validation.
CheckBasicACL(RequestInfo) bool CheckBasicACL(RequestInfo) bool
// CheckEACL must return true only if request // CheckEACL must return non-nil error if request
// passes extended ACL validation. // doesn't pass extended ACL validation.
CheckEACL(interface{}, RequestInfo) bool CheckEACL(interface{}, RequestInfo) error
// StickyBitCheck must return true only if sticky bit // StickyBitCheck must return true only if sticky bit
// is disabled or enabled but request contains correct // is disabled or enabled but request contains correct
// owner field. // owner field.