forked from TrueCloudLab/frostfs-node
[#1216] ape: Introduce BearerChainFeedRouter
* Unlike default chain router, `BearerChainFedRouter` performs checks for overrides defined in the bearer token; * Add unit-test for the introduced router. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
8eb591d668
commit
47bcd346d3
3 changed files with 259 additions and 25 deletions
94
pkg/ape/router/bearer_overrides.go
Normal file
94
pkg/ape/router/bearer_overrides.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
)
|
||||
|
||||
func newTarget(ct ape.ChainTarget) (policyengine.Target, error) {
|
||||
var target policyengine.Target
|
||||
switch ct.TargetType {
|
||||
case ape.TargetTypeContainer:
|
||||
var cid cidSDK.ID
|
||||
err := cid.DecodeString(ct.Name)
|
||||
if err != nil {
|
||||
return target, fmt.Errorf("invalid cid format: %s", target.Name)
|
||||
}
|
||||
target.Type = policyengine.Container
|
||||
case ape.TargetTypeGroup:
|
||||
target.Type = policyengine.Group
|
||||
case ape.TargetTypeNamespace:
|
||||
target.Type = policyengine.Namespace
|
||||
case ape.TargetTypeUser:
|
||||
target.Type = policyengine.User
|
||||
default:
|
||||
return target, fmt.Errorf("unsupported target type: %v", ct.TargetType)
|
||||
}
|
||||
target.Name = ct.Name
|
||||
return target, nil
|
||||
}
|
||||
|
||||
type morphReaderDecorator struct {
|
||||
policyengine.MorphRuleChainStorageReader
|
||||
|
||||
bearerTokenTarget policyengine.Target
|
||||
|
||||
bearerTokenChains []*chain.Chain
|
||||
}
|
||||
|
||||
func newMorphReaderDecorator(r policyengine.MorphRuleChainStorageReader, override bearer.APEOverride) (*morphReaderDecorator, error) {
|
||||
if r == nil {
|
||||
return nil, errors.New("empty morph chain rule reader")
|
||||
}
|
||||
t, err := newTarget(override.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bearerTokenChains := make([]*chain.Chain, len(override.Chains))
|
||||
for i := range override.Chains {
|
||||
chain := new(chain.Chain)
|
||||
if err := chain.DecodeBytes(override.Chains[i].Raw); err != nil {
|
||||
return nil, fmt.Errorf("invalid ape chain: %w", err)
|
||||
}
|
||||
bearerTokenChains[i] = chain
|
||||
}
|
||||
|
||||
return &morphReaderDecorator{
|
||||
MorphRuleChainStorageReader: r,
|
||||
bearerTokenTarget: t,
|
||||
bearerTokenChains: bearerTokenChains,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *morphReaderDecorator) ListMorphRuleChains(name chain.Name, target policyengine.Target) ([]*chain.Chain, error) {
|
||||
if len(m.bearerTokenChains) > 0 && m.bearerTokenTarget.Type == target.Type {
|
||||
if m.bearerTokenTarget.Name != target.Name {
|
||||
return nil, fmt.Errorf("unexpected bearer token target: %s", m.bearerTokenTarget.Name)
|
||||
}
|
||||
return m.bearerTokenChains, nil
|
||||
}
|
||||
return m.MorphRuleChainStorageReader.ListMorphRuleChains(name, target)
|
||||
}
|
||||
|
||||
// BearerChainFeedRouter creates a chain router emplacing bearer token rule chains.
|
||||
// Bearer token chains override only container target chains within Policy contract. This means the order of checking
|
||||
// is as follows:
|
||||
//
|
||||
// 1. Local overrides;
|
||||
// 2. Policy contract chains for a namespace target (as namespace chains have higher priority);
|
||||
// 3. Bearer token chains for a container target - if they're not defined, then it checks Policy contract chains;
|
||||
// 4. Checks for the remaining targets.
|
||||
func BearerChainFeedRouter(localOverrideStorage policyengine.LocalOverrideStorage, morphChainStorage policyengine.MorphRuleChainStorageReader, override bearer.APEOverride) (policyengine.ChainRouter, error) {
|
||||
mr, err := newMorphReaderDecorator(morphChainStorage, override)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create morph reader with bearer override error: %w", err)
|
||||
}
|
||||
return policyengine.NewDefaultChainRouterWithLocalOverrides(mr, localOverrideStorage), nil
|
||||
}
|
165
pkg/ape/router/bearer_overrides_test.go
Normal file
165
pkg/ape/router/bearer_overrides_test.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
package router_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/router"
|
||||
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
bearerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"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"
|
||||
resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
container = "67ETTZzbzJC6WxdQhHHHsJNCttVMBqYrSoFaUFVDNfiX"
|
||||
rootNs = ""
|
||||
)
|
||||
|
||||
var (
|
||||
allowBySourceIP = &chain.Chain{
|
||||
Rules: []chain.Rule{
|
||||
{
|
||||
Status: chain.Allow,
|
||||
Actions: chain.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: chain.Resources{Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container)}},
|
||||
Condition: []chain.Condition{
|
||||
{
|
||||
Op: chain.CondStringEquals,
|
||||
Kind: chain.KindRequest,
|
||||
Key: "SourceIP",
|
||||
Value: "10.122.1.20",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
denyBySourceIP = &chain.Chain{
|
||||
Rules: []chain.Rule{
|
||||
{
|
||||
Status: chain.AccessDenied,
|
||||
Actions: chain.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: chain.Resources{Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container)}},
|
||||
Condition: []chain.Condition{
|
||||
{
|
||||
Op: chain.CondStringEquals,
|
||||
Kind: chain.KindRequest,
|
||||
Key: "SourceIP",
|
||||
Value: "10.122.1.20",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestBearerChainFedRouter(t *testing.T) {
|
||||
t.Run("no bearer token overrides", func(t *testing.T) {
|
||||
inmem := inmemory.NewInMemoryLocalOverrides()
|
||||
|
||||
inmem.LocalStorage().AddOverride(chain.Ingress, engine.ContainerTarget(container), denyBySourceIP)
|
||||
inmem.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), allowBySourceIP)
|
||||
|
||||
_, err := router.BearerChainFeedRouter(inmem.LocalStorage(), inmem.MorphRuleChainStorage(), bearerSDK.APEOverride{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("allow by container with deny by bearer overrides", func(t *testing.T) {
|
||||
inmem := inmemory.NewInMemoryLocalOverrides()
|
||||
|
||||
inmem.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), allowBySourceIP)
|
||||
|
||||
bt := bearerSDK.APEOverride{
|
||||
Target: apeSDK.ChainTarget{
|
||||
TargetType: apeSDK.TargetTypeContainer,
|
||||
Name: container,
|
||||
},
|
||||
Chains: []apeSDK.Chain{{
|
||||
Raw: denyBySourceIP.Bytes(),
|
||||
}},
|
||||
}
|
||||
|
||||
r, err := router.BearerChainFeedRouter(inmem.LocalStorage(), inmem.MorphRuleChainStorage(), bt)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := resourcetest.NewRequest(nativeschema.MethodPutObject,
|
||||
resourcetest.NewResource(fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container), map[string]string{}),
|
||||
map[string]string{
|
||||
"SourceIP": "10.122.1.20",
|
||||
"Actor": "someOwner",
|
||||
},
|
||||
)
|
||||
|
||||
st, found, err := r.IsAllowed(chain.Ingress, engine.NewRequestTarget(rootNs, container), req)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
require.Equal(t, st, chain.AccessDenied)
|
||||
})
|
||||
t.Run("allow by namespace with deny by bearer overrides", func(t *testing.T) {
|
||||
inmem := inmemory.NewInMemoryLocalOverrides()
|
||||
|
||||
inmem.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), allowBySourceIP)
|
||||
inmem.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(rootNs), allowBySourceIP)
|
||||
|
||||
bt := bearerSDK.APEOverride{
|
||||
Target: apeSDK.ChainTarget{
|
||||
TargetType: apeSDK.TargetTypeContainer,
|
||||
Name: container,
|
||||
},
|
||||
Chains: []apeSDK.Chain{{
|
||||
Raw: denyBySourceIP.Bytes(),
|
||||
}},
|
||||
}
|
||||
|
||||
r, err := router.BearerChainFeedRouter(inmem.LocalStorage(), inmem.MorphRuleChainStorage(), bt)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := resourcetest.NewRequest(nativeschema.MethodPutObject,
|
||||
resourcetest.NewResource(fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container), map[string]string{}),
|
||||
map[string]string{
|
||||
"SourceIP": "10.122.1.20",
|
||||
"Actor": "someOwner",
|
||||
},
|
||||
)
|
||||
|
||||
st, found, err := r.IsAllowed(chain.Ingress, engine.NewRequestTarget(rootNs, container), req)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
require.Equal(t, st, chain.AccessDenied)
|
||||
})
|
||||
t.Run("deny by namespace with allow by bearer overrides", func(t *testing.T) {
|
||||
inmem := inmemory.NewInMemoryLocalOverrides()
|
||||
|
||||
inmem.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(rootNs), denyBySourceIP)
|
||||
|
||||
bt := bearerSDK.APEOverride{
|
||||
Target: apeSDK.ChainTarget{
|
||||
TargetType: apeSDK.TargetTypeContainer,
|
||||
Name: container,
|
||||
},
|
||||
Chains: []apeSDK.Chain{{
|
||||
Raw: allowBySourceIP.Bytes(),
|
||||
}},
|
||||
}
|
||||
|
||||
r, err := router.BearerChainFeedRouter(inmem.LocalStorage(), inmem.MorphRuleChainStorage(), bt)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := resourcetest.NewRequest(nativeschema.MethodPutObject,
|
||||
resourcetest.NewResource(fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container), map[string]string{}),
|
||||
map[string]string{
|
||||
"SourceIP": "10.122.1.20",
|
||||
"Actor": "someOwner",
|
||||
},
|
||||
)
|
||||
|
||||
st, found, err := r.IsAllowed(chain.Ingress, engine.NewRequestTarget(rootNs, container), req)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
require.Equal(t, st, chain.AccessDenied)
|
||||
})
|
||||
}
|
|
@ -3,37 +3,12 @@ package router
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
apechain "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"
|
||||
)
|
||||
|
||||
func newTarget(ct ape.ChainTarget) (engine.Target, error) {
|
||||
var target engine.Target
|
||||
switch ct.TargetType {
|
||||
case ape.TargetTypeContainer:
|
||||
var cid cidSDK.ID
|
||||
err := cid.DecodeString(ct.Name)
|
||||
if err != nil {
|
||||
return target, fmt.Errorf("invalid cid format: %s", target.Name)
|
||||
}
|
||||
target.Type = engine.Container
|
||||
case ape.TargetTypeGroup:
|
||||
target.Type = engine.Group
|
||||
case ape.TargetTypeNamespace:
|
||||
target.Type = engine.Namespace
|
||||
case ape.TargetTypeUser:
|
||||
target.Type = engine.User
|
||||
default:
|
||||
return target, fmt.Errorf("unsupported target type: %v", ct.TargetType)
|
||||
}
|
||||
target.Name = ct.Name
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// SingleUseRouterWithBearerTokenChains creates chain router with inmemory storage implementation and
|
||||
// fed with APE chains defined in Bearer token.
|
||||
func SingleUseRouterWithBearerTokenChains(overrides []bearer.APEOverride) (engine.ChainRouter, error) {
|
||||
|
|
Loading…
Reference in a new issue