492 lines
14 KiB
Go
492 lines
14 KiB
Go
|
package container
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"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"
|
||
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||
|
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||
|
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/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
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 list containers for owner with PK", testDenyListContainersForPK)
|
||
|
}
|
||
|
|
||
|
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{}
|
||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, 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{}
|
||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, 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{}
|
||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, 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.ObjectSigned()
|
||
|
sToken.BindContainer(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{}
|
||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, 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 := &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.ObjectSigned()
|
||
|
sToken.BindContainer(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))
|
||
|
|
||
|
resp, err := apeSrv.Put(context.Background(), req)
|
||
|
require.Nil(t, resp)
|
||
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
||
|
require.ErrorAs(t, err, &errAccessDenied)
|
||
|
}
|
||
|
|
||
|
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{}
|
||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, 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: pk.PublicKey().String(),
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|