forked from TrueCloudLab/frostfs-node
[#872] object: Introduce APE middlewar for object service
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
e43609c616
commit
c8baf76fae
13 changed files with 1456 additions and 175 deletions
348
pkg/services/object/ape/checker_test.go
Normal file
348
pkg/services/object/ape/checker_test.go
Normal file
|
@ -0,0 +1,348 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"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"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"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) (*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(t *testing.T, 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"
|
||||
|
||||
role = "Container"
|
||||
|
||||
senderKey = hex.EncodeToString([]byte{1, 0, 0, 1})
|
||||
)
|
||||
|
||||
func TestAPECheck(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
container string
|
||||
object *string
|
||||
methods []string
|
||||
header testHeader
|
||||
containerRules []chain.Rule
|
||||
expectAPEErr bool
|
||||
}{
|
||||
{
|
||||
name: "oid required requests are allowed",
|
||||
container: containerID,
|
||||
object: stringPtr(objectID),
|
||||
methods: methodsRequiredOID,
|
||||
},
|
||||
{
|
||||
name: "oid optional requests are allowed",
|
||||
container: containerID,
|
||||
methods: methodsOptionalOID,
|
||||
},
|
||||
{
|
||||
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,
|
||||
Object: chain.ObjectResource,
|
||||
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,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||
Value: senderKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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,
|
||||
Object: chain.ObjectResource,
|
||||
Key: nativeschema.PropertyKeyObjectPayloadLength,
|
||||
Value: "1000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectAPEErr: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, method := range test.methods {
|
||||
t.Run(method, func(t *testing.T) {
|
||||
headerProvider := newHeaderProviderMock()
|
||||
|
||||
cnr := newContainerIDSDK(t, test.container)
|
||||
obj := newObjectIDSDK(t, test.object)
|
||||
|
||||
ls := inmemory.NewInmemoryLocalStorage()
|
||||
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
||||
|
||||
ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
|
||||
Rules: test.containerRules,
|
||||
})
|
||||
|
||||
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
|
||||
|
||||
checker := NewChecker(router, headerProvider)
|
||||
|
||||
prm := Prm{
|
||||
Method: method,
|
||||
Container: cnr,
|
||||
Object: obj,
|
||||
Role: role,
|
||||
SenderKey: senderKey,
|
||||
}
|
||||
|
||||
var headerObjSDK *objectSDK.Object
|
||||
if test.header.headerObjSDK != nil {
|
||||
headerObjSDK = newHeaderObjectSDK(t, 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 {
|
||||
aErr := apeErr(method, chain.AccessDenied)
|
||||
require.ErrorAs(t, err, &aErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue