generated from TrueCloudLab/basic
[#7] engine: Revise storage interface #15
15 changed files with 511 additions and 496 deletions
|
@ -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",
|
||||
},
|
||||
|
|
110
inmemory.go
110
inmemory.go
|
@ -1,110 +0,0 @@
|
|||
package policyengine
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
||||
)
|
||||
|
||||
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 !util.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
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
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,25 +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
|
||||
}
|
||||
|
@ -138,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:
|
||||
|
@ -175,7 +169,7 @@ 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 util.GlobMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
||||
|
@ -194,14 +188,14 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
|
|||
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
|
||||
|
@ -210,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
|
||||
|
@ -219,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"
|
||||
|
113
pkg/engine/inmemory.go
Normal file
113
pkg/engine/inmemory.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
type inmemory struct {
|
||||
namespace map[chain.Name][]chainWrapper
|
||||
resource map[chain.Name][]chainWrapper
|
||||
local map[chain.Name][]*chain.Chain
|
||||
}
|
||||
|
||||
type chainWrapper struct {
|
||||
object string
|
||||
chain *chain.Chain
|
||||
}
|
||||
|
||||
// NewInMemory returns new inmemory instance of chain storage.
|
||||
func NewInMemory() CachedChainStorage {
|
||||
return &inmemory{
|
||||
namespace: make(map[chain.Name][]chainWrapper),
|
||||
resource: make(map[chain.Name][]chainWrapper),
|
||||
local: make(map[chain.Name][]*chain.Chain),
|
||||
}
|
||||
}
|
||||
|
||||
// IsAllowed implements the Engine interface.
|
||||
func (s *inmemory) IsAllowed(name chain.Name, namespace string, r resource.Request) (chain.Status, bool) {
|
||||
var ruleFound bool
|
||||
if local, ok := s.local[name]; ok {
|
||||
for _, c := range local {
|
||||
if status, matched := c.Match(r); matched && status != chain.Allow {
|
||||
return status, true
|
||||
}
|
||||
}
|
||||
}
|
||||
if cs, ok := s.namespace[name]; ok {
|
||||
status, ok := matchArray(cs, namespace, r)
|
||||
if ok && status != chain.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 chain.Allow, true
|
||||
}
|
||||
return chain.NoRuleFound, false
|
||||
}
|
||||
|
||||
func matchArray(cs []chainWrapper, object string, r resource.Request) (chain.Status, bool) {
|
||||
for _, c := range cs {
|
||||
if !util.GlobMatch(object, c.object) {
|
||||
continue
|
||||
}
|
||||
if status, matched := c.chain.Match(r); matched {
|
||||
return status, true
|
||||
}
|
||||
}
|
||||
return chain.NoRuleFound, false
|
||||
}
|
||||
|
||||
func (s *inmemory) AddResourceChain(name chain.Name, resource string, c *chain.Chain) {
|
||||
s.resource[name] = append(s.resource[name], chainWrapper{resource, c})
|
||||
}
|
||||
|
||||
func (s *inmemory) AddNameSpaceChain(name chain.Name, namespace string, c *chain.Chain) {
|
||||
s.namespace[name] = append(s.namespace[name], chainWrapper{namespace, c})
|
||||
}
|
||||
|
||||
func (s *inmemory) AddOverride(name chain.Name, c *chain.Chain) {
|
||||
s.local[name] = append(s.local[name], c)
|
||||
}
|
||||
|
||||
func (s *inmemory) GetOverride(name chain.Name, chainID chain.ID) (chain *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 chain.Name, chainID chain.ID) (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 chain.Name) []*chain.Chain {
|
||||
return s.local[name]
|
||||
}
|
195
pkg/engine/inmemory_test.go
Normal file
195
pkg/engine/inmemory_test.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
|
||||
"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 := 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.AddNameSpaceChain(chain.Ingress, 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.AddNameSpaceChain(chain.Ingress, 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.AddResourceChain(chain.Ingress, 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.AddOverride(chain.Ingress, &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)
|
||||
})
|
||||
t.Run("quota on the request container", func(t *testing.T) {
|
||||
s.AddOverride(chain.Ingress, &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)
|
||||
})
|
||||
})
|
||||
}
|
30
pkg/engine/interface.go
Normal file
30
pkg/engine/interface.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
)
|
||||
|
||||
// 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 chain.Name, namespace string, r resource.Request) (chain.Status, bool)
|
||||
}
|
||||
|
||||
// CachedChainStorage ...
|
||||
type CachedChainStorage interface {
|
||||
Engine
|
||||
// Adds a policy chain used for all operations with a specific resource.
|
||||
AddResourceChain(name chain.Name, resource string, c *chain.Chain)
|
||||
// Adds a policy chain used for all operations in the namespace.
|
||||
AddNameSpaceChain(name chain.Name, namespace string, c *chain.Chain)
|
||||
// Adds a local policy chain used for all operations with this service.
|
||||
AddOverride(name chain.Name, c *chain.Chain)
|
||||
// Gets the local override with given chain id.
|
||||
GetOverride(name chain.Name, chainID chain.ID) (chain *chain.Chain, found bool)
|
||||
// Remove the local override with given chain id.
|
||||
RemoveOverride(name chain.Name, chainID chain.ID) (removed bool)
|
||||
// ListOverrides returns the list of local overrides.
|
||||
ListOverrides(name chain.Name) []*chain.Chain
|
||||
}
|
|
@ -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
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,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue
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