forked from TrueCloudLab/policy-engine
334 lines
12 KiB
Go
334 lines
12 KiB
Go
package inmemory
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAddRootOverrides(t *testing.T) {
|
|
s := NewInMemoryLocalOverrides()
|
|
|
|
target := engine.NamespaceTarget("")
|
|
|
|
id, err := s.LocalStorage().AddOverride(chain.S3, target, &chain.Chain{
|
|
Rules: []chain.Rule{{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: []string{"s3:PutObject"}},
|
|
Resources: chain.Resources{Names: []string{"*"}},
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
res, err := s.LocalStorage().ListOverrides(chain.S3, target)
|
|
require.NoError(t, err)
|
|
require.Len(t, res, 1)
|
|
require.Equal(t, string(id), string(res[0].ID))
|
|
}
|
|
|
|
func TestInmemory(t *testing.T) {
|
|
const (
|
|
object = "native::object::abc/xyz"
|
|
container = "native::object::abc/*"
|
|
namespace = "Tenant1"
|
|
namespace2 = "Tenant2"
|
|
actor1 = "owner1"
|
|
actor2 = "owner2"
|
|
)
|
|
|
|
s := NewInMemoryLocalOverrides()
|
|
|
|
// Object which was put via S3.
|
|
res := resourcetest.NewResource(object, map[string]string{"FromS3": "true"})
|
|
// Request initiating from the trusted subnet and actor.
|
|
reqGood := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
"SourceIP": "10.1.1.12",
|
|
"Actor": actor1,
|
|
})
|
|
|
|
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTargetWithNamespace(namespace), reqGood)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
|
|
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{ // Restrict to remove ANY object from the namespace.
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
},
|
|
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
Any: true,
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringNotLike,
|
|
Object: chain.ObjectRequest,
|
|
Key: "SourceIP",
|
|
Value: "10.1.1.*",
|
|
},
|
|
{
|
|
Op: chain.CondStringNotEquals,
|
|
Object: chain.ObjectRequest,
|
|
Key: "Actor",
|
|
Value: actor1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
_, it, err := s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace))
|
|
|
|
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{ // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz".
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
|
|
Resources: chain.Resources{Inverted: true, Names: []string{object}},
|
|
},
|
|
},
|
|
})
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
|
|
|
|
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: []string{"native::object::get"}},
|
|
Resources: chain.Resources{Names: []string{"native::object::abc/*"}},
|
|
Condition: []chain.Condition{
|
|
{
|
|
Op: chain.CondStringEquals,
|
|
Object: chain.ObjectResource,
|
|
Key: "Department",
|
|
Value: "HR",
|
|
},
|
|
{
|
|
Op: chain.CondStringEquals,
|
|
Object: chain.ObjectRequest,
|
|
Key: "Actor",
|
|
Value: actor2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(container))
|
|
|
|
t.Run("bad subnet, namespace deny", func(t *testing.T) {
|
|
// Request initiating from the untrusted subnet.
|
|
reqBadIP := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
"SourceIP": "10.122.1.20",
|
|
"Actor": actor1,
|
|
})
|
|
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadIP)
|
|
require.Equal(t, chain.AccessDenied, status)
|
|
require.True(t, ok)
|
|
})
|
|
t.Run("bad actor, namespace deny", func(t *testing.T) {
|
|
// Request initiating from the untrusted actor.
|
|
reqBadActor := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
"SourceIP": "10.1.1.13",
|
|
"Actor": actor2,
|
|
})
|
|
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadActor)
|
|
require.Equal(t, chain.AccessDenied, status)
|
|
require.True(t, ok)
|
|
})
|
|
t.Run("bad object, container deny", func(t *testing.T) {
|
|
objGood := resourcetest.NewResource("native::object::abc/id1", map[string]string{"Department": "HR"})
|
|
objBadAttr := resourcetest.NewResource("native::object::abc/id2", map[string]string{"Department": "Support"})
|
|
|
|
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), resourcetest.NewRequest("native::object::get", objGood, map[string]string{
|
|
"SourceIP": "10.1.1.14",
|
|
"Actor": actor2,
|
|
}))
|
|
require.Equal(t, chain.Allow, status)
|
|
require.True(t, ok)
|
|
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), resourcetest.NewRequest("native::object::get", objBadAttr, map[string]string{
|
|
"SourceIP": "10.1.1.14",
|
|
"Actor": actor2,
|
|
}))
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
})
|
|
t.Run("bad operation, namespace deny", func(t *testing.T) {
|
|
// Request with the forbidden operation.
|
|
reqBadOperation := resourcetest.NewRequest("native::object::delete", res, map[string]string{
|
|
"SourceIP": "10.1.1.12",
|
|
"Actor": actor1,
|
|
})
|
|
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadOperation)
|
|
require.Equal(t, chain.AccessDenied, status)
|
|
require.True(t, ok)
|
|
})
|
|
t.Run("inverted rules", func(t *testing.T) {
|
|
req := resourcetest.NewRequest("native::object::put", resourcetest.NewResource(object, nil), nil)
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
|
|
req = resourcetest.NewRequest("native::object::put", resourcetest.NewResource("native::object::cba/def", nil), nil)
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
require.Equal(t, chain.AccessDenied, status)
|
|
require.True(t, ok)
|
|
|
|
req = resourcetest.NewRequest("native::object::get", resourcetest.NewResource("native::object::cba/def", nil), nil)
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
})
|
|
t.Run("good", func(t *testing.T) {
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
|
|
t.Run("quota on a different container", func(t *testing.T) {
|
|
s.LocalStorage().AddOverride(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
|
|
Rules: []chain.Rule{{
|
|
Status: chain.QuotaLimitReached,
|
|
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
Resources: chain.Resources{Names: []string{"native::object::cba/*"}},
|
|
}},
|
|
})
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(container))
|
|
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
})
|
|
|
|
var quotaRuleChainID chain.ID
|
|
t.Run("quota on the request container", func(t *testing.T) {
|
|
quotaRuleChainID, _ = s.LocalStorage().AddOverride(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
|
|
Rules: []chain.Rule{{
|
|
Status: chain.QuotaLimitReached,
|
|
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
Resources: chain.Resources{Names: []string{"native::object::abc/*"}},
|
|
}},
|
|
})
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(container))
|
|
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
|
|
require.Equal(t, chain.QuotaLimitReached, status)
|
|
require.True(t, ok)
|
|
})
|
|
t.Run("removed quota on the request container", func(t *testing.T) {
|
|
err := s.LocalStorage().RemoveOverride(chain.Ingress, engine.ContainerTarget(container), quotaRuleChainID)
|
|
require.NoError(t, err)
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
|
|
|
|
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
|
|
require.NoError(t, err)
|
|
itemStacksEqual(t, it.Values, toStackItems(container))
|
|
|
|
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
|
|
require.Equal(t, chain.NoRuleFound, status)
|
|
require.False(t, ok)
|
|
})
|
|
})
|
|
|
|
t.Run("remove all", func(t *testing.T) {
|
|
s := NewInMemoryLocalOverrides()
|
|
_, _, err := s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
|
|
Resources: chain.Resources{Inverted: true, Names: []string{object}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
|
|
Resources: chain.Resources{Inverted: true, Names: []string{object}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.AccessDenied,
|
|
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
|
|
Resources: chain.Resources{Inverted: true, Names: []string{object}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
_, _, err = s.MorphRuleChainStorage().RemoveMorphRuleChainsByTarget(chain.Ingress, engine.NamespaceTarget(namespace2))
|
|
require.NoError(t, err)
|
|
chains, err := s.MorphRuleChainStorage().ListMorphRuleChains(chain.Ingress, engine.NamespaceTarget(namespace2))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(chains))
|
|
chains, err = s.MorphRuleChainStorage().ListMorphRuleChains(chain.Ingress, engine.NamespaceTarget(namespace))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(chains))
|
|
})
|
|
}
|
|
|
|
func itemStacksEqual(t *testing.T, got []stackitem.Item, expected []stackitem.Item) {
|
|
next:
|
|
for _, exp := range expected {
|
|
expBytes, err := exp.TryBytes()
|
|
require.NoError(t, err)
|
|
for _, v := range got {
|
|
vBytes, err := v.TryBytes()
|
|
require.NoError(t, err)
|
|
if bytes.Equal(vBytes, expBytes) {
|
|
continue next
|
|
}
|
|
}
|
|
t.Fatalf("not found %s", exp)
|
|
}
|
|
}
|
|
|
|
func toStackItems(names ...string) []stackitem.Item {
|
|
var items []stackitem.Item
|
|
for _, name := range names {
|
|
items = append(items, stackitem.NewByteArray([]byte(name)))
|
|
}
|
|
return items
|
|
}
|