Dmitrii Stepanov
f53d30fa95
All checks were successful
Build / Build Components (1.21) (pull_request) Successful in 1m37s
Build / Build Components (1.22) (pull_request) Successful in 2m49s
Vulncheck / Vulncheck (pull_request) Successful in 2m25s
Tests and linters / Tests with -race (pull_request) Successful in 5m21s
Tests and linters / Tests (1.22) (pull_request) Successful in 5m48s
DCO action / DCO (pull_request) Successful in 31s
Pre-commit hooks / Pre-commit (pull_request) Successful in 1m12s
Tests and linters / Staticcheck (pull_request) Successful in 1m44s
Tests and linters / Lint (pull_request) Successful in 2m20s
Tests and linters / gopls check (pull_request) Successful in 2m33s
Tests and linters / Tests (1.21) (pull_request) Successful in 3m36s
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
1682 lines
47 KiB
Go
1682 lines
47 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
|
|
"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"
|
|
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
|
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"
|
|
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
|
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"
|
|
"google.golang.org/grpc/peer"
|
|
)
|
|
|
|
const (
|
|
testDomainName = "testdomainname"
|
|
testDomainZone = "testdomainname.ns"
|
|
)
|
|
|
|
func TestAPE(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
|
|
t.Run("allow by group id", TestAllowByGroupIDs)
|
|
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
|
|
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
|
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
|
|
t.Run("deny get container by IP", testDenyGetContainerByIP)
|
|
t.Run("deny get container by group id", testDenyGetContainerByGroupID)
|
|
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)
|
|
}
|
|
|
|
const (
|
|
incomingIP = "192.92.33.1"
|
|
)
|
|
|
|
func ctxWithPeerInfo() context.Context {
|
|
return peer.NewContext(context.Background(), &peer.Peer{
|
|
Addr: &net.TCPAddr{
|
|
IP: net.ParseIP(incomingIP),
|
|
Port: 41111,
|
|
},
|
|
})
|
|
}
|
|
|
|
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 TestAllowByGroupIDs(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: 1,
|
|
Name: "Group#1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
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)
|
|
|
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
|
|
|
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.GroupTarget(":1"), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{
|
|
Names: []string{
|
|
nativeschema.MethodGetContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: commonschema.PropertyKeyFrostFSIDGroupID,
|
|
Value: "1",
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
resp, err := apeSrv.Get(context.Background(), req)
|
|
require.NotNil(t, resp)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
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) {
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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 testDenyGetContainerByUserClaimTag(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)
|
|
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.KindRequest,
|
|
Key: fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"),
|
|
Value: "value100",
|
|
Op: chain.CondStringNotEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
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(context.Background(), req)
|
|
require.Nil(t, resp)
|
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
|
require.ErrorAs(t, err, &errAccessDenied)
|
|
}
|
|
|
|
func testDenyGetContainerByIP(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)
|
|
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.KindRequest,
|
|
Key: commonschema.PropertyKeyFrostFSSourceIP,
|
|
Value: incomingIP + "/16",
|
|
Op: chain.CondIPAddress,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
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 testDenyGetContainerByGroupID(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)
|
|
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.KindRequest,
|
|
Key: commonschema.PropertyKeyFrostFSIDGroupID,
|
|
Value: "19888",
|
|
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(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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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{}
|
|
|
|
testContainer := containertest.Container()
|
|
owner := testContainer.Owner()
|
|
ownerAddr, err := owner.ScriptHash()
|
|
require.NoError(t, err)
|
|
frostfsIDSubjectReader := &frostfsidStub{
|
|
subjects: map[util.Uint160]*client.Subject{
|
|
ownerAddr: {},
|
|
},
|
|
}
|
|
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.MethodPutContainer,
|
|
},
|
|
},
|
|
Resources: chain.Resources{
|
|
Names: []string{
|
|
nativeschema.ResourceFormatRootContainers,
|
|
},
|
|
},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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,
|
|
},
|
|
},
|
|
subjectsExt: map[util.Uint160]*client.SubjectExtended{
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
Groups: []*client.Group{
|
|
{
|
|
ID: 19888,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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,
|
|
},
|
|
},
|
|
subjectsExt: map[util.Uint160]*client.SubjectExtended{
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
Groups: []*client.Group{
|
|
{
|
|
ID: 19888,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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,
|
|
},
|
|
},
|
|
subjectsExt: map[util.Uint160]*client.SubjectExtended{
|
|
actorScriptHash: {
|
|
Namespace: actorDomain,
|
|
Name: actorDomain,
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
Groups: []*client.Group{
|
|
{
|
|
ID: 19777,
|
|
},
|
|
},
|
|
},
|
|
ownerScriptHash: {
|
|
Namespace: testDomainName,
|
|
Name: testDomainName,
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
Groups: []*client.Group{
|
|
{
|
|
ID: 19888,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
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) 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
|
|
}
|
|
|
|
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
|
|
subjectsExt map[util.Uint160]*client.SubjectExtended
|
|
}
|
|
|
|
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
|
|
s, ok := f.subjects[owner]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (f *frostfsidStub) GetSubjectExtended(owner util.Uint160) (*client.SubjectExtended, error) {
|
|
s, ok := f.subjectsExt[owner]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
|
|
}
|
|
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{},
|
|
subjectsExt: map[util.Uint160]*client.SubjectExtended{},
|
|
}
|
|
|
|
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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
addDefaultAllowGetPolicy(t, components.engine, contID)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
addDefaultAllowGetPolicy(t, components.engine, contID)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
addDefaultAllowGetPolicy(t, components.engine, contID)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
addDefaultAllowGetPolicy(t, components.engine, contID)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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{
|
|
{
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
Op: chain.CondStringEquals,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := initTestGetContainerRequest(t, contID)
|
|
|
|
err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), 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
|
|
}
|
|
|
|
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)
|
|
}
|