object: Introduce soft ape checks #986

Merged
fyrchik merged 3 commits from aarifullin/frostfs-node:fix/strict_ape_checks into master 2024-02-28 19:05:58 +00:00
6 changed files with 176 additions and 108 deletions
Showing only changes of commit e9363a5094 - Show all commits

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

You are always using NOT operator with result of this function and with field StrictAPECheck. How about to replace it with SoftAPECheck?

You are always using NOT operator with result of this function and with field `StrictAPECheck`. How about to replace it with `SoftAPECheck`?

Replaced to SoftAPECheck. Could u check please if I've left mistakes. Also, I fixed commit message

Replaced to `SoftAPECheck`. Could u check please if I've left mistakes. Also, I fixed commit message
}
// 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
} }