[#1689] container: Make APE middleware form container system attributes

* Make `Put` handler extract container attributes from request body and
  form APE-resource properties;
* Make `validateContainerBoundedOperation` used by `Get` and `Delete` handlers
  extracts attributes from read container.

Change-Id: I005345575c3d25b505bae4108f60cd320a7489ba
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2025-04-24 19:08:30 +03:00
parent c2a495814f
commit 86aec1ad6d
2 changed files with 206 additions and 4 deletions

View file

@ -280,11 +280,16 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
return nil, err
}
cnrProps, err := getContainerPropsFromV2(req.GetBody().GetContainer())
if err != nil {
return nil, fmt.Errorf("get container properties: %w", err)
}
request := aperequest.NewRequest(
nativeschema.MethodPutContainer,
aperequest.NewResource(
resourceName(namespace, ""),
make(map[string]string),
cnrProps,
),
reqProps,
)
@ -395,7 +400,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(ctx context.Context, con
op,
aperequest.NewResource(
resourceName(namespace, id.EncodeToString()),
ac.getContainerProps(cont),
getContainerProps(cont),
),
reqProps,
)
@ -445,10 +450,26 @@ func resourceName(namespace string, container string) string {
return fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, namespace, container)
}
func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]string {
return map[string]string{
func getContainerProps(c *containercore.Container) map[string]string {
props := map[string]string{
nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(),
}
for attrName, attrVal := range c.Value.Attributes() {
name := fmt.Sprintf(nativeschema.PropertyKeyFormatContainerAttribute, attrName)
props[name] = attrVal
}
return props
}
func getContainerPropsFromV2(cnrV2 *container.Container) (map[string]string, error) {
if cnrV2 == nil {
return nil, errors.New("container is not set")
}
c := cnrSDK.Container{}
if err := c.ReadFromV2(*cnrV2); err != nil {
return nil, err
}
return getContainerProps(&containercore.Container{Value: c}), nil
}
func (ac *apeChecker) getRequestProps(ctx context.Context, mh *session.RequestMetaHeader, vh *session.RequestVerificationHeader,

View file

@ -54,6 +54,8 @@ func TestAPE(t *testing.T) {
t.Run("deny put container with invlaid namespace", testDenyPutContainerInvalidNamespace)
t.Run("deny list containers for owner with PK", testDenyListContainersForPK)
t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
t.Run("deny get by container attribute rules", testDenyGetContainerSysZoneAttr)
t.Run("deny put by container attribute rules", testDenyPutContainerSysZoneAttr)
}
const (
@ -564,6 +566,185 @@ func testDenyGetContainerByIP(t *testing.T) {
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}
func testDenyGetContainerSysZoneAttr(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{}
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
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)
testContainer.SetAttribute(container.SysAttributeZone, "eggplant")
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
_, _, 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()),
},
},
Condition: []chain.Condition{
{
Kind: chain.KindResource,
Key: fmt.Sprintf(nativeschema.PropertyKeyFormatContainerAttribute, container.SysAttributeZone),
Value: "eggplant",
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := &container.GetRequest{}
req.SetBody(&container.GetRequestBody{})
var refContID refs.ContainerID
contID.WriteToV2(&refContID)
req.GetBody().SetContainerID(&refContID)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
resp, err := apeSrv.Get(ctxWithPeerInfo(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}
func testDenyPutContainerSysZoneAttr(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{}
contID := cidtest.ID()
testContainer := containertest.Container()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
testContainer.SetPlacementPolicy(pp)
testContainer.SetAttribute(container.SysAttributeZone, "eggplant")
contRdr.c[contID] = &containercore.Container{Value: testContainer}
owner := testContainer.Owner()
ownerAddr := owner.ScriptHash()
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
ownerAddr: {},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
ownerAddr: {},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
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
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodPutContainer,
},
},
Resources: chain.Resources{
Names: []string{
nativeschema.ResourceFormatRootContainers,
},
},
Condition: []chain.Condition{
{
Kind: chain.KindResource,
Key: fmt.Sprintf(nativeschema.PropertyKeyFormatContainerAttribute, container.SysAttributeZone),
Value: "eggplant",
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initPutRequest(t, testContainer)
resp, err := apeSrv.Put(ctxWithPeerInfo(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}
func testDenyGetContainerByGroupID(t *testing.T) {
t.Parallel()
srv := &srvStub{