[#1689] container: Make APE middleware form container system attributes
All checks were successful
Vulncheck / Vulncheck (push) Successful in 1m14s
Pre-commit hooks / Pre-commit (push) Successful in 1m39s
Build / Build Components (push) Successful in 1m50s
Tests and linters / Run gofumpt (push) Successful in 3m20s
Tests and linters / Staticcheck (push) Successful in 3m39s
Tests and linters / Lint (push) Successful in 3m46s
Tests and linters / Tests (push) Successful in 4m11s
Tests and linters / gopls check (push) Successful in 4m8s
Tests and linters / Tests with -race (push) Successful in 4m28s
OCI image / Build container images (push) Successful in 4m47s

* 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{