1130 lines
33 KiB
Go
1130 lines
33 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
|
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
|
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
|
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
|
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
sessiontest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session/test"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
|
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
testDomainName = "testdomainname"
|
|
testDomainZone = "testdomainname.ns"
|
|
)
|
|
|
|
func TestAPE(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
|
t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
|
|
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
|
|
t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken)
|
|
t.Run("deny put container, read namespace from frostfsID", testDenyPutContainerReadNamespaceFromFrostfsID)
|
|
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)
|
|
}
|
|
|
|
func testDenyGetContainerForOthers(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
|
|
|
|
_, _, 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{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
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)
|
|
|
|
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)
|
|
}
|
|
|
|
func testDenySetContainerEACLForIR(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
|
|
|
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodSetContainerEACL,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleIR,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := &container.SetExtendedACLRequest{}
|
|
req.SetBody(&container.SetExtendedACLRequestBody{})
|
|
var refContID refs.ContainerID
|
|
contID.WriteToV2(&refContID)
|
|
req.GetBody().SetEACL(&acl.Table{})
|
|
req.GetBody().GetEACL().SetContainerID(&refContID)
|
|
|
|
pk, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
|
ir.keys = append(ir.keys, pk.PublicKey().Bytes())
|
|
|
|
resp, err := apeSrv.SetExtendedACL(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyGetContainerEACLForIRSessionToken(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
|
|
|
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodGetContainerEACL,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleIR,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := &container.GetExtendedACLRequest{}
|
|
req.SetBody(&container.GetExtendedACLRequestBody{})
|
|
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))
|
|
|
|
sessionPK, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
sToken := sessiontest.ContainerSigned()
|
|
sToken.ApplyOnlyTo(contID)
|
|
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
|
|
var sTokenV2 session.Token
|
|
sToken.WriteToV2(&sTokenV2)
|
|
metaHeader := new(session.RequestMetaHeader)
|
|
metaHeader.SetSessionToken(&sTokenV2)
|
|
req.SetMetaHeader(metaHeader)
|
|
|
|
ir.keys = append(ir.keys, sessionPK.PublicKey().Bytes())
|
|
|
|
resp, err := apeSrv.GetExtendedACL(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyPutContainerForOthersSessionToken(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)
|
|
|
|
testContainer := containertest.Container()
|
|
|
|
nm.currentEpoch = 100
|
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
|
|
|
_, _, 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{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initPutRequest(t, testContainer)
|
|
|
|
resp, err := apeSrv.Put(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyPutContainerReadNamespaceFromFrostfsID(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{}
|
|
|
|
cnrID, testContainer := initTestContainer(t, true)
|
|
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
|
|
|
|
nm.currentEpoch = 100
|
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
|
|
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodPutContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initPutRequest(t, testContainer)
|
|
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
|
|
|
|
frostfsIDSubjectReader := &frostfsidStub{
|
|
subjects: map[util.Uint160]*client.Subject{
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
},
|
|
},
|
|
}
|
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
|
resp, err := apeSrv.Put(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyPutContainerInvalidNamespace(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{}
|
|
|
|
cnrID, testContainer := initTestContainer(t, false)
|
|
var domain cnrSDK.Domain
|
|
domain.SetName("incorrect" + testDomainName)
|
|
domain.SetZone("incorrect" + testDomainZone)
|
|
cnrSDK.WriteDomain(&testContainer, domain)
|
|
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
|
|
|
|
nm.currentEpoch = 100
|
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
|
|
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodPutContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initPutRequest(t, testContainer)
|
|
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
|
|
|
|
frostfsIDSubjectReader := &frostfsidStub{
|
|
subjects: map[util.Uint160]*client.Subject{
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
},
|
|
},
|
|
}
|
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
|
resp, err := apeSrv.Put(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
require.ErrorContains(t, err, "invalid domain zone")
|
|
}
|
|
|
|
func testDenyListContainersForPK(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)
|
|
|
|
nm.currentEpoch = 100
|
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
|
|
|
pk, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodListContainers,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
nativeschema.ResourceFormatRootContainers,
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
|
Value: hex.EncodeToString(pk.PublicKey().Bytes()),
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var userID user.ID
|
|
user.IDFromKey(&userID, pk.PrivateKey.PublicKey)
|
|
|
|
req := &container.ListRequest{}
|
|
req.SetBody(&container.ListRequestBody{})
|
|
var ownerID refs.OwnerID
|
|
userID.WriteToV2(&ownerID)
|
|
req.GetBody().SetOwnerID(&ownerID)
|
|
|
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
|
|
|
resp, err := apeSrv.List(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyListContainersValidationNamespaceError(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{}
|
|
|
|
actorPK, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
ownerPK, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
actorScriptHash, ownerScriptHash := initActorOwnerScriptHashes(t, actorPK, ownerPK)
|
|
|
|
const actorDomain = "actor" + testDomainName
|
|
|
|
frostfsIDSubjectReader := &frostfsidStub{
|
|
subjects: map[util.Uint160]*client.Subject{
|
|
actorScriptHash: {
|
|
Namespace: actorDomain,
|
|
Name: actorDomain,
|
|
},
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
},
|
|
},
|
|
}
|
|
|
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
|
|
|
nm.currentEpoch = 100
|
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
|
|
|
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodListContainers,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
nativeschema.ResourceFormatRootContainers,
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
|
Value: actorPK.PublicKey().String(),
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initListRequest(t, actorPK, ownerPK)
|
|
|
|
resp, err := apeSrv.List(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
require.ErrorContains(t, err, "actor namespace "+actorDomain+" differs")
|
|
}
|
|
|
|
type srvStub struct {
|
|
calls map[string]int
|
|
}
|
|
|
|
func (s *srvStub) AnnounceUsedSpace(context.Context, *container.AnnounceUsedSpaceRequest) (*container.AnnounceUsedSpaceResponse, error) {
|
|
s.calls["AnnounceUsedSpace"]++
|
|
return &container.AnnounceUsedSpaceResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) Delete(context.Context, *container.DeleteRequest) (*container.DeleteResponse, error) {
|
|
s.calls["Delete"]++
|
|
return &container.DeleteResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) Get(context.Context, *container.GetRequest) (*container.GetResponse, error) {
|
|
s.calls["Get"]++
|
|
return &container.GetResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) GetExtendedACL(context.Context, *container.GetExtendedACLRequest) (*container.GetExtendedACLResponse, error) {
|
|
s.calls["GetExtendedACL"]++
|
|
return &container.GetExtendedACLResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) List(context.Context, *container.ListRequest) (*container.ListResponse, error) {
|
|
s.calls["List"]++
|
|
return &container.ListResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) Put(context.Context, *container.PutRequest) (*container.PutResponse, error) {
|
|
s.calls["Put"]++
|
|
return &container.PutResponse{}, nil
|
|
}
|
|
|
|
func (s *srvStub) SetExtendedACL(context.Context, *container.SetExtendedACLRequest) (*container.SetExtendedACLResponse, error) {
|
|
s.calls["SetExtendedACL"]++
|
|
return &container.SetExtendedACLResponse{}, nil
|
|
}
|
|
|
|
type irStub struct {
|
|
keys [][]byte
|
|
}
|
|
|
|
func (s *irStub) InnerRingKeys() ([][]byte, error) {
|
|
return s.keys, nil
|
|
}
|
|
|
|
type containerStub struct {
|
|
c map[cid.ID]*containercore.Container
|
|
}
|
|
|
|
func (s *containerStub) Get(id cid.ID) (*containercore.Container, error) {
|
|
if v, ok := s.c[id]; ok {
|
|
return v, nil
|
|
}
|
|
return nil, errors.New("container not found")
|
|
}
|
|
|
|
type netmapStub struct {
|
|
netmaps map[uint64]*netmap.NetMap
|
|
currentEpoch uint64
|
|
}
|
|
|
|
func (s *netmapStub) GetNetMap(diff uint64) (*netmap.NetMap, error) {
|
|
if diff >= s.currentEpoch {
|
|
return nil, errors.New("invalid diff")
|
|
}
|
|
return s.GetNetMapByEpoch(s.currentEpoch - diff)
|
|
}
|
|
|
|
func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
|
|
if nm, found := s.netmaps[epoch]; found {
|
|
return nm, nil
|
|
}
|
|
return nil, errors.New("netmap not found")
|
|
}
|
|
|
|
func (s *netmapStub) Epoch() (uint64, error) {
|
|
return s.currentEpoch, nil
|
|
}
|
|
|
|
type frostfsidStub struct {
|
|
subjects map[util.Uint160]*client.Subject
|
|
}
|
|
|
|
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
|
|
s, ok := f.subjects[owner]
|
|
if !ok {
|
|
return nil, errSubjectNotFound
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
type testAPEServer struct {
|
|
engine engine.Engine
|
|
|
|
containerReader *containerStub
|
|
|
|
ir *irStub
|
|
|
|
netmap *netmapStub
|
|
|
|
frostfsIDSubjectReader *frostfsidStub
|
|
|
|
apeChecker *apeChecker
|
|
}
|
|
|
|
func newTestAPEServer() testAPEServer {
|
|
srv := &srvStub{
|
|
calls: map[string]int{},
|
|
}
|
|
|
|
engine := inmemory.NewInMemory()
|
|
|
|
containerReader := &containerStub{
|
|
c: map[cid.ID]*containercore.Container{},
|
|
}
|
|
|
|
ir := &irStub{
|
|
keys: [][]byte{},
|
|
}
|
|
|
|
netmap := &netmapStub{}
|
|
|
|
frostfsIDSubjectReader := &frostfsidStub{
|
|
subjects: map[util.Uint160]*client.Subject{},
|
|
}
|
|
|
|
apeChecker := &apeChecker{
|
|
router: engine,
|
|
reader: containerReader,
|
|
ir: ir,
|
|
nm: netmap,
|
|
frostFSIDClient: frostfsIDSubjectReader,
|
|
next: srv,
|
|
}
|
|
|
|
return testAPEServer{
|
|
engine: engine,
|
|
containerReader: containerReader,
|
|
ir: ir,
|
|
netmap: netmap,
|
|
frostfsIDSubjectReader: frostfsIDSubjectReader,
|
|
apeChecker: apeChecker,
|
|
}
|
|
}
|
|
|
|
func TestValidateContainerBoundedOperation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("check root-defined container in root-defined container target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, false)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.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{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
|
require.ErrorContains(t, err, aErr.Error())
|
|
})
|
|
|
|
t.Run("check root-defined container in testdomain-defined container target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, false)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.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.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("check root-defined container in testdomain namespace target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, false)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodGetContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("check testdomain-defined container in root-defined container target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, true)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.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{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("check testdomain-defined container in testdomain-defined container target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, true)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.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.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
|
require.ErrorContains(t, err, aErr.Error())
|
|
})
|
|
|
|
t.Run("check testdomain-defined container in testdomain namespace target rule", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
components := newTestAPEServer()
|
|
contID, testContainer := initTestContainer(t, true)
|
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
|
initTestNetmap(components.netmap)
|
|
|
|
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodGetContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Object: chain.ObjectRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
|
require.ErrorContains(t, err, aErr.Error())
|
|
})
|
|
}
|
|
|
|
func initTestGetContainerRequest(t *testing.T, contID cid.ID) *container.GetRequest {
|
|
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))
|
|
return req
|
|
}
|
|
|
|
func initTestNetmap(netmapStub *netmapStub) {
|
|
netmapStub.currentEpoch = 100
|
|
netmapStub.netmaps = map[uint64]*netmap.NetMap{}
|
|
var testNetmap netmap.NetMap
|
|
testNetmap.SetEpoch(netmapStub.currentEpoch)
|
|
testNetmap.SetNodes([]netmap.NodeInfo{{}})
|
|
netmapStub.netmaps[netmapStub.currentEpoch] = &testNetmap
|
|
netmapStub.netmaps[netmapStub.currentEpoch-1] = &testNetmap
|
|
}
|
|
|
|
func initTestContainer(t *testing.T, isDomainSet bool) (cid.ID, cnrSDK.Container) {
|
|
contID := cidtest.ID()
|
|
testContainer := containertest.Container()
|
|
pp := netmap.PlacementPolicy{}
|
|
require.NoError(t, pp.DecodeString("REP 1"))
|
|
testContainer.SetPlacementPolicy(pp)
|
|
if isDomainSet {
|
|
// no domain defined -> container is defined in root namespace
|
|
var domain cnrSDK.Domain
|
|
domain.SetName(testDomainName)
|
|
domain.SetZone(testDomainZone)
|
|
cnrSDK.WriteDomain(&testContainer, domain)
|
|
}
|
|
return contID, testContainer
|
|
}
|
|
|
|
func initPutRequest(t *testing.T, testContainer cnrSDK.Container) *container.PutRequest {
|
|
req := &container.PutRequest{}
|
|
req.SetBody(&container.PutRequestBody{})
|
|
var reqCont container.Container
|
|
testContainer.WriteToV2(&reqCont)
|
|
req.GetBody().SetContainer(&reqCont)
|
|
|
|
sessionPK, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
sToken := sessiontest.ContainerSigned()
|
|
sToken.ApplyOnlyTo(cid.ID{})
|
|
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
|
|
var sTokenV2 session.Token
|
|
sToken.WriteToV2(&sTokenV2)
|
|
metaHeader := new(session.RequestMetaHeader)
|
|
metaHeader.SetSessionToken(&sTokenV2)
|
|
req.SetMetaHeader(metaHeader)
|
|
|
|
pk, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
|
|
|
return req
|
|
}
|
|
|
|
func initOwnerIDScriptHash(t *testing.T, testContainer cnrSDK.Container) util.Uint160 {
|
|
var ownerSDK *user.ID
|
|
owner := testContainer.Owner()
|
|
ownerSDK = &owner
|
|
sc, err := ownerSDK.ScriptHash()
|
|
require.NoError(t, err)
|
|
return sc
|
|
}
|
|
|
|
func initActorOwnerScriptHashes(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) (actorScriptHash util.Uint160, ownerScriptHash util.Uint160) {
|
|
var actorUserID user.ID
|
|
user.IDFromKey(&actorUserID, ecdsa.PublicKey(*actorPK.PublicKey()))
|
|
var err error
|
|
actorScriptHash, err = actorUserID.ScriptHash()
|
|
require.NoError(t, err)
|
|
|
|
var ownerUserID user.ID
|
|
user.IDFromKey(&ownerUserID, ecdsa.PublicKey(*ownerPK.PublicKey()))
|
|
ownerScriptHash, err = ownerUserID.ScriptHash()
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, ownerScriptHash.String(), actorScriptHash.String())
|
|
return
|
|
}
|
|
|
|
func initListRequest(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) *container.ListRequest {
|
|
var ownerUserID user.ID
|
|
user.IDFromKey(&ownerUserID, ownerPK.PrivateKey.PublicKey)
|
|
|
|
req := &container.ListRequest{}
|
|
req.SetBody(&container.ListRequestBody{})
|
|
var ownerID refs.OwnerID
|
|
ownerUserID.WriteToV2(&ownerID)
|
|
req.GetBody().SetOwnerID(&ownerID)
|
|
|
|
require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req))
|
|
return req
|
|
}
|