[#7] engine: Revise storage interface #15
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -52,41 +52,41 @@ const (
|
|||
CondArnNotLike string = "ArnNotLike"
|
||||
)
|
||||
|
||||
func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||
func (p Policy) ToChain() (*chain.Chain, error) {
|
||||
if err := p.Validate(GeneralPolicyType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chain policyengine.Chain
|
||||
var ch chain.Chain
|
||||
|
||||
for _, statement := range p.Statement {
|
||||
status := policyengine.AccessDenied
|
||||
status := chain.AccessDenied
|
||||
if statement.Effect == AllowEffect {
|
||||
status = policyengine.Allow
|
||||
status = chain.Allow
|
||||
}
|
||||
|
||||
var principals []string
|
||||
var op policyengine.ConditionType
|
||||
var op chain.ConditionType
|
||||
statementPrincipal, inverted := statement.principal()
|
||||
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
||||
principals = []string{Wildcard}
|
||||
op = policyengine.CondStringLike
|
||||
op = chain.CondStringLike
|
||||
} else {
|
||||
for _, principal := range statementPrincipal {
|
||||
principals = append(principals, principal...)
|
||||
}
|
||||
|
||||
op = policyengine.CondStringEquals
|
||||
op = chain.CondStringEquals
|
||||
if inverted {
|
||||
op = policyengine.CondStringNotEquals
|
||||
op = chain.CondStringNotEquals
|
||||
}
|
||||
}
|
||||
|
||||
var conditions []policyengine.Condition
|
||||
var conditions []chain.Condition
|
||||
for _, principal := range principals {
|
||||
conditions = append(conditions, policyengine.Condition{
|
||||
conditions = append(conditions, chain.Condition{
|
||||
Op: op,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: RequestOwnerProperty,
|
||||
Value: principal,
|
||||
})
|
||||
|
@ -99,49 +99,49 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
|||
conditions = append(conditions, conds...)
|
||||
|
||||
action, actionInverted := statement.action()
|
||||
ruleAction := policyengine.Actions{Inverted: actionInverted, Names: action}
|
||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: action}
|
||||
|
||||
resource, resourceInverted := statement.resource()
|
||||
ruleResource := policyengine.Resources{Inverted: resourceInverted, Names: resource}
|
||||
ruleResource := chain.Resources{Inverted: resourceInverted, Names: resource}
|
||||
|
||||
r := policyengine.Rule{
|
||||
r := chain.Rule{
|
||||
Status: status,
|
||||
Actions: ruleAction,
|
||||
Resources: ruleResource,
|
||||
Any: true,
|
||||
Condition: conditions,
|
||||
}
|
||||
chain.Rules = append(chain.Rules, r)
|
||||
ch.Rules = append(ch.Rules, r)
|
||||
}
|
||||
|
||||
return &chain, nil
|
||||
return &ch, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||
var conditions []policyengine.Condition
|
||||
func (c Conditions) ToChainCondition() ([]chain.Condition, error) {
|
||||
var conditions []chain.Condition
|
||||
|
||||
var convertValue convertFunction
|
||||
|
||||
for op, KVs := range c {
|
||||
var condType policyengine.ConditionType
|
||||
var condType chain.ConditionType
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(op, "String"):
|
||||
convertValue = noConvertFunction
|
||||
switch op {
|
||||
case CondStringEquals:
|
||||
condType = policyengine.CondStringEquals
|
||||
condType = chain.CondStringEquals
|
||||
case CondStringNotEquals:
|
||||
condType = policyengine.CondStringNotEquals
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondStringEqualsIgnoreCase:
|
||||
condType = policyengine.CondStringEqualsIgnoreCase
|
||||
condType = chain.CondStringEqualsIgnoreCase
|
||||
case CondStringNotEqualsIgnoreCase:
|
||||
condType = policyengine.CondStringNotEqualsIgnoreCase
|
||||
condType = chain.CondStringNotEqualsIgnoreCase
|
||||
case CondStringLike:
|
||||
condType = policyengine.CondStringLike
|
||||
condType = chain.CondStringLike
|
||||
case CondStringNotLike:
|
||||
condType = policyengine.CondStringNotLike
|
||||
condType = chain.CondStringNotLike
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
|
@ -149,13 +149,13 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
|||
convertValue = noConvertFunction
|
||||
switch op {
|
||||
case CondArnEquals:
|
||||
condType = policyengine.CondStringEquals
|
||||
condType = chain.CondStringEquals
|
||||
case CondArnNotEquals:
|
||||
condType = policyengine.CondStringNotEquals
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondArnLike:
|
||||
condType = policyengine.CondStringLike
|
||||
condType = chain.CondStringLike
|
||||
case CondArnNotLike:
|
||||
condType = policyengine.CondStringNotLike
|
||||
condType = chain.CondStringNotLike
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
|
@ -165,33 +165,33 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
|||
convertValue = dateConvertFunction
|
||||
switch op {
|
||||
case CondDateEquals:
|
||||
condType = policyengine.CondStringEquals
|
||||
condType = chain.CondStringEquals
|
||||
case CondDateNotEquals:
|
||||
condType = policyengine.CondStringNotEquals
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondDateLessThan:
|
||||
condType = policyengine.CondStringLessThan
|
||||
condType = chain.CondStringLessThan
|
||||
case CondDateLessThanEquals:
|
||||
condType = policyengine.CondStringLessThanEquals
|
||||
condType = chain.CondStringLessThanEquals
|
||||
case CondDateGreaterThan:
|
||||
condType = policyengine.CondStringGreaterThan
|
||||
condType = chain.CondStringGreaterThan
|
||||
case CondDateGreaterThanEquals:
|
||||
condType = policyengine.CondStringGreaterThanEquals
|
||||
condType = chain.CondStringGreaterThanEquals
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
case op == CondBool:
|
||||
convertValue = noConvertFunction
|
||||
condType = policyengine.CondStringEqualsIgnoreCase
|
||||
condType = chain.CondStringEqualsIgnoreCase
|
||||
case op == CondIPAddress:
|
||||
// todo consider using converters
|
||||
// "203.0.113.0/24" -> "203.0.113.*",
|
||||
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
|
||||
// or having specific condition type for IP
|
||||
convertValue = noConvertFunction
|
||||
condType = policyengine.CondStringLike
|
||||
condType = chain.CondStringLike
|
||||
case op == CondNotIPAddress:
|
||||
convertValue = noConvertFunction
|
||||
condType = policyengine.CondStringNotLike
|
||||
condType = chain.CondStringNotLike
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
|
@ -203,9 +203,9 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
conditions = append(conditions, policyengine.Condition{
|
||||
conditions = append(conditions, chain.Condition{
|
||||
Op: condType,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: key,
|
||||
Value: converted,
|
||||
})
|
||||
|
|
|
@ -3,7 +3,7 @@ package iam
|
|||
import (
|
||||
"testing"
|
||||
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||
chain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
fyrchik marked this conversation as resolved
Outdated
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -26,22 +26,22 @@ func TestConverters(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||
expected := &chain.Chain{Rules: []chain.Rule{
|
||||
{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
||||
Status: chain.Allow,
|
||||
Actions: chain.Actions{Names: p.Statement[0].Action},
|
||||
Resources: chain.Resources{Names: p.Statement[0].Resource},
|
||||
Any: true,
|
||||
Condition: []policyengine.Condition{
|
||||
Condition: []chain.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: RequestOwnerProperty,
|
||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "s3:RequestObjectTag/Department",
|
||||
Value: "Finance",
|
||||
},
|
||||
|
@ -67,16 +67,16 @@ func TestConverters(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||
expected := &chain.Chain{Rules: []chain.Rule{
|
||||
{
|
||||
Status: policyengine.AccessDenied,
|
||||
Actions: policyengine.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
||||
Resources: policyengine.Resources{Inverted: true, Names: p.Statement[0].NotResource},
|
||||
Status: chain.AccessDenied,
|
||||
Actions: chain.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
||||
Resources: chain.Resources{Inverted: true, Names: p.Statement[0].NotResource},
|
||||
Any: true,
|
||||
Condition: []policyengine.Condition{
|
||||
Condition: []chain.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: RequestOwnerProperty,
|
||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
||||
},
|
||||
|
@ -154,136 +154,136 @@ func TestConverters(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||
expected := &chain.Chain{Rules: []chain.Rule{
|
||||
{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
||||
Status: chain.Allow,
|
||||
Actions: chain.Actions{Names: p.Statement[0].Action},
|
||||
Resources: chain.Resources{Names: p.Statement[0].Resource},
|
||||
Any: true,
|
||||
Condition: []policyengine.Condition{
|
||||
Condition: []chain.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: RequestOwnerProperty,
|
||||
Value: "*",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key1",
|
||||
Value: "val0",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key1",
|
||||
Value: "val1",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key2",
|
||||
Value: "val2",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEqualsIgnoreCase,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEqualsIgnoreCase,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key3",
|
||||
Value: "val3",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEqualsIgnoreCase,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotEqualsIgnoreCase,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key4",
|
||||
Value: "val4",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key5",
|
||||
Value: "val5",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key6",
|
||||
Value: "val6",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key7",
|
||||
Value: "1136189045",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key8",
|
||||
Value: "1136214245",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringLessThan,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLessThan,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key9",
|
||||
Value: "1136192645",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringLessThanEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLessThanEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key10",
|
||||
Value: "1136203445",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringGreaterThan,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringGreaterThan,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key11",
|
||||
Value: "1136217845",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringGreaterThanEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringGreaterThanEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key12",
|
||||
Value: "1136225045",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEqualsIgnoreCase,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEqualsIgnoreCase,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key13",
|
||||
Value: "True",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key14",
|
||||
Value: "val14",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key15",
|
||||
Value: "val15",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key16",
|
||||
Value: "val16",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key17",
|
||||
Value: "val17",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotEquals,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key18",
|
||||
Value: "val18",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotLike,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Op: chain.CondStringNotLike,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: "key19",
|
||||
Value: "val19",
|
||||
},
|
||||
|
|
106
inmemory.go
|
@ -1,106 +0,0 @@
|
|||
package policyengine
|
||||
|
||||
type inmemory struct {
|
||||
namespace map[Name][]chain
|
||||
resource map[Name][]chain
|
||||
local map[Name][]*Chain
|
||||
}
|
||||
|
||||
type chain struct {
|
||||
object string
|
||||
chain *Chain
|
||||
}
|
||||
|
||||
// NewInMemory returns new inmemory instance of chain storage.
|
||||
func NewInMemory() CachedChainStorage {
|
||||
return &inmemory{
|
||||
namespace: make(map[Name][]chain),
|
||||
resource: make(map[Name][]chain),
|
||||
local: make(map[Name][]*Chain),
|
||||
}
|
||||
}
|
||||
|
||||
// IsAllowed implements the Engine interface.
|
||||
func (s *inmemory) IsAllowed(name Name, namespace string, r Request) (Status, bool) {
|
||||
var ruleFound bool
|
||||
if local, ok := s.local[name]; ok {
|
||||
for _, c := range local {
|
||||
if status, matched := c.Match(r); matched && status != Allow {
|
||||
return status, true
|
||||
}
|
||||
}
|
||||
}
|
||||
if cs, ok := s.namespace[name]; ok {
|
||||
status, ok := matchArray(cs, namespace, r)
|
||||
if ok && status != Allow {
|
||||
return status, true
|
||||
}
|
||||
ruleFound = ruleFound || ok
|
||||
}
|
||||
if cs, ok := s.resource[name]; ok {
|
||||
status, ok := matchArray(cs, r.Resource().Name(), r)
|
||||
if ok {
|
||||
return status, true
|
||||
}
|
||||
ruleFound = ruleFound || ok
|
||||
}
|
||||
if ruleFound {
|
||||
return Allow, true
|
||||
}
|
||||
return NoRuleFound, false
|
||||
}
|
||||
|
||||
func matchArray(cs []chain, object string, r Request) (Status, bool) {
|
||||
for _, c := range cs {
|
||||
if !globMatch(object, c.object) {
|
||||
continue
|
||||
}
|
||||
if status, matched := c.chain.Match(r); matched {
|
||||
return status, true
|
||||
}
|
||||
}
|
||||
return NoRuleFound, false
|
||||
}
|
||||
|
||||
func (s *inmemory) AddResourceChain(name Name, resource string, c *Chain) {
|
||||
s.resource[name] = append(s.resource[name], chain{resource, c})
|
||||
}
|
||||
|
||||
func (s *inmemory) AddNameSpaceChain(name Name, namespace string, c *Chain) {
|
||||
s.namespace[name] = append(s.namespace[name], chain{namespace, c})
|
||||
}
|
||||
|
||||
func (s *inmemory) AddOverride(name Name, c *Chain) {
|
||||
s.local[name] = append(s.local[name], c)
|
||||
}
|
||||
|
||||
func (s *inmemory) GetOverride(name Name, chainID ChainID) (chain *Chain, found bool) {
|
||||
chains := s.local[name]
|
||||
|
||||
for _, chain = range chains {
|
||||
if chain.ID == chainID {
|
||||
found = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inmemory) RemoveOverride(name Name, chainID ChainID) (found bool) {
|
||||
chains := s.local[name]
|
||||
|
||||
for i, chain := range chains {
|
||||
if chain.ID == chainID {
|
||||
s.local[name] = append(chains[:i], chains[i+1:]...)
|
||||
found = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inmemory) ListOverrides(name Name) []*Chain {
|
||||
return s.local[name]
|
||||
}
|
193
inmemory_test.go
|
@ -1,193 +0,0 @@
|
|||
package policyengine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInmemory(t *testing.T) {
|
||||
const (
|
||||
object = "native::object::abc/xyz"
|
||||
container = "native::object::abc/*"
|
||||
namespace = "Tenant1"
|
||||
namespace2 = "Tenant2"
|
||||
actor1 = "owner1"
|
||||
actor2 = "owner2"
|
||||
)
|
||||
|
||||
s := NewInMemory()
|
||||
|
||||
// Object which was put via S3.
|
||||
res := newResource(object, map[string]string{"FromS3": "true"})
|
||||
// Request initiating from the trusted subnet and actor.
|
||||
reqGood := newRequest("native::object::put", res, map[string]string{
|
||||
"SourceIP": "10.1.1.12",
|
||||
"Actor": actor1,
|
||||
})
|
||||
|
||||
status, ok := s.IsAllowed(Ingress, namespace, reqGood)
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
|
||||
s.AddNameSpaceChain(Ingress, namespace, &Chain{
|
||||
Rules: []Rule{
|
||||
{ // Restrict to remove ANY object from the namespace.
|
||||
Status: AccessDenied,
|
||||
Actions: Actions{Names: []string{"native::object::delete"}},
|
||||
Resources: Resources{Names: []string{"native::object::*"}},
|
||||
},
|
||||
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
||||
Status: AccessDenied,
|
||||
Actions: Actions{Names: []string{"native::object::put"}},
|
||||
Resources: Resources{Names: []string{"native::object::*"}},
|
||||
Any: true,
|
||||
Condition: []Condition{
|
||||
{
|
||||
Op: CondStringNotLike,
|
||||
Object: ObjectRequest,
|
||||
Key: "SourceIP",
|
||||
Value: "10.1.1.*",
|
||||
},
|
||||
{
|
||||
Op: CondStringNotEquals,
|
||||
Object: ObjectRequest,
|
||||
Key: "Actor",
|
||||
Value: actor1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
s.AddNameSpaceChain(Ingress, namespace2, &Chain{
|
||||
Rules: []Rule{
|
||||
{ // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz".
|
||||
Status: AccessDenied,
|
||||
Actions: Actions{Inverted: true, Names: []string{"native::object::get"}},
|
||||
Resources: Resources{Inverted: true, Names: []string{object}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
s.AddResourceChain(Ingress, container, &Chain{
|
||||
Rules: []Rule{
|
||||
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
||||
Status: Allow,
|
||||
Actions: Actions{Names: []string{"native::object::get"}},
|
||||
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||
Condition: []Condition{
|
||||
{
|
||||
Op: CondStringEquals,
|
||||
Object: ObjectResource,
|
||||
Key: "Department",
|
||||
Value: "HR",
|
||||
},
|
||||
{
|
||||
Op: CondStringEquals,
|
||||
Object: ObjectRequest,
|
||||
Key: "Actor",
|
||||
Value: actor2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
t.Run("bad subnet, namespace deny", func(t *testing.T) {
|
||||
// Request initiating from the untrusted subnet.
|
||||
reqBadIP := newRequest("native::object::put", res, map[string]string{
|
||||
"SourceIP": "10.122.1.20",
|
||||
"Actor": actor1,
|
||||
})
|
||||
status, ok := s.IsAllowed(Ingress, namespace, reqBadIP)
|
||||
require.Equal(t, AccessDenied, status)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("bad actor, namespace deny", func(t *testing.T) {
|
||||
// Request initiating from the untrusted actor.
|
||||
reqBadActor := newRequest("native::object::put", res, map[string]string{
|
||||
"SourceIP": "10.1.1.13",
|
||||
"Actor": actor2,
|
||||
})
|
||||
status, ok := s.IsAllowed(Ingress, namespace, reqBadActor)
|
||||
require.Equal(t, AccessDenied, status)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("bad object, container deny", func(t *testing.T) {
|
||||
objGood := newResource("native::object::abc/id1", map[string]string{"Department": "HR"})
|
||||
objBadAttr := newResource("native::object::abc/id2", map[string]string{"Department": "Support"})
|
||||
|
||||
status, ok := s.IsAllowed(Ingress, namespace, newRequest("native::object::get", objGood, map[string]string{
|
||||
"SourceIP": "10.1.1.14",
|
||||
"Actor": actor2,
|
||||
}))
|
||||
require.Equal(t, Allow, status)
|
||||
require.True(t, ok)
|
||||
|
||||
status, ok = s.IsAllowed(Ingress, namespace, newRequest("native::object::get", objBadAttr, map[string]string{
|
||||
"SourceIP": "10.1.1.14",
|
||||
"Actor": actor2,
|
||||
}))
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("bad operation, namespace deny", func(t *testing.T) {
|
||||
// Request with the forbidden operation.
|
||||
reqBadOperation := newRequest("native::object::delete", res, map[string]string{
|
||||
"SourceIP": "10.1.1.12",
|
||||
"Actor": actor1,
|
||||
})
|
||||
status, ok := s.IsAllowed(Ingress, namespace, reqBadOperation)
|
||||
require.Equal(t, AccessDenied, status)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("inverted rules", func(t *testing.T) {
|
||||
req := newRequest("native::object::put", newResource(object, nil), nil)
|
||||
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
|
||||
req = newRequest("native::object::put", newResource("native::object::cba/def", nil), nil)
|
||||
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||
require.Equal(t, AccessDenied, status)
|
||||
require.True(t, ok)
|
||||
|
||||
req = newRequest("native::object::get", newResource("native::object::cba/def", nil), nil)
|
||||
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
|
||||
t.Run("quota on a different container", func(t *testing.T) {
|
||||
s.AddOverride(Ingress, &Chain{
|
||||
Rules: []Rule{{
|
||||
Status: QuotaLimitReached,
|
||||
Actions: Actions{Names: []string{"native::object::put"}},
|
||||
Resources: Resources{Names: []string{"native::object::cba/*"}},
|
||||
}},
|
||||
})
|
||||
|
||||
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||
require.Equal(t, NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("quota on the request container", func(t *testing.T) {
|
||||
s.AddOverride(Ingress, &Chain{
|
||||
Rules: []Rule{{
|
||||
Status: QuotaLimitReached,
|
||||
Actions: Actions{Names: []string{"native::object::put"}},
|
||||
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||
}},
|
||||
})
|
||||
|
||||
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||
require.Equal(t, QuotaLimitReached, status)
|
||||
require.True(t, ok)
|
||||
})
|
||||
})
|
||||
}
|
18
interface.go
|
@ -1,18 +0,0 @@
|
|||
package policyengine
|
||||
|
||||
// CachedChainStorage ...
|
||||
type CachedChainStorage interface {
|
||||
Engine
|
||||
// Adds a policy chain used for all operations with a specific resource.
|
||||
AddResourceChain(name Name, resource string, c *Chain)
|
||||
// Adds a policy chain used for all operations in the namespace.
|
||||
AddNameSpaceChain(name Name, namespace string, c *Chain)
|
||||
// Adds a local policy chain used for all operations with this service.
|
||||
AddOverride(name Name, c *Chain)
|
||||
// Gets the local override with given chain id.
|
||||
GetOverride(name Name, chainID ChainID) (chain *Chain, found bool)
|
||||
// Remove the local override with given chain id.
|
||||
RemoveOverride(name Name, chainID ChainID) (removed bool)
|
||||
// ListOverrides returns the list of local overrides.
|
||||
ListOverrides(name Name) []*Chain
|
||||
}
|
|
@ -1,23 +1,19 @@
|
|||
package policyengine
|
||||
package chain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
||||
)
|
||||
|
||||
// Engine ...
|
||||
type Engine interface {
|
||||
// IsAllowed returns status for the operation after all checks.
|
||||
// The second return value signifies whether a matching rule was found.
|
||||
IsAllowed(name Name, namespace string, r Request) (Status, bool)
|
||||
}
|
||||
|
||||
// ChainID is the ID of rule chain.
|
||||
type ChainID string
|
||||
// ID is the ID of rule chain.
|
||||
type ID string
|
||||
|
||||
type Chain struct {
|
||||
ID ChainID
|
||||
ID ID
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
Why is it a pointer? Empty value can equal to missing ID is is not a valid name. Why is it a pointer? Empty value can equal to missing ID is is not a valid name.
aarifullin
commented
Fixed Fixed
|
||||
|
||||
Rules []Rule
|
||||
}
|
||||
|
@ -136,7 +132,7 @@ func (c ConditionType) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Condition) Match(req Request) bool {
|
||||
func (c *Condition) Match(req resource.Request) bool {
|
||||
var val string
|
||||
switch c.Object {
|
||||
case ObjectResource:
|
||||
|
@ -159,9 +155,9 @@ func (c *Condition) Match(req Request) bool {
|
|||
case CondStringNotEqualsIgnoreCase:
|
||||
return !strings.EqualFold(val, c.Value)
|
||||
case CondStringLike:
|
||||
return globMatch(val, c.Value)
|
||||
return util.GlobMatch(val, c.Value)
|
||||
case CondStringNotLike:
|
||||
return !globMatch(val, c.Value)
|
||||
return !util.GlobMatch(val, c.Value)
|
||||
case CondStringLessThan:
|
||||
return val < c.Value
|
||||
case CondStringLessThanEquals:
|
||||
|
@ -173,10 +169,10 @@ func (c *Condition) Match(req Request) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Rule) Match(req Request) (status Status, matched bool) {
|
||||
func (r *Rule) Match(req resource.Request) (status Status, matched bool) {
|
||||
found := len(r.Resources.Names) == 0
|
||||
for i := range r.Resources.Names {
|
||||
if globMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
||||
if util.GlobMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
@ -185,21 +181,21 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
|
|||
return NoRuleFound, false
|
||||
}
|
||||
for i := range r.Actions.Names {
|
||||
if globMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
|
||||
if util.GlobMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
|
||||
return r.matchCondition(req)
|
||||
}
|
||||
}
|
||||
return NoRuleFound, false
|
||||
}
|
||||
|
||||
func (r *Rule) matchCondition(obj Request) (status Status, matched bool) {
|
||||
func (r *Rule) matchCondition(obj resource.Request) (status Status, matched bool) {
|
||||
if r.Any {
|
||||
return r.matchAny(obj)
|
||||
}
|
||||
return r.matchAll(obj)
|
||||
}
|
||||
|
||||
func (r *Rule) matchAny(obj Request) (status Status, matched bool) {
|
||||
func (r *Rule) matchAny(obj resource.Request) (status Status, matched bool) {
|
||||
for i := range r.Condition {
|
||||
if r.Condition[i].Match(obj) {
|
||||
return r.Status, true
|
||||
|
@ -208,7 +204,7 @@ func (r *Rule) matchAny(obj Request) (status Status, matched bool) {
|
|||
return NoRuleFound, false
|
||||
}
|
||||
|
||||
func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
|
||||
func (r *Rule) matchAll(obj resource.Request) (status Status, matched bool) {
|
||||
for i := range r.Condition {
|
||||
if !r.Condition[i].Match(obj) {
|
||||
return NoRuleFound, false
|
||||
|
@ -217,7 +213,7 @@ func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
|
|||
return r.Status, true
|
||||
}
|
||||
|
||||
func (c *Chain) Match(req Request) (status Status, matched bool) {
|
||||
func (c *Chain) Match(req resource.Request) (status Status, matched bool) {
|
||||
for i := range c.Rules {
|
||||
status, matched := c.Rules[i].Match(req)
|
||||
if matched {
|
|
@ -1,4 +1,4 @@
|
|||
package policyengine
|
||||
package chain
|
||||
|
||||
// Name represents the place in the request lifecycle where policy is applied.
|
||||
type Name string
|
|
@ -1,4 +1,4 @@
|
|||
package policyengine
|
||||
package chain
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -1,4 +1,4 @@
|
|||
package policyengine
|
||||
package chain
|
||||
|
||||
import "fmt"
|
||||
|
101
pkg/engine/chain_router.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
type defaultChainRouter struct {
|
||||
morph MorphRuleChainStorage
|
||||
|
||||
local LocalOverrideStorage
|
||||
}
|
||||
|
||||
func NewDefaultChainRouter(morph MorphRuleChainStorage) ChainRouter {
|
||||
return &defaultChainRouter{
|
||||
morph: morph,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultChainRouterWithLocalOverrides(morph MorphRuleChainStorage, local LocalOverrideStorage) ChainRouter {
|
||||
return &defaultChainRouter{
|
||||
morph: morph,
|
||||
local: local,
|
||||
}
|
||||
}
|
||||
|
||||
func (dr *defaultChainRouter) IsAllowed(name chain.Name, namespace string, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
||||
if dr.local != nil {
|
||||
var localRuleFound bool
|
||||
status, localRuleFound, err = dr.checkLocalOverrides(name, r)
|
||||
if err != nil {
|
||||
return chain.NoRuleFound, false, err
|
||||
} else if localRuleFound {
|
||||
ruleFound = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var namespaceRuleFound bool
|
||||
status, namespaceRuleFound, err = dr.checkNamespaceChains(name, namespace, r)
|
||||
if err != nil {
|
||||
return
|
||||
} else if namespaceRuleFound && status != chain.Allow {
|
||||
ruleFound = true
|
||||
return
|
||||
}
|
||||
|
||||
var cnrRuleFound bool
|
||||
status, cnrRuleFound, err = dr.checkContainerChains(name, r.Resource().Name(), r)
|
||||
if err != nil {
|
||||
return
|
||||
} else if cnrRuleFound && status != chain.Allow {
|
||||
ruleFound = true
|
||||
return
|
||||
}
|
||||
|
||||
status = chain.NoRuleFound
|
||||
if ruleFound = namespaceRuleFound || cnrRuleFound; ruleFound {
|
||||
status = chain.Allow
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dr *defaultChainRouter) checkLocalOverrides(name chain.Name, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
||||
localOverrides, err := dr.local.ListOverrides(name, r.Resource().Name())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, c := range localOverrides {
|
||||
if status, ruleFound = c.Match(r); ruleFound && status != chain.Allow {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dr *defaultChainRouter) checkNamespaceChains(name chain.Name, namespace string, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
||||
namespaceChains, err := dr.morph.ListMorphRuleChains(name, NamespaceTarget(namespace))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, c := range namespaceChains {
|
||||
if status, ruleFound = c.Match(r); ruleFound {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dr *defaultChainRouter) checkContainerChains(name chain.Name, container string, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
||||
containerChains, err := dr.morph.ListMorphRuleChains(name, ContainerTarget(container))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, c := range containerChains {
|
||||
if status, ruleFound = c.Match(r); ruleFound {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
10
pkg/engine/errors.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package engine
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrUnknownTarget = errors.New("unknown target type")
|
||||
ErrChainNotFound = errors.New("chain not found")
|
||||
ErrChainNameNotFound = errors.New("chain name not found")
|
||||
ErrResourceNotFound = errors.New("resource not found")
|
||||
)
|
48
pkg/engine/inmemory/inmemory.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package inmemory
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
type inmemory struct {
|
||||
router engine.ChainRouter
|
||||
|
||||
morph engine.MorphRuleChainStorage
|
||||
|
||||
local engine.LocalOverrideStorage
|
||||
}
|
||||
|
||||
// NewInMemoryLocalOverrides returns new inmemory instance of chain storage with
|
||||
// local overrides manager.
|
||||
func NewInMemoryLocalOverrides() engine.LocalOverrideEngine {
|
||||
morph := NewInmemoryMorphRuleChainStorage()
|
||||
local := NewInmemoryLocalStorage()
|
||||
return &inmemory{
|
||||
router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local),
|
||||
morph: morph,
|
||||
local: local,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInMemory returns new inmemory instance of chain storage.
|
||||
func NewInMemory() engine.Engine {
|
||||
morph := NewInmemoryMorphRuleChainStorage()
|
||||
return &inmemory{
|
||||
router: engine.NewDefaultChainRouter(morph),
|
||||
morph: morph,
|
||||
}
|
||||
}
|
||||
|
||||
func (im *inmemory) LocalStorage() engine.LocalOverrideStorage {
|
||||
return im.local
|
||||
}
|
||||
|
||||
func (im *inmemory) MorphRuleChainStorage() engine.MorphRuleChainStorage {
|
||||
return im.morph
|
||||
}
|
||||
|
||||
func (im *inmemory) IsAllowed(name chain.Name, namespace string, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
||||
return im.router.IsAllowed(name, namespace, r)
|
||||
}
|
206
pkg/engine/inmemory/inmemory_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package inmemory
|
||||
|
||||
import (
|
||||
"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/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInmemory(t *testing.T) {
|
||||
aarifullin marked this conversation as resolved
Outdated
aarifullin
commented
@TrueCloudLab/storage-core-developers Even I tried to move to this file with @TrueCloudLab/storage-core-developers
Even I tried to move to this file with `git mv` it's showed as the new created file because I changed the file in next commits. But I want to **emphasize** I have **not** changed the logical structure of the unittest and changed only signatures
|
||||
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, 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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, namespace, 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, namespace, 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, namespace, 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, namespace, 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, namespace, 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, namespace2, 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, namespace2, 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, namespace2, req)
|
||||
require.Equal(t, chain.NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
status, ok, _ = s.IsAllowed(chain.Ingress, namespace, 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, 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/*"}},
|
||||
}},
|
||||
})
|
||||
|
||||
status, ok, _ = s.IsAllowed(chain.Ingress, namespace, 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, 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/*"}},
|
||||
}},
|
||||
})
|
||||
|
||||
status, ok, _ = s.IsAllowed(chain.Ingress, namespace, 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, container, quotaRuleChainID)
|
||||
require.NoError(t, err)
|
||||
|
||||
status, ok, _ = s.IsAllowed(chain.Ingress, namespace, reqGood)
|
||||
require.Equal(t, chain.NoRuleFound, status)
|
||||
require.False(t, ok)
|
||||
})
|
||||
})
|
||||
}
|
109
pkg/engine/inmemory/local_storage.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package inmemory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
||||
)
|
||||
|
||||
type targetToChain map[string][]*chain.Chain
|
||||
|
||||
type inmemoryLocalStorage struct {
|
||||
usedChainID map[chain.ID]struct{}
|
||||
nameToResourceChains map[chain.Name]targetToChain
|
||||
}
|
||||
|
||||
func NewInmemoryLocalStorage() engine.LocalOverrideStorage {
|
||||
return &inmemoryLocalStorage{
|
||||
usedChainID: map[chain.ID]struct{}{},
|
||||
nameToResourceChains: make(map[chain.Name]targetToChain),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) generateChainID(name chain.Name, resource string) chain.ID {
|
||||
fyrchik
commented
Does chain id have a specific structure? Does chain id have a specific structure?
aarifullin
commented
No, it does not. I glimpsed at the chain names in policy contract unit-tests and found the idea to name a chain like No, it does not.
I glimpsed at the chain names in policy contract unit-tests and found the idea to name a chain like `ingress:resource:name` more useful than a numeric ID. Anyway, `inmemory` implementation will only be used for a while and will be kept only for unit-tests. I think such names are fine and readable
dkirillov
commented
Will we have some in-memory implementation for > Anyway, inmemory implementation will only be used for a while and will be kept only for unit-tests.
Will we have some in-memory implementation for `LocalOverrideStorage` that can be used in s3-gw/node? Or it's expected that s3-gw and node must implement their own `LocalOverrideStorage` ?
aarifullin
commented
Good question. But I am wondering was it implied to define local overrides for
Good question.
It assumed that `LocalOverrideStorage` should be implemented by each service separately. For example, frostfs-node will use DB to keep and retrieve local overrides that doesn't seem applicable for s3-gw.
But I am wondering was it implied to define local overrides for `s3-gw`? If it was not, then probably it makes sense to separate the interface:
```golang
type Engine interface {
ChainRouter
MorphRuleChainStorage() MorphRuleChainStorage
}
type EngineWithLocalOverrides interface {
Engine
LocalStorage() LocalOverrideStorage
}
```
dkirillov
commented
I'm not sure. It seems we will have only some cache layer, but it be a part of > But I am wondering was it implied to define local overrides for s3-gw?
I'm not sure. It seems we will have only some cache layer, but it be a part of `MorphRuleChainStorage` implementation I think
@alexvanin
aarifullin
commented
I have splitted the interface into two: I have splitted the interface into two: `LocalOverrideEngine` and `Engine` (the first one contains the second). Please, check
|
||||
var id chain.ID
|
||||
for {
|
||||
suffix := rand.Uint32() % 100
|
||||
sid := fmt.Sprintf("%s:%s/%d", name, resource, suffix)
|
||||
sid = strings.ReplaceAll(sid, "*", "")
|
||||
sid = strings.ReplaceAll(sid, "/", ":")
|
||||
sid = strings.ReplaceAll(sid, "::", ":")
|
||||
id = chain.ID(sid)
|
||||
_, ok := s.usedChainID[id]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
s.usedChainID[id] = struct{}{}
|
||||
break
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) AddOverride(name chain.Name, resource string, c *chain.Chain) (chain.ID, error) {
|
||||
// AddOverride assigns generated chain ID if it has not been assigned.
|
||||
if c.ID == "" {
|
||||
c.ID = s.generateChainID(name, resource)
|
||||
}
|
||||
if s.nameToResourceChains[name] == nil {
|
||||
s.nameToResourceChains[name] = make(targetToChain)
|
||||
}
|
||||
rc := s.nameToResourceChains[name]
|
||||
rc[resource] = append(rc[resource], c)
|
||||
return c.ID, nil
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) GetOverride(name chain.Name, resource string, chainID chain.ID) (*chain.Chain, error) {
|
||||
if _, ok := s.nameToResourceChains[name]; !ok {
|
||||
return nil, engine.ErrChainNameNotFound
|
||||
}
|
||||
chains, ok := s.nameToResourceChains[name][resource]
|
||||
if !ok {
|
||||
return nil, engine.ErrResourceNotFound
|
||||
}
|
||||
for _, c := range chains {
|
||||
if c.ID == chainID {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
return nil, engine.ErrChainNotFound
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) RemoveOverride(name chain.Name, resource string, chainID chain.ID) error {
|
||||
if _, ok := s.nameToResourceChains[name]; !ok {
|
||||
return engine.ErrChainNameNotFound
|
||||
}
|
||||
chains, ok := s.nameToResourceChains[name][resource]
|
||||
if !ok {
|
||||
return engine.ErrResourceNotFound
|
||||
}
|
||||
for i, c := range chains {
|
||||
if c.ID == chainID {
|
||||
s.nameToResourceChains[name][resource] = append(chains[:i], chains[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return engine.ErrChainNotFound
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) ListOverrides(name chain.Name, resource string) ([]*chain.Chain, error) {
|
||||
rcs, ok := s.nameToResourceChains[name]
|
||||
if !ok {
|
||||
return []*chain.Chain{}, nil
|
||||
}
|
||||
for container, chains := range rcs {
|
||||
if !util.GlobMatch(resource, container) {
|
||||
continue
|
||||
}
|
||||
return chains, nil
|
||||
}
|
||||
return []*chain.Chain{}, nil
|
||||
}
|
||||
|
||||
func (s *inmemoryLocalStorage) DropAllOverrides(name chain.Name) error {
|
||||
s.nameToResourceChains[name] = make(targetToChain)
|
||||
return nil
|
||||
}
|
217
pkg/engine/inmemory/local_storage_test.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package inmemory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
resrc = "native:::object/ExYw/*"
|
||||
chainID = "ingress:ExYw"
|
||||
nonExistChainId = "ingress:LxGyWyL"
|
||||
)
|
||||
|
||||
func testInmemLocalStorage() *inmemoryLocalStorage {
|
||||
return NewInmemoryLocalStorage().(*inmemoryLocalStorage)
|
||||
}
|
||||
|
||||
func TestAddOverride(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
|
||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
||||
Rules: []chain.Rule{
|
||||
{
|
||||
Status: chain.AccessDenied,
|
||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ingressChains, ok := inmem.nameToResourceChains[chain.Ingress]
|
||||
require.True(t, ok)
|
||||
resourceChains, ok := ingressChains[resrc]
|
||||
require.True(t, ok)
|
||||
require.Len(t, resourceChains, 1)
|
||||
require.Len(t, resourceChains[0].Rules, 1)
|
||||
|
||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
||||
Rules: []chain.Rule{
|
||||
{
|
||||
Status: chain.QuotaLimitReached,
|
||||
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
||||
},
|
||||
{
|
||||
Status: chain.AccessDenied,
|
||||
Actions: chain.Actions{Names: []string{"native::object::get"}},
|
||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ingressChains, ok = inmem.nameToResourceChains[chain.Ingress]
|
||||
require.True(t, ok)
|
||||
resourceChains, ok = ingressChains[resrc]
|
||||
require.True(t, ok)
|
||||
require.Len(t, resourceChains, 2)
|
||||
require.Len(t, resourceChains[1].Rules, 2)
|
||||
}
|
||||
|
||||
func TestRemoveOverride(t *testing.T) {
|
||||
t.Run("remove from empty storage", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.ErrorIs(t, err, engine.ErrChainNameNotFound)
|
||||
})
|
||||
|
||||
t.Run("remove not added chain id", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
||||
ID: chain.ID(chainID),
|
||||
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::*"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(nonExistChainId))
|
||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
||||
})
|
||||
|
||||
t.Run("remove existing chain id", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
||||
ID: chain.ID(chainID),
|
||||
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::*"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.NoError(t, err)
|
||||
|
||||
ingressChains, ok := inmem.nameToResourceChains[chain.Ingress]
|
||||
require.True(t, ok)
|
||||
require.Len(t, ingressChains, 1)
|
||||
resourceChains, ok := ingressChains[resrc]
|
||||
require.True(t, ok)
|
||||
require.Len(t, resourceChains, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetOverride(t *testing.T) {
|
||||
addChain := &chain.Chain{
|
||||
ID: chain.ID(chainID),
|
||||
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::*"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("get from empty storage", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
_, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.ErrorIs(t, err, engine.ErrChainNameNotFound)
|
||||
})
|
||||
|
||||
t.Run("get not added chain id", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
||||
|
||||
const nonExistingChainID = "ingress:LxGyWyL"
|
||||
|
||||
_, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(nonExistingChainID))
|
||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
||||
})
|
||||
|
||||
t.Run("get existing chain id", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
||||
|
||||
c, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, *addChain, *c)
|
||||
})
|
||||
|
||||
t.Run("get removed chain id", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
||||
|
||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListOverrides(t *testing.T) {
|
||||
addChain := &chain.Chain{
|
||||
ID: chain.ID(chainID),
|
||||
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::*"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("list empty storage", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
||||
require.Len(t, l, 0)
|
||||
})
|
||||
|
||||
t.Run("list with one added resource", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
||||
require.Len(t, l, 1)
|
||||
})
|
||||
|
||||
t.Run("list after drop", func(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
||||
require.Len(t, l, 1)
|
||||
|
||||
_ = inmem.DropAllOverrides(chain.Ingress)
|
||||
l, _ = inmem.ListOverrides(chain.Ingress, resrc)
|
||||
require.Len(t, l, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateID(t *testing.T) {
|
||||
inmem := testInmemLocalStorage()
|
||||
ids := make([]chain.ID, 0, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
ids = append(ids, inmem.generateChainID(chain.Ingress, resrc))
|
||||
}
|
||||
require.False(t, hasDuplicates(ids))
|
||||
}
|
||||
|
||||
func hasDuplicates(ids []chain.ID) bool {
|
||||
seen := make(map[chain.ID]bool)
|
||||
for _, id := range ids {
|
||||
if seen[id] {
|
||||
return true
|
||||
}
|
||||
seen[id] = true
|
||||
}
|
||||
return false
|
||||
}
|
52
pkg/engine/inmemory/morph_storage.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package inmemory
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
)
|
||||
|
||||
type inmemoryMorphRuleChainStorage struct {
|
||||
nameToNamespaceChains engine.LocalOverrideStorage
|
||||
nameToContainerChains engine.LocalOverrideStorage
|
||||
}
|
||||
|
||||
func NewInmemoryMorphRuleChainStorage() engine.MorphRuleChainStorage {
|
||||
return &inmemoryMorphRuleChainStorage{
|
||||
nameToNamespaceChains: NewInmemoryLocalStorage(),
|
||||
nameToContainerChains: NewInmemoryLocalStorage(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inmemoryMorphRuleChainStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (err error) {
|
||||
switch target.Type {
|
||||
case engine.Namespace:
|
||||
_, err = s.nameToNamespaceChains.AddOverride(name, target.Name, c)
|
||||
case engine.Container:
|
||||
_, err = s.nameToContainerChains.AddOverride(name, target.Name, c)
|
||||
default:
|
||||
err = engine.ErrUnknownTarget
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) error {
|
||||
switch target.Type {
|
||||
case engine.Namespace:
|
||||
return s.nameToNamespaceChains.RemoveOverride(name, target.Name, chainID)
|
||||
case engine.Container:
|
||||
return s.nameToContainerChains.RemoveOverride(name, target.Name, chainID)
|
||||
default:
|
||||
return engine.ErrUnknownTarget
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inmemoryMorphRuleChainStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
|
||||
switch target.Type {
|
||||
case engine.Namespace:
|
||||
return s.nameToNamespaceChains.ListOverrides(name, target.Name)
|
||||
case engine.Container:
|
||||
return s.nameToContainerChains.ListOverrides(name, target.Name)
|
||||
default:
|
||||
}
|
||||
return nil, engine.ErrUnknownTarget
|
||||
}
|
78
pkg/engine/interface.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
type ChainRouter interface {
|
||||
// IsAllowed returns status for the operation after all checks.
|
||||
// The second return value signifies whether a matching rule was found.
|
||||
IsAllowed(name chain.Name, target string, r resource.Request) (status chain.Status, found bool, err error)
|
||||
}
|
||||
|
||||
// LocalOverrideStorage is the interface to manage local overrides defined
|
||||
// for a node. Local overrides have a higher priority than chains got from morph storage.
|
||||
type LocalOverrideStorage interface {
|
||||
AddOverride(name chain.Name, resource string, c *chain.Chain) (chain.ID, error)
|
||||
|
||||
GetOverride(name chain.Name, resource string, chainID chain.ID) (*chain.Chain, error)
|
||||
|
||||
RemoveOverride(name chain.Name, resource string, chainID chain.ID) error
|
||||
|
||||
ListOverrides(name chain.Name, resource string) ([]*chain.Chain, error)
|
||||
|
||||
DropAllOverrides(name chain.Name) error
|
||||
}
|
||||
|
||||
type TargetType rune
|
||||
|
||||
const (
|
||||
Namespace TargetType = 'n'
|
||||
Container TargetType = 'c'
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
Type TargetType
|
||||
Name string
|
||||
}
|
||||
|
||||
func NamespaceTarget(namespace string) Target {
|
||||
return Target{
|
||||
Type: Namespace,
|
||||
Name: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
func ContainerTarget(container string) Target {
|
||||
return Target{
|
||||
Type: Container,
|
||||
Name: container,
|
||||
}
|
||||
}
|
||||
|
||||
// MorphRuleChainStorage is the interface to manage chains from the chain storage.
|
||||
// Basically, this implies that the storage manages rules stored in policy contract.
|
||||
type MorphRuleChainStorage interface {
|
||||
AddMorphRuleChain(name chain.Name, target Target, c *chain.Chain) error
|
||||
|
||||
RemoveMorphRuleChain(name chain.Name, target Target, chainID chain.ID) error
|
||||
|
||||
ListMorphRuleChains(name chain.Name, target Target) ([]*chain.Chain, error)
|
||||
}
|
||||
|
||||
// Engine is the interface that provides methods to check request permissions checking
|
||||
// chain rules from morph client - this implies using the policy contract.
|
||||
type Engine interface {
|
||||
ChainRouter
|
||||
|
||||
MorphRuleChainStorage() MorphRuleChainStorage
|
||||
}
|
||||
|
||||
// LocalOverrideEngine is extended Engine that also provides methods to manage a local
|
||||
// chain rule storage. Local overrides must have the highest priority during request checking.
|
||||
type LocalOverrideEngine interface {
|
||||
Engine
|
||||
|
||||
LocalStorage() LocalOverrideStorage
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package policyengine
|
||||
package resource
|
||||
|
||||
// Request represents generic named resource (bucket, container etc.).
|
||||
// Name is resource depenent but should be globally unique for any given
|
53
pkg/resource/testutil/resource.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
resourcepkg "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
name string
|
||||
properties map[string]string
|
||||
}
|
||||
|
||||
func (r *Resource) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *Resource) Property(name string) string {
|
||||
return r.properties[name]
|
||||
}
|
||||
|
||||
func NewResource(name string, properties map[string]string) *Resource {
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
Why do both request and resource accept a map now? Why do both request and resource accept a map now?
aarifullin
commented
I don't know. I just moved the file and made constructors importable :) ( Also, this structs are usable only for unit-tests witihn policy-engine I don't know. I just moved the file and made constructors importable :) (`new..` -> `New...`). What is your concern about these maps?
Also, this structs are usable only for unit-tests witihn policy-engine
fyrchik
commented
Oh, I see, missed testutil package Oh, I see, missed testutil package
|
||||
if properties == nil {
|
||||
properties = make(map[string]string)
|
||||
}
|
||||
return &Resource{name: name, properties: properties}
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
operation string
|
||||
properties map[string]string
|
||||
resource *Resource
|
||||
}
|
||||
|
||||
var _ resourcepkg.Request = (*Request)(nil)
|
||||
|
||||
func (r *Request) Operation() string {
|
||||
return r.operation
|
||||
}
|
||||
|
||||
func (r *Request) Resource() resourcepkg.Resource {
|
||||
return r.resource
|
||||
}
|
||||
|
||||
func (r *Request) Property(name string) string {
|
||||
return r.properties[name]
|
||||
}
|
||||
|
||||
func NewRequest(op string, r *Resource, properties map[string]string) *Request {
|
||||
return &Request{
|
||||
operation: op,
|
||||
properties: properties,
|
||||
resource: r,
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package policyengine
|
||||
|
||||
type resource struct {
|
||||
name string
|
||||
properties map[string]string
|
||||
}
|
||||
|
||||
func (r *resource) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *resource) Property(name string) string {
|
||||
return r.properties[name]
|
||||
}
|
||||
|
||||
func newResource(name string, properties map[string]string) *resource {
|
||||
if properties == nil {
|
||||
properties = make(map[string]string)
|
||||
}
|
||||
return &resource{name: name, properties: properties}
|
||||
}
|
||||
|
||||
type request struct {
|
||||
operation string
|
||||
properties map[string]string
|
||||
resource *resource
|
||||
}
|
||||
|
||||
var _ Request = (*request)(nil)
|
||||
|
||||
func (r *request) Operation() string {
|
||||
return r.operation
|
||||
}
|
||||
|
||||
func (r *request) Resource() Resource {
|
||||
return r.resource
|
||||
}
|
||||
|
||||
func (r *request) Property(name string) string {
|
||||
return r.properties[name]
|
||||
}
|
||||
|
||||
func newRequest(op string, r *resource, properties map[string]string) *request {
|
||||
return &request{
|
||||
operation: op,
|
||||
properties: properties,
|
||||
resource: r,
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package policyengine
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -9,7 +9,7 @@ import (
|
|||
// ? in pattern correspond to any symbol.
|
||||
// * in pattern correspond to any sequence of symbols.
|
||||
// Currently only '*' in the suffix is supported.
|
||||
func globMatch(s, pattern string) bool {
|
||||
func GlobMatch(s, pattern string) bool {
|
||||
index := strings.IndexByte(pattern, '*')
|
||||
switch index {
|
||||
default:
|
Why do we use alias here? In the previous version it was because of the hyphen.
Because tests use
So, either we need to make either an alias for the package or rename
chain
. Would we prefer the second option?I think the second option is better. I will fix