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
9 changed files with 340 additions and 112 deletions

View file

@ -162,7 +162,7 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
return nil, err return nil, err
} }
if !found || s == apechain.Allow { if found && s == apechain.Allow {
return ac.next.List(ctx, req) return ac.next.List(ctx, req)
} }
@ -207,7 +207,7 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
return nil, err return nil, err
} }
if !found || s == apechain.Allow { if found && s == apechain.Allow {
return ac.next.Put(ctx, req) return ac.next.Put(ctx, req)
} }
@ -296,13 +296,13 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
} }
s, found, err := ac.router.IsAllowed(apechain.Ingress, s, found, err := ac.router.IsAllowed(apechain.Ingress,
policyengine.NewRequestTarget(cntNamespace, id.EncodeToString()), policyengine.NewRequestTarget(namespace, id.EncodeToString()),
request) request)
if err != nil { if err != nil {
return err return err
} }
if !found || s == apechain.Allow { if found && s == apechain.Allow {
return nil return nil
} }

View file

@ -39,6 +39,8 @@ const (
func TestAPE(t *testing.T) { func TestAPE(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
t.Run("deny get container for others", testDenyGetContainerForOthers) t.Run("deny get container for others", testDenyGetContainerForOthers)
t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR) t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken) t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
@ -49,6 +51,130 @@ func TestAPE(t *testing.T) {
t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError) t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
} }
func testAllowThenDenyGetContainerRuleDefined(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID()
testContainer := containertest.Container()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
testContainer.SetPlacementPolicy(pp)
contRdr.c[contID] = &containercore.Container{Value: testContainer}
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
var testNetmap netmap.NetMap
testNetmap.SetEpoch(nm.currentEpoch)
testNetmap.SetNodes([]netmap.NodeInfo{{}})
nm.netmaps[nm.currentEpoch] = &testNetmap
nm.netmaps[nm.currentEpoch-1] = &testNetmap
addDefaultAllowGetPolicy(t, router, contID)
req := &container.GetRequest{}
req.SetBody(&container.GetRequestBody{})
var refContID refs.ContainerID
contID.WriteToV2(&refContID)
req.GetBody().SetContainerID(&refContID)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
_, err = apeSrv.Get(context.Background(), req)
require.NoError(t, err)
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
},
},
},
},
})
require.NoError(t, err)
resp, err := apeSrv.Get(context.Background(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}
func testDenyGetContainerNoRuleFound(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID()
testContainer := containertest.Container()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
testContainer.SetPlacementPolicy(pp)
contRdr.c[contID] = &containercore.Container{Value: testContainer}
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
var testNetmap netmap.NetMap
testNetmap.SetEpoch(nm.currentEpoch)
testNetmap.SetNodes([]netmap.NodeInfo{{}})
nm.netmaps[nm.currentEpoch] = &testNetmap
nm.netmaps[nm.currentEpoch-1] = &testNetmap
req := &container.GetRequest{}
req.SetBody(&container.GetRequestBody{})
var refContID refs.ContainerID
contID.WriteToV2(&refContID)
req.GetBody().SetContainerID(&refContID)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
resp, err := apeSrv.Get(context.Background(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
require.Contains(t, errAccessDenied.Reason(), chain.NoRuleFound.String())
}
func testDenyGetContainerForOthers(t *testing.T) { func testDenyGetContainerForOthers(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -854,6 +980,8 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
addDefaultAllowGetPolicy(t, components.engine, contID)
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
@ -895,6 +1023,8 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
addDefaultAllowGetPolicy(t, components.engine, contID)
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
@ -936,6 +1066,8 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
addDefaultAllowGetPolicy(t, components.engine, contID)
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
@ -977,6 +1109,8 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
addDefaultAllowGetPolicy(t, components.engine, contID)
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
@ -1128,3 +1262,24 @@ func initListRequest(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.Priva
require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req)) require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req))
return req return req
} }
func addDefaultAllowGetPolicy(t *testing.T, e engine.Engine, contID cid.ID) {
_, _, err := e.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
nativeschema.ResourceFormatAllContainers,
},
},
},
},
})
require.NoError(t, err)
}

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
} }

View file

@ -77,6 +77,11 @@ func (s *Service) verifyClient(req message, cid cidSDK.ID, rawBearer []byte, op
} }
basicACL := cnr.Value.BasicACL() basicACL := cnr.Value.BasicACL()
// Basic ACL mask can be unset, if a container operations are performed
// with strict APE checks only.
if basicACL == 0x0 {
return nil
}
if !basicACL.IsOpAllowed(op, role) { if !basicACL.IsOpAllowed(op, role) {
return basicACLErr(op) return basicACLErr(op)