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 }