772 lines
20 KiB
Go
772 lines
20 KiB
Go
package ape
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
|
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
|
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
|
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
policyengine "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"
|
|
)
|
|
|
|
type headerProviderMock struct {
|
|
m map[oid.Address]*objectSDK.Object
|
|
}
|
|
|
|
var _ HeaderProvider = (*headerProviderMock)(nil)
|
|
|
|
func (h *headerProviderMock) addHeader(c cid.ID, o oid.ID, header *objectSDK.Object) {
|
|
var addr oid.Address
|
|
addr.SetContainer(c)
|
|
addr.SetObject(o)
|
|
h.m[addr] = header
|
|
}
|
|
|
|
func (h *headerProviderMock) GetHeader(_ context.Context, c cid.ID, o oid.ID, _ bool) (*objectSDK.Object, error) {
|
|
var addr oid.Address
|
|
addr.SetContainer(c)
|
|
addr.SetObject(o)
|
|
obj, ok := h.m[addr]
|
|
if !ok {
|
|
return nil, fmt.Errorf("address not found")
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
func newHeaderProviderMock() *headerProviderMock {
|
|
return &headerProviderMock{
|
|
m: make(map[oid.Address]*objectSDK.Object),
|
|
}
|
|
}
|
|
|
|
func newContainerIDSDK(t *testing.T, encodedCID string) cid.ID {
|
|
var cnr cid.ID
|
|
require.NoError(t, cnr.DecodeString(encodedCID))
|
|
return cnr
|
|
}
|
|
|
|
func newObjectIDSDK(t *testing.T, encodedOID *string) *oid.ID {
|
|
if encodedOID == nil {
|
|
return nil
|
|
}
|
|
obj := new(oid.ID)
|
|
require.NoError(t, obj.DecodeString(*encodedOID))
|
|
return obj
|
|
}
|
|
|
|
type headerObjectSDKParams struct {
|
|
majorVersion, minorVersion uint32
|
|
owner user.ID
|
|
epoch uint64
|
|
payloadSize uint64
|
|
typ objectSDK.Type
|
|
payloadChecksum checksum.Checksum
|
|
payloadHomomorphicHash checksum.Checksum
|
|
attributes []struct {
|
|
key string
|
|
val string
|
|
}
|
|
}
|
|
|
|
func stringPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
func newHeaderObjectSDK(cnr cid.ID, oid *oid.ID, headerObjSDK *headerObjectSDKParams) *objectSDK.Object {
|
|
objSDK := objectSDK.New()
|
|
objSDK.SetContainerID(cnr)
|
|
if oid != nil {
|
|
objSDK.SetID(*oid)
|
|
}
|
|
if headerObjSDK == nil {
|
|
return objSDK
|
|
}
|
|
ver := new(version.Version)
|
|
ver.SetMajor(headerObjSDK.majorVersion)
|
|
ver.SetMinor(headerObjSDK.minorVersion)
|
|
objSDK.SetVersion(ver)
|
|
objSDK.SetCreationEpoch(headerObjSDK.epoch)
|
|
objSDK.SetOwnerID(headerObjSDK.owner)
|
|
objSDK.SetPayloadSize(headerObjSDK.payloadSize)
|
|
objSDK.SetType(headerObjSDK.typ)
|
|
objSDK.SetPayloadChecksum(headerObjSDK.payloadChecksum)
|
|
objSDK.SetPayloadHomomorphicHash(headerObjSDK.payloadHomomorphicHash)
|
|
|
|
var attrs []objectSDK.Attribute
|
|
for _, attr := range headerObjSDK.attributes {
|
|
attrSDK := objectSDK.NewAttribute()
|
|
attrSDK.SetKey(attr.key)
|
|
attrSDK.SetValue(attr.val)
|
|
attrs = append(attrs, *attrSDK)
|
|
}
|
|
objSDK.SetAttributes(attrs...)
|
|
|
|
return objSDK
|
|
}
|
|
|
|
type testHeader struct {
|
|
headerObjSDK *headerObjectSDKParams
|
|
|
|
// If fromHeaderProvider is set, then running test should
|
|
// consider that a header is recieved from a header provider.
|
|
fromHeaderProvider bool
|
|
|
|
// If fromHeaderProvider is set, then running test should
|
|
// consider that a header is recieved from a message header.
|
|
fromRequestResponseHeader bool
|
|
}
|
|
|
|
var (
|
|
methodsRequiredOID = []string{
|
|
nativeschema.MethodGetObject,
|
|
nativeschema.MethodHeadObject,
|
|
nativeschema.MethodRangeObject,
|
|
nativeschema.MethodHashObject,
|
|
nativeschema.MethodDeleteObject,
|
|
}
|
|
|
|
methodsOptionalOID = []string{
|
|
nativeschema.MethodSearchObject, nativeschema.MethodPutObject,
|
|
}
|
|
|
|
namespace = "test_namespace"
|
|
|
|
containerID = "73tQMTYyUkTgmvPR1HWib6pndbhSoBovbnMF7Pws8Rcy"
|
|
|
|
objectID = "BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R"
|
|
|
|
groupID = "1"
|
|
|
|
role = "Container"
|
|
|
|
senderPrivateKey, _ = keys.NewPrivateKey()
|
|
|
|
senderKey = hex.EncodeToString(senderPrivateKey.PublicKey().Bytes())
|
|
)
|
|
|
|
type frostfsIDProviderMock struct {
|
|
subjects map[util.Uint160]*client.Subject
|
|
subjectsExtended map[util.Uint160]*client.SubjectExtended
|
|
}
|
|
|
|
var _ frostfsidcore.SubjectProvider = (*frostfsIDProviderMock)(nil)
|
|
|
|
func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock {
|
|
return &frostfsIDProviderMock{
|
|
subjects: map[util.Uint160]*client.Subject{
|
|
scriptHashFromSenderKey(t, senderKey): {
|
|
Namespace: "testnamespace",
|
|
Name: "test",
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
},
|
|
},
|
|
subjectsExtended: map[util.Uint160]*client.SubjectExtended{
|
|
scriptHashFromSenderKey(t, senderKey): {
|
|
Namespace: "testnamespace",
|
|
Name: "test",
|
|
KV: map[string]string{
|
|
"tag-attr1": "value1",
|
|
"tag-attr2": "value2",
|
|
},
|
|
Groups: []*client.Group{
|
|
{
|
|
ID: 1,
|
|
Name: "test",
|
|
Namespace: "testnamespace",
|
|
KV: map[string]string{
|
|
"attr1": "value1",
|
|
"attr2": "value2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func scriptHashFromSenderKey(t *testing.T, senderKey string) util.Uint160 {
|
|
pk, err := keys.NewPublicKeyFromString(senderKey)
|
|
require.NoError(t, err)
|
|
return pk.GetScriptHash()
|
|
}
|
|
|
|
func (f *frostfsIDProviderMock) GetSubject(key util.Uint160) (*client.Subject, error) {
|
|
v, ok := f.subjects[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func (f *frostfsIDProviderMock) GetSubjectExtended(key util.Uint160) (*client.SubjectExtended, error) {
|
|
v, ok := f.subjectsExtended[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
var apeCheckTestCases = []struct {
|
|
name string
|
|
container string
|
|
object *string
|
|
methods []string
|
|
header testHeader
|
|
xHeaders []session.XHeader
|
|
containerRules []chain.Rule
|
|
groupidRules []chain.Rule
|
|
expectAPEErr bool
|
|
}{
|
|
{
|
|
name: "oid required requests are allowed",
|
|
container: containerID,
|
|
object: stringPtr(objectID),
|
|
methods: methodsRequiredOID,
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: methodsRequiredOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "oid optional requests are allowed",
|
|
container: containerID,
|
|
methods: methodsOptionalOID,
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: methodsOptionalOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "oid required requests are denied",
|
|
container: containerID,
|
|
object: stringPtr(objectID),
|
|
methods: methodsRequiredOID,
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: methodsRequiredOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
{
|
|
name: "oid required requests are denied by an attribute",
|
|
container: containerID,
|
|
object: stringPtr(objectID),
|
|
methods: methodsRequiredOID,
|
|
header: testHeader{
|
|
headerObjSDK: &headerObjectSDKParams{
|
|
attributes: []struct {
|
|
key string
|
|
val string
|
|
}{
|
|
{
|
|
key: "attr1",
|
|
val: "attribute_value",
|
|
},
|
|
},
|
|
},
|
|
fromHeaderProvider: true,
|
|
},
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: methodsRequiredOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringLike,
|
|
Kind: chain.KindResource,
|
|
Key: "attr1",
|
|
Value: "attribute*",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
{
|
|
name: "oid required requests are denied by sender",
|
|
container: containerID,
|
|
object: stringPtr(objectID),
|
|
methods: methodsRequiredOID,
|
|
header: testHeader{
|
|
headerObjSDK: &headerObjectSDKParams{
|
|
attributes: []struct {
|
|
key string
|
|
val string
|
|
}{
|
|
{
|
|
key: "attr1",
|
|
val: "attribute_value",
|
|
},
|
|
},
|
|
},
|
|
fromHeaderProvider: true,
|
|
},
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: methodsRequiredOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringLike,
|
|
Kind: chain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
|
Value: senderKey,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
{
|
|
name: "oid required requests are denied by xheader",
|
|
container: containerID,
|
|
object: stringPtr(objectID),
|
|
methods: methodsRequiredOID,
|
|
header: testHeader{
|
|
headerObjSDK: &headerObjectSDKParams{
|
|
attributes: []struct {
|
|
key string
|
|
val string
|
|
}{
|
|
{
|
|
key: "attr1",
|
|
val: "attribute_value",
|
|
},
|
|
},
|
|
},
|
|
fromHeaderProvider: true,
|
|
},
|
|
xHeaders: []session.XHeader{
|
|
func() (xhead session.XHeader) {
|
|
xhead.SetKey("X-Test-ID")
|
|
xhead.SetValue("aezakmi")
|
|
return
|
|
}(),
|
|
},
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: methodsRequiredOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, containerID, objectID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringLike,
|
|
Kind: chain.KindRequest,
|
|
Key: fmt.Sprintf(commonschema.PropertyKeyFrostFSXHeader, "X-Test-ID"),
|
|
Value: "aezakmi",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
{
|
|
name: "optional oid requests reached quota limit by an attribute",
|
|
container: containerID,
|
|
methods: methodsOptionalOID,
|
|
header: testHeader{
|
|
headerObjSDK: &headerObjectSDKParams{
|
|
payloadSize: 1000,
|
|
},
|
|
fromRequestResponseHeader: true,
|
|
},
|
|
containerRules: []chain.Rule{
|
|
{
|
|
Status: chain.QuotaLimitReached,
|
|
Actions: chain.Actions{Names: methodsOptionalOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringEquals,
|
|
Kind: chain.KindResource,
|
|
Key: nativeschema.PropertyKeyObjectPayloadLength,
|
|
Value: "1000",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
{
|
|
name: "optional oid requests reached quota limit by group-id",
|
|
container: containerID,
|
|
methods: methodsOptionalOID,
|
|
header: testHeader{
|
|
headerObjSDK: &headerObjectSDKParams{
|
|
payloadSize: 1000,
|
|
},
|
|
fromRequestResponseHeader: true,
|
|
},
|
|
groupidRules: []chain.Rule{
|
|
{
|
|
Status: chain.QuotaLimitReached,
|
|
Actions: chain.Actions{Names: methodsOptionalOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringEquals,
|
|
Kind: chain.KindRequest,
|
|
Key: commonschema.PropertyKeyFrostFSIDGroupID,
|
|
Value: groupID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectAPEErr: true,
|
|
},
|
|
}
|
|
|
|
type stMock struct{}
|
|
|
|
func (m *stMock) CurrentEpoch() uint64 {
|
|
return 8
|
|
}
|
|
|
|
func TestAPECheck_BearerTokenOverrides(t *testing.T) {
|
|
for _, test := range apeCheckTestCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
chain := chain.Chain{
|
|
Rules: test.containerRules,
|
|
MatchType: chain.MatchTypeFirstMatch,
|
|
}
|
|
chainSDK := apeSDK.Chain{
|
|
Raw: chain.Bytes(),
|
|
}
|
|
bt := new(bearer.Token)
|
|
bt.SetIat(1)
|
|
bt.SetExp(10)
|
|
bt.SetAPEOverride(bearer.APEOverride{
|
|
Target: apeSDK.ChainTarget{
|
|
TargetType: apeSDK.TargetTypeContainer,
|
|
Name: test.container,
|
|
},
|
|
Chains: []apeSDK.Chain{chainSDK},
|
|
})
|
|
bt.Sign(senderPrivateKey.PrivateKey)
|
|
var cnrOwner user.ID
|
|
user.IDFromKey(&cnrOwner, (ecdsa.PublicKey)(*senderPrivateKey.PublicKey()))
|
|
|
|
for _, method := range test.methods {
|
|
t.Run(method, func(t *testing.T) {
|
|
headerProvider := newHeaderProviderMock()
|
|
frostfsidProvider := newFrostfsIDProviderMock(t)
|
|
|
|
cnr := newContainerIDSDK(t, test.container)
|
|
obj := newObjectIDSDK(t, test.object)
|
|
|
|
ls := inmemory.NewInmemoryLocalStorage()
|
|
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
|
|
|
checker := NewChecker(ls, ms, headerProvider, frostfsidProvider, nil, &stMock{}, nil, nil)
|
|
|
|
prm := Prm{
|
|
Method: method,
|
|
Container: cnr,
|
|
Object: obj,
|
|
Role: role,
|
|
ContainerOwner: cnrOwner,
|
|
SenderKey: senderKey,
|
|
BearerToken: bt,
|
|
}
|
|
|
|
var headerObjSDK *objectSDK.Object
|
|
if test.header.headerObjSDK != nil {
|
|
headerObjSDK = newHeaderObjectSDK(cnr, obj, test.header.headerObjSDK)
|
|
if test.header.fromHeaderProvider {
|
|
require.NotNil(t, obj, "oid is required if a header is expected to be found in header provider")
|
|
headerProvider.addHeader(cnr, *obj, headerObjSDK)
|
|
} else if test.header.fromRequestResponseHeader {
|
|
prm.Header = headerObjSDK.ToV2().GetHeader()
|
|
}
|
|
}
|
|
|
|
err := checker.CheckAPE(context.Background(), prm)
|
|
if test.expectAPEErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAPECheck(t *testing.T) {
|
|
for _, test := range apeCheckTestCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
for _, method := range test.methods {
|
|
t.Run(method, func(t *testing.T) {
|
|
headerProvider := newHeaderProviderMock()
|
|
frostfsidProvider := newFrostfsIDProviderMock(t)
|
|
|
|
cnr := newContainerIDSDK(t, test.container)
|
|
obj := newObjectIDSDK(t, test.object)
|
|
|
|
ls := inmemory.NewInmemoryLocalStorage()
|
|
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
|
|
|
if len(test.containerRules) > 0 {
|
|
ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
|
|
Rules: test.containerRules,
|
|
MatchType: chain.MatchTypeFirstMatch,
|
|
})
|
|
}
|
|
|
|
if len(test.groupidRules) > 0 {
|
|
ls.AddOverride(chain.Ingress, policyengine.GroupTarget(":"+groupID), &chain.Chain{
|
|
Rules: test.groupidRules,
|
|
MatchType: chain.MatchTypeFirstMatch,
|
|
})
|
|
}
|
|
|
|
checker := NewChecker(ls, ms, headerProvider, frostfsidProvider, nil, &stMock{}, nil, nil)
|
|
|
|
prm := Prm{
|
|
Method: method,
|
|
Container: cnr,
|
|
Object: obj,
|
|
Role: role,
|
|
SenderKey: senderKey,
|
|
}
|
|
|
|
var headerObjSDK *objectSDK.Object
|
|
if test.header.headerObjSDK != nil {
|
|
headerObjSDK = newHeaderObjectSDK(cnr, obj, test.header.headerObjSDK)
|
|
if test.header.fromHeaderProvider {
|
|
require.NotNil(t, obj, "oid is required if a header is expected to be found in header provider")
|
|
headerProvider.addHeader(cnr, *obj, headerObjSDK)
|
|
} else if test.header.fromRequestResponseHeader {
|
|
prm.Header = headerObjSDK.ToV2().GetHeader()
|
|
}
|
|
}
|
|
|
|
err := checker.CheckAPE(context.Background(), prm)
|
|
if test.expectAPEErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type netmapStub struct {
|
|
netmaps map[uint64]*netmapSDK.NetMap
|
|
currentEpoch uint64
|
|
}
|
|
|
|
func (s *netmapStub) GetNetMap(diff uint64) (*netmapSDK.NetMap, error) {
|
|
if diff >= s.currentEpoch {
|
|
return nil, errors.New("invalid diff")
|
|
}
|
|
return s.GetNetMapByEpoch(s.currentEpoch - diff)
|
|
}
|
|
|
|
func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmapSDK.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 testContainerSource struct {
|
|
containers map[cid.ID]*container.Container
|
|
}
|
|
|
|
func (s *testContainerSource) Get(cnrID cid.ID) (*container.Container, error) {
|
|
if cnr, found := s.containers[cnrID]; found {
|
|
return cnr, nil
|
|
}
|
|
return nil, fmt.Errorf("container not found")
|
|
}
|
|
|
|
func (s *testContainerSource) DeletionInfo(cid.ID) (*container.DelInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestPutECChunk(t *testing.T) {
|
|
headerProvider := newHeaderProviderMock()
|
|
frostfsidProvider := newFrostfsIDProviderMock(t)
|
|
|
|
cnr := newContainerIDSDK(t, containerID)
|
|
obj := newObjectIDSDK(t, &objectID)
|
|
|
|
ls := inmemory.NewInmemoryLocalStorage()
|
|
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
|
|
|
ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(containerID), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: methodsOptionalOID},
|
|
Resources: chain.Resources{
|
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
|
},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringEquals,
|
|
Kind: chain.KindResource,
|
|
Key: "attr1",
|
|
Value: "value",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MatchType: chain.MatchTypeFirstMatch,
|
|
})
|
|
|
|
node1Key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
node1 := netmapSDK.NodeInfo{}
|
|
node1.SetPublicKey(node1Key.PublicKey().Bytes())
|
|
netmap := &netmapSDK.NetMap{}
|
|
netmap.SetEpoch(100)
|
|
netmap.SetNodes([]netmapSDK.NodeInfo{node1})
|
|
|
|
nm := &netmapStub{
|
|
currentEpoch: 100,
|
|
netmaps: map[uint64]*netmapSDK.NetMap{
|
|
99: netmap,
|
|
100: netmap,
|
|
},
|
|
}
|
|
|
|
cont := containerSDK.Container{}
|
|
cont.Init()
|
|
pp := netmapSDK.PlacementPolicy{}
|
|
require.NoError(t, pp.DecodeString("REP 1"))
|
|
cont.SetPlacementPolicy(pp)
|
|
cs := &testContainerSource{
|
|
containers: map[cid.ID]*container.Container{
|
|
cnr: {
|
|
Value: cont,
|
|
},
|
|
},
|
|
}
|
|
|
|
checker := NewChecker(ls, ms, headerProvider, frostfsidProvider, nm, &stMock{}, cs, node1Key.PublicKey().Bytes())
|
|
|
|
ecParentID := oidtest.ID()
|
|
chunkHeader := newHeaderObjectSDK(cnr, obj, nil).ToV2().GetHeader()
|
|
ecHeader := object.ECHeader{
|
|
Index: 1,
|
|
Total: 5,
|
|
Parent: &refs.ObjectID{},
|
|
}
|
|
chunkHeader.SetEC(&ecHeader)
|
|
ecParentID.WriteToV2(ecHeader.Parent)
|
|
|
|
parentHeader := newHeaderObjectSDK(cnr, &ecParentID, &headerObjectSDKParams{
|
|
attributes: []struct {
|
|
key string
|
|
val string
|
|
}{
|
|
{
|
|
key: "attr1",
|
|
val: "value",
|
|
},
|
|
},
|
|
})
|
|
headerProvider.addHeader(cnr, ecParentID, parentHeader)
|
|
|
|
t.Run("access denied for container node", func(t *testing.T) {
|
|
prm := Prm{
|
|
Method: nativeschema.MethodPutObject,
|
|
Container: cnr,
|
|
Object: obj,
|
|
Role: role,
|
|
SenderKey: senderKey,
|
|
Header: chunkHeader,
|
|
SoftAPECheck: true,
|
|
}
|
|
|
|
err = checker.CheckAPE(context.Background(), prm)
|
|
require.Error(t, err)
|
|
})
|
|
t.Run("access allowed for non container node", func(t *testing.T) {
|
|
otherKey, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
checker = NewChecker(ls, ms, headerProvider, frostfsidProvider, nm, &stMock{}, cs, otherKey.PublicKey().Bytes())
|
|
prm := Prm{
|
|
Method: nativeschema.MethodPutObject,
|
|
Container: cnr,
|
|
Object: obj,
|
|
Role: nativeschema.PropertyValueContainerRoleOthers,
|
|
SenderKey: senderKey,
|
|
Header: chunkHeader,
|
|
SoftAPECheck: true,
|
|
}
|
|
|
|
err = checker.CheckAPE(context.Background(), prm)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|