[#986] object: Introduce soft ape checks

* Soft APE check means that APE should allow request even
  it gets status NoRuleFound for a request. Otherwise,
  it is interpreted as Deny.
* Soft APE check is performed if basic ACL mask is not set.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-02-14 14:14:07 +03:00 committed by Evgenii Stratonikov
parent bc9dbb26ec
commit 7cc368e188
6 changed files with 176 additions and 108 deletions

View file

@ -104,6 +104,13 @@ func (r RequestInfo) RequestRole() acl.Role {
return r.requestRole return r.requestRole
} }
// IsSoftAPECheck states if APE should perform soft checks.
// Soft APE check allows a request if CheckAPE returns NoRuleFound for it,
// otherwise it denies the request.
func (r RequestInfo) IsSoftAPECheck() bool {
return r.BasicACL().Bits() != 0
}
// MetaWithToken groups session and bearer tokens, // MetaWithToken groups session and bearer tokens,
// verification header and raw API request. // verification header and raw API request.
type MetaWithToken struct { type MetaWithToken struct {

View file

@ -113,9 +113,10 @@ type wrappedGetObjectStream struct {
func (w *wrappedGetObjectStream) Context() context.Context { func (w *wrappedGetObjectStream) Context() context.Context {
return context.WithValue(w.GetObjectStream.Context(), object.RequestContextKey, &object.RequestContext{ return context.WithValue(w.GetObjectStream.Context(), object.RequestContextKey, &object.RequestContext{
Namespace: w.requestInfo.ContainerNamespace(), Namespace: w.requestInfo.ContainerNamespace(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
}) })
} }
@ -136,9 +137,10 @@ type wrappedRangeStream struct {
func (w *wrappedRangeStream) Context() context.Context { func (w *wrappedRangeStream) Context() context.Context {
return context.WithValue(w.GetObjectRangeStream.Context(), object.RequestContextKey, &object.RequestContext{ return context.WithValue(w.GetObjectRangeStream.Context(), object.RequestContextKey, &object.RequestContext{
Namespace: w.requestInfo.ContainerNamespace(), Namespace: w.requestInfo.ContainerNamespace(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
}) })
} }
@ -159,9 +161,10 @@ type wrappedSearchStream struct {
func (w *wrappedSearchStream) Context() context.Context { func (w *wrappedSearchStream) Context() context.Context {
return context.WithValue(w.SearchStream.Context(), object.RequestContextKey, &object.RequestContext{ return context.WithValue(w.SearchStream.Context(), object.RequestContextKey, &object.RequestContext{
Namespace: w.requestInfo.ContainerNamespace(), Namespace: w.requestInfo.ContainerNamespace(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
}) })
} }
@ -216,10 +219,12 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return basicACLErr(reqInfo)
return eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
} }
return b.next.Get(request, &getStreamBasicChecker{ return b.next.Get(request, &getStreamBasicChecker{
@ -283,10 +288,12 @@ func (b Service) Head(
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return nil, basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return nil, basicACLErr(reqInfo)
return nil, eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
} }
resp, err := b.next.Head(requestContext(ctx, reqInfo), request) resp, err := b.next.Head(requestContext(ctx, reqInfo), request)
@ -334,10 +341,12 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr
return err return err
} }
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return basicACLErr(reqInfo)
return eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
} }
return b.next.Search(request, &searchStreamBasicChecker{ return b.next.Search(request, &searchStreamBasicChecker{
@ -392,10 +401,12 @@ func (b Service) Delete(
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return nil, basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return nil, basicACLErr(reqInfo)
return nil, eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
} }
return b.next.Delete(requestContext(ctx, reqInfo), request) return b.next.Delete(requestContext(ctx, reqInfo), request)
@ -443,10 +454,12 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return basicACLErr(reqInfo)
return eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
} }
return b.next.GetRange(request, &rangeStreamBasicChecker{ return b.next.GetRange(request, &rangeStreamBasicChecker{
@ -458,9 +471,10 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context { func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context {
return context.WithValue(ctx, object.RequestContextKey, &object.RequestContext{ return context.WithValue(ctx, object.RequestContextKey, &object.RequestContext{
Namespace: reqInfo.ContainerNamespace(), Namespace: reqInfo.ContainerNamespace(),
SenderKey: reqInfo.SenderKey(), SenderKey: reqInfo.SenderKey(),
Role: reqInfo.RequestRole(), Role: reqInfo.RequestRole(),
SoftAPECheck: reqInfo.IsSoftAPECheck(),
}) })
} }
@ -509,10 +523,12 @@ func (b Service) GetRangeHash(
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) { if reqInfo.IsSoftAPECheck() {
return nil, basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) {
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil { return nil, basicACLErr(reqInfo)
return nil, eACLErr(reqInfo, err) } else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
} }
return b.next.GetRangeHash(requestContext(ctx, reqInfo), request) return b.next.GetRangeHash(requestContext(ctx, reqInfo), request)
@ -566,12 +582,13 @@ func (b Service) PutSingle(ctx context.Context, request *objectV2.PutSingleReque
reqInfo.obj = obj reqInfo.obj = obj
if !b.checker.CheckBasicACL(reqInfo) || !b.checker.StickyBitCheck(reqInfo, idOwner) { if reqInfo.IsSoftAPECheck() {
return nil, basicACLErr(reqInfo) if !b.checker.CheckBasicACL(reqInfo) || !b.checker.StickyBitCheck(reqInfo, idOwner) {
} return nil, basicACLErr(reqInfo)
}
if err := b.checker.CheckEACL(request, reqInfo); err != nil { if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err) return nil, eACLErr(reqInfo, err)
}
} }
return b.next.PutSingle(requestContext(ctx, reqInfo), request) return b.next.PutSingle(requestContext(ctx, reqInfo), request)
@ -639,8 +656,10 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
reqInfo.obj = obj reqInfo.obj = obj
if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, idOwner) { if reqInfo.IsSoftAPECheck() {
return basicACLErr(reqInfo) if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, idOwner) {
return basicACLErr(reqInfo)
}
} }
ctx = requestContext(ctx, reqInfo) ctx = requestContext(ctx, reqInfo)

View file

@ -45,6 +45,9 @@ type Prm struct {
// An encoded sender's public key string. // An encoded sender's public key string.
SenderKey string SenderKey string
// If SoftAPECheck is set to true, then NoRuleFound is interpreted as allow.
SoftAPECheck bool
} }
var errMissingOID = fmt.Errorf("object ID is not set") var errMissingOID = fmt.Errorf("object ID is not set")
@ -63,9 +66,9 @@ func (c *checkerImpl) CheckAPE(ctx context.Context, prm Prm) error {
return err return err
} }
if !ruleFound || status == apechain.Allow { if !ruleFound && prm.SoftAPECheck || status == apechain.Allow {
return nil return nil
} }
return fmt.Errorf("found denying rule for %s: %s", prm.Method, status) return fmt.Errorf("method %s: %s", prm.Method, status)
} }

View file

@ -165,11 +165,29 @@ func TestAPECheck(t *testing.T) {
container: containerID, container: containerID,
object: stringPtr(objectID), object: stringPtr(objectID),
methods: methodsRequiredOID, methods: methodsRequiredOID,
containerRules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: methodsRequiredOID},
Resources: chain.Resources{
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
},
},
},
}, },
{ {
name: "oid optional requests are allowed", name: "oid optional requests are allowed",
container: containerID, container: containerID,
methods: methodsOptionalOID, methods: methodsOptionalOID,
containerRules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: methodsOptionalOID},
Resources: chain.Resources{
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
},
},
},
}, },
{ {
name: "oid required requests are denied", name: "oid required requests are denied",

View file

@ -60,9 +60,13 @@ type getStreamBasicChecker struct {
apeChecker Checker apeChecker Checker
namespace string
senderKey []byte senderKey []byte
role string role string
softAPECheck bool
} }
func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error { func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
@ -73,12 +77,14 @@ func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
} }
prm := Prm{ prm := Prm{
Container: cnrID, Namespace: g.namespace,
Object: objID, Container: cnrID,
Header: partInit.GetHeader(), Object: objID,
Method: nativeschema.MethodGetObject, Header: partInit.GetHeader(),
SenderKey: hex.EncodeToString(g.senderKey), Method: nativeschema.MethodGetObject,
Role: g.role, SenderKey: hex.EncodeToString(g.senderKey),
Role: g.role,
SoftAPECheck: g.softAPECheck,
} }
if err := g.apeChecker.CheckAPE(g.Context(), prm); err != nil { if err := g.apeChecker.CheckAPE(g.Context(), prm); err != nil {
@ -112,12 +118,13 @@ func (c *Service) Get(request *objectV2.GetRequest, stream objectSvc.GetObjectSt
} }
err = c.apeChecker.CheckAPE(stream.Context(), Prm{ err = c.apeChecker.CheckAPE(stream.Context(), Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Method: nativeschema.MethodGetObject, Method: nativeschema.MethodGetObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return toStatusErr(err) return toStatusErr(err)
@ -126,6 +133,10 @@ func (c *Service) Get(request *objectV2.GetRequest, stream objectSvc.GetObjectSt
return c.next.Get(request, &getStreamBasicChecker{ return c.next.Get(request, &getStreamBasicChecker{
GetObjectStream: stream, GetObjectStream: stream,
apeChecker: c.apeChecker, apeChecker: c.apeChecker,
namespace: reqCtx.Namespace,
senderKey: reqCtx.SenderKey,
role: nativeSchemaRole(reqCtx.Role),
softAPECheck: reqCtx.SoftAPECheck,
}) })
} }
@ -148,13 +159,14 @@ func (p *putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutR
} }
prm := Prm{ prm := Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Header: partInit.GetHeader(), Header: partInit.GetHeader(),
Method: nativeschema.MethodPutObject, Method: nativeschema.MethodPutObject,
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SoftAPECheck: reqCtx.SoftAPECheck,
} }
if err := p.apeChecker.CheckAPE(ctx, prm); err != nil { if err := p.apeChecker.CheckAPE(ctx, prm); err != nil {
@ -190,12 +202,13 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
} }
err = c.apeChecker.CheckAPE(ctx, Prm{ err = c.apeChecker.CheckAPE(ctx, Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Method: nativeschema.MethodHeadObject, Method: nativeschema.MethodHeadObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -226,13 +239,14 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
} }
err = c.apeChecker.CheckAPE(ctx, Prm{ err = c.apeChecker.CheckAPE(ctx, Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Header: header, Header: header,
Method: nativeschema.MethodHeadObject, Method: nativeschema.MethodHeadObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -254,11 +268,12 @@ func (c *Service) Search(request *objectV2.SearchRequest, stream objectSvc.Searc
} }
err = c.apeChecker.CheckAPE(stream.Context(), Prm{ err = c.apeChecker.CheckAPE(stream.Context(), Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Method: nativeschema.MethodSearchObject, Method: nativeschema.MethodSearchObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return toStatusErr(err) return toStatusErr(err)
@ -279,12 +294,13 @@ func (c *Service) Delete(ctx context.Context, request *objectV2.DeleteRequest) (
} }
err = c.apeChecker.CheckAPE(ctx, Prm{ err = c.apeChecker.CheckAPE(ctx, Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Method: nativeschema.MethodDeleteObject, Method: nativeschema.MethodDeleteObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -310,12 +326,13 @@ func (c *Service) GetRange(request *objectV2.GetRangeRequest, stream objectSvc.G
} }
err = c.apeChecker.CheckAPE(stream.Context(), Prm{ err = c.apeChecker.CheckAPE(stream.Context(), Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Method: nativeschema.MethodRangeObject, Method: nativeschema.MethodRangeObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
}) })
if err != nil { if err != nil {
return toStatusErr(err) return toStatusErr(err)
@ -336,12 +353,13 @@ func (c *Service) GetRangeHash(ctx context.Context, request *objectV2.GetRangeHa
} }
prm := Prm{ prm := Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Method: nativeschema.MethodHashObject, Method: nativeschema.MethodHashObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
} }
if err = c.apeChecker.CheckAPE(ctx, prm); err != nil { if err = c.apeChecker.CheckAPE(ctx, prm); err != nil {
@ -371,13 +389,14 @@ func (c *Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequ
} }
prm := Prm{ prm := Prm{
Namespace: reqCtx.Namespace, Namespace: reqCtx.Namespace,
Container: cnrID, Container: cnrID,
Object: objID, Object: objID,
Header: request.GetBody().GetObject().GetHeader(), Header: request.GetBody().GetObject().GetHeader(),
Method: nativeschema.MethodPutObject, Method: nativeschema.MethodPutObject,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
SoftAPECheck: reqCtx.SoftAPECheck,
} }
if err = c.apeChecker.CheckAPE(ctx, prm); err != nil { if err = c.apeChecker.CheckAPE(ctx, prm); err != nil {

View file

@ -13,4 +13,6 @@ type RequestContext struct {
SenderKey []byte SenderKey []byte
Role acl.Role Role acl.Role
SoftAPECheck bool
} }