[#1157] object: Make APE checker use Bearer-token's APE overrides

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-05-29 11:11:45 +03:00 committed by Evgenii Stratonikov
parent 4edff5eea6
commit 04a3f891fd
6 changed files with 358 additions and 168 deletions

View file

@ -2,6 +2,7 @@ package ape
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
@ -12,6 +13,8 @@ import (
"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"
@ -228,170 +231,249 @@ func (f *frostfsIDProviderMock) GetSubjectExtended(key util.Uint160) (*client.Su
return v, nil
}
var apeCheckTestCases = []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,
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: "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,
},
}
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()
r := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
checker := NewChecker(r, 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 []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,
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: "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,
},
} {
for _, test := range apeCheckTestCases {
t.Run(test.name, func(t *testing.T) {
for _, method := range test.methods {
t.Run(method, func(t *testing.T) {
@ -411,7 +493,7 @@ func TestAPECheck(t *testing.T) {
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
checker := NewChecker(router, headerProvider, frostfsidProvider, nil, nil, nil)
checker := NewChecker(router, headerProvider, frostfsidProvider, nil, &stMock{}, nil, nil)
prm := Prm{
Method: method,
@ -544,7 +626,7 @@ func TestPutECChunk(t *testing.T) {
},
}
checker := NewChecker(router, headerProvider, frostfsidProvider, nm, cs, node1Key.PublicKey().Bytes())
checker := NewChecker(router, headerProvider, frostfsidProvider, nm, &stMock{}, cs, node1Key.PublicKey().Bytes())
ecParentID := oidtest.ID()
chunkHeader := newHeaderObjectSDK(cnr, obj, nil).ToV2().GetHeader()
@ -586,7 +668,7 @@ func TestPutECChunk(t *testing.T) {
t.Run("access allowed for non container node", func(t *testing.T) {
otherKey, err := keys.NewPrivateKey()
require.NoError(t, err)
checker = NewChecker(router, headerProvider, frostfsidProvider, nm, cs, otherKey.PublicKey().Bytes())
checker = NewChecker(router, headerProvider, frostfsidProvider, nm, &stMock{}, cs, otherKey.PublicKey().Bytes())
prm := Prm{
Method: nativeschema.MethodPutObject,
Container: cnr,