generated from TrueCloudLab/basic
Compare commits
3 commits
master
...
feature/7-
Author | SHA1 | Date | |
---|---|---|---|
|
e47af4b111 | ||
|
6eb4a649c3 | ||
|
a064a01fdb |
21 changed files with 1002 additions and 498 deletions
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -52,41 +52,41 @@ const (
|
||||||
CondArnNotLike string = "ArnNotLike"
|
CondArnNotLike string = "ArnNotLike"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Policy) ToChain() (*policyengine.Chain, error) {
|
func (p Policy) ToChain() (*chain.Chain, error) {
|
||||||
if err := p.Validate(GeneralPolicyType); err != nil {
|
if err := p.Validate(GeneralPolicyType); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var chain policyengine.Chain
|
var ch chain.Chain
|
||||||
|
|
||||||
for _, statement := range p.Statement {
|
for _, statement := range p.Statement {
|
||||||
status := policyengine.AccessDenied
|
status := chain.AccessDenied
|
||||||
if statement.Effect == AllowEffect {
|
if statement.Effect == AllowEffect {
|
||||||
status = policyengine.Allow
|
status = chain.Allow
|
||||||
}
|
}
|
||||||
|
|
||||||
var principals []string
|
var principals []string
|
||||||
var op policyengine.ConditionType
|
var op chain.ConditionType
|
||||||
statementPrincipal, inverted := statement.principal()
|
statementPrincipal, inverted := statement.principal()
|
||||||
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
||||||
principals = []string{Wildcard}
|
principals = []string{Wildcard}
|
||||||
op = policyengine.CondStringLike
|
op = chain.CondStringLike
|
||||||
} else {
|
} else {
|
||||||
for _, principal := range statementPrincipal {
|
for _, principal := range statementPrincipal {
|
||||||
principals = append(principals, principal...)
|
principals = append(principals, principal...)
|
||||||
}
|
}
|
||||||
|
|
||||||
op = policyengine.CondStringEquals
|
op = chain.CondStringEquals
|
||||||
if inverted {
|
if inverted {
|
||||||
op = policyengine.CondStringNotEquals
|
op = chain.CondStringNotEquals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var conditions []policyengine.Condition
|
var conditions []chain.Condition
|
||||||
for _, principal := range principals {
|
for _, principal := range principals {
|
||||||
conditions = append(conditions, policyengine.Condition{
|
conditions = append(conditions, chain.Condition{
|
||||||
Op: op,
|
Op: op,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: principal,
|
Value: principal,
|
||||||
})
|
})
|
||||||
|
@ -99,49 +99,49 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
conditions = append(conditions, conds...)
|
conditions = append(conditions, conds...)
|
||||||
|
|
||||||
action, actionInverted := statement.action()
|
action, actionInverted := statement.action()
|
||||||
ruleAction := policyengine.Actions{Inverted: actionInverted, Names: action}
|
ruleAction := chain.Actions{Inverted: actionInverted, Names: action}
|
||||||
|
|
||||||
resource, resourceInverted := statement.resource()
|
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,
|
Status: status,
|
||||||
Actions: ruleAction,
|
Actions: ruleAction,
|
||||||
Resources: ruleResource,
|
Resources: ruleResource,
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: conditions,
|
Condition: conditions,
|
||||||
}
|
}
|
||||||
chain.Rules = append(chain.Rules, r)
|
ch.Rules = append(ch.Rules, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &chain, nil
|
return &ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
func (c Conditions) ToChainCondition() ([]chain.Condition, error) {
|
||||||
var conditions []policyengine.Condition
|
var conditions []chain.Condition
|
||||||
|
|
||||||
var convertValue convertFunction
|
var convertValue convertFunction
|
||||||
|
|
||||||
for op, KVs := range c {
|
for op, KVs := range c {
|
||||||
var condType policyengine.ConditionType
|
var condType chain.ConditionType
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(op, "String"):
|
case strings.HasPrefix(op, "String"):
|
||||||
convertValue = noConvertFunction
|
convertValue = noConvertFunction
|
||||||
switch op {
|
switch op {
|
||||||
case CondStringEquals:
|
case CondStringEquals:
|
||||||
condType = policyengine.CondStringEquals
|
condType = chain.CondStringEquals
|
||||||
case CondStringNotEquals:
|
case CondStringNotEquals:
|
||||||
condType = policyengine.CondStringNotEquals
|
condType = chain.CondStringNotEquals
|
||||||
case CondStringEqualsIgnoreCase:
|
case CondStringEqualsIgnoreCase:
|
||||||
condType = policyengine.CondStringEqualsIgnoreCase
|
condType = chain.CondStringEqualsIgnoreCase
|
||||||
case CondStringNotEqualsIgnoreCase:
|
case CondStringNotEqualsIgnoreCase:
|
||||||
condType = policyengine.CondStringNotEqualsIgnoreCase
|
condType = chain.CondStringNotEqualsIgnoreCase
|
||||||
case CondStringLike:
|
case CondStringLike:
|
||||||
condType = policyengine.CondStringLike
|
condType = chain.CondStringLike
|
||||||
case CondStringNotLike:
|
case CondStringNotLike:
|
||||||
condType = policyengine.CondStringNotLike
|
condType = chain.CondStringNotLike
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
}
|
}
|
||||||
|
@ -149,13 +149,13 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||||
convertValue = noConvertFunction
|
convertValue = noConvertFunction
|
||||||
switch op {
|
switch op {
|
||||||
case CondArnEquals:
|
case CondArnEquals:
|
||||||
condType = policyengine.CondStringEquals
|
condType = chain.CondStringEquals
|
||||||
case CondArnNotEquals:
|
case CondArnNotEquals:
|
||||||
condType = policyengine.CondStringNotEquals
|
condType = chain.CondStringNotEquals
|
||||||
case CondArnLike:
|
case CondArnLike:
|
||||||
condType = policyengine.CondStringLike
|
condType = chain.CondStringLike
|
||||||
case CondArnNotLike:
|
case CondArnNotLike:
|
||||||
condType = policyengine.CondStringNotLike
|
condType = chain.CondStringNotLike
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
}
|
}
|
||||||
|
@ -165,33 +165,33 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||||
convertValue = dateConvertFunction
|
convertValue = dateConvertFunction
|
||||||
switch op {
|
switch op {
|
||||||
case CondDateEquals:
|
case CondDateEquals:
|
||||||
condType = policyengine.CondStringEquals
|
condType = chain.CondStringEquals
|
||||||
case CondDateNotEquals:
|
case CondDateNotEquals:
|
||||||
condType = policyengine.CondStringNotEquals
|
condType = chain.CondStringNotEquals
|
||||||
case CondDateLessThan:
|
case CondDateLessThan:
|
||||||
condType = policyengine.CondStringLessThan
|
condType = chain.CondStringLessThan
|
||||||
case CondDateLessThanEquals:
|
case CondDateLessThanEquals:
|
||||||
condType = policyengine.CondStringLessThanEquals
|
condType = chain.CondStringLessThanEquals
|
||||||
case CondDateGreaterThan:
|
case CondDateGreaterThan:
|
||||||
condType = policyengine.CondStringGreaterThan
|
condType = chain.CondStringGreaterThan
|
||||||
case CondDateGreaterThanEquals:
|
case CondDateGreaterThanEquals:
|
||||||
condType = policyengine.CondStringGreaterThanEquals
|
condType = chain.CondStringGreaterThanEquals
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
}
|
}
|
||||||
case op == CondBool:
|
case op == CondBool:
|
||||||
convertValue = noConvertFunction
|
convertValue = noConvertFunction
|
||||||
condType = policyengine.CondStringEqualsIgnoreCase
|
condType = chain.CondStringEqualsIgnoreCase
|
||||||
case op == CondIPAddress:
|
case op == CondIPAddress:
|
||||||
// todo consider using converters
|
// todo consider using converters
|
||||||
// "203.0.113.0/24" -> "203.0.113.*",
|
// "203.0.113.0/24" -> "203.0.113.*",
|
||||||
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
|
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
|
||||||
// or having specific condition type for IP
|
// or having specific condition type for IP
|
||||||
convertValue = noConvertFunction
|
convertValue = noConvertFunction
|
||||||
condType = policyengine.CondStringLike
|
condType = chain.CondStringLike
|
||||||
case op == CondNotIPAddress:
|
case op == CondNotIPAddress:
|
||||||
convertValue = noConvertFunction
|
convertValue = noConvertFunction
|
||||||
condType = policyengine.CondStringNotLike
|
condType = chain.CondStringNotLike
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
}
|
}
|
||||||
|
@ -203,9 +203,9 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions = append(conditions, policyengine.Condition{
|
conditions = append(conditions, chain.Condition{
|
||||||
Op: condType,
|
Op: condType,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: key,
|
Key: key,
|
||||||
Value: converted,
|
Value: converted,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ package iam
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
chain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
"github.com/stretchr/testify/require"
|
"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,
|
Status: chain.Allow,
|
||||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
Actions: chain.Actions{Names: p.Statement[0].Action},
|
||||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
Resources: chain.Resources{Names: p.Statement[0].Resource},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []chain.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "s3:RequestObjectTag/Department",
|
Key: "s3:RequestObjectTag/Department",
|
||||||
Value: "Finance",
|
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,
|
Status: chain.AccessDenied,
|
||||||
Actions: policyengine.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
Actions: chain.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
||||||
Resources: policyengine.Resources{Inverted: true, Names: p.Statement[0].NotResource},
|
Resources: chain.Resources{Inverted: true, Names: p.Statement[0].NotResource},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []chain.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: chain.CondStringNotEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
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,
|
Status: chain.Allow,
|
||||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
Actions: chain.Actions{Names: p.Statement[0].Action},
|
||||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
Resources: chain.Resources{Names: p.Statement[0].Resource},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []chain.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLike,
|
Op: chain.CondStringLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: "*",
|
Value: "*",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key1",
|
Key: "key1",
|
||||||
Value: "val0",
|
Value: "val0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key1",
|
Key: "key1",
|
||||||
Value: "val1",
|
Value: "val1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: chain.CondStringNotEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key2",
|
Key: "key2",
|
||||||
Value: "val2",
|
Value: "val2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEqualsIgnoreCase,
|
Op: chain.CondStringEqualsIgnoreCase,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key3",
|
Key: "key3",
|
||||||
Value: "val3",
|
Value: "val3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEqualsIgnoreCase,
|
Op: chain.CondStringNotEqualsIgnoreCase,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key4",
|
Key: "key4",
|
||||||
Value: "val4",
|
Value: "val4",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLike,
|
Op: chain.CondStringLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key5",
|
Key: "key5",
|
||||||
Value: "val5",
|
Value: "val5",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotLike,
|
Op: chain.CondStringNotLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key6",
|
Key: "key6",
|
||||||
Value: "val6",
|
Value: "val6",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key7",
|
Key: "key7",
|
||||||
Value: "1136189045",
|
Value: "1136189045",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: chain.CondStringNotEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key8",
|
Key: "key8",
|
||||||
Value: "1136214245",
|
Value: "1136214245",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLessThan,
|
Op: chain.CondStringLessThan,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key9",
|
Key: "key9",
|
||||||
Value: "1136192645",
|
Value: "1136192645",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLessThanEquals,
|
Op: chain.CondStringLessThanEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key10",
|
Key: "key10",
|
||||||
Value: "1136203445",
|
Value: "1136203445",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringGreaterThan,
|
Op: chain.CondStringGreaterThan,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key11",
|
Key: "key11",
|
||||||
Value: "1136217845",
|
Value: "1136217845",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringGreaterThanEquals,
|
Op: chain.CondStringGreaterThanEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key12",
|
Key: "key12",
|
||||||
Value: "1136225045",
|
Value: "1136225045",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEqualsIgnoreCase,
|
Op: chain.CondStringEqualsIgnoreCase,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key13",
|
Key: "key13",
|
||||||
Value: "True",
|
Value: "True",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLike,
|
Op: chain.CondStringLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key14",
|
Key: "key14",
|
||||||
Value: "val14",
|
Value: "val14",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotLike,
|
Op: chain.CondStringNotLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key15",
|
Key: "key15",
|
||||||
Value: "val15",
|
Value: "val15",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key16",
|
Key: "key16",
|
||||||
Value: "val16",
|
Value: "val16",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLike,
|
Op: chain.CondStringLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key17",
|
Key: "key17",
|
||||||
Value: "val17",
|
Value: "val17",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: chain.CondStringNotEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key18",
|
Key: "key18",
|
||||||
Value: "val18",
|
Value: "val18",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotLike,
|
Op: chain.CondStringNotLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: chain.ObjectRequest,
|
||||||
Key: "key19",
|
Key: "key19",
|
||||||
Value: "val19",
|
Value: "val19",
|
||||||
},
|
},
|
||||||
|
|
106
inmemory.go
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
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,23 +1,19 @@
|
||||||
package policyengine
|
package chain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||||
|
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Engine ...
|
// ID is the ID of rule chain.
|
||||||
type Engine interface {
|
type ID string
|
||||||
// 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
|
|
||||||
|
|
||||||
type Chain struct {
|
type Chain struct {
|
||||||
ID ChainID
|
ID ID
|
||||||
|
|
||||||
Rules []Rule
|
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
|
var val string
|
||||||
switch c.Object {
|
switch c.Object {
|
||||||
case ObjectResource:
|
case ObjectResource:
|
||||||
|
@ -159,9 +155,9 @@ func (c *Condition) Match(req Request) bool {
|
||||||
case CondStringNotEqualsIgnoreCase:
|
case CondStringNotEqualsIgnoreCase:
|
||||||
return !strings.EqualFold(val, c.Value)
|
return !strings.EqualFold(val, c.Value)
|
||||||
case CondStringLike:
|
case CondStringLike:
|
||||||
return globMatch(val, c.Value)
|
return util.GlobMatch(val, c.Value)
|
||||||
case CondStringNotLike:
|
case CondStringNotLike:
|
||||||
return !globMatch(val, c.Value)
|
return !util.GlobMatch(val, c.Value)
|
||||||
case CondStringLessThan:
|
case CondStringLessThan:
|
||||||
return val < c.Value
|
return val < c.Value
|
||||||
case CondStringLessThanEquals:
|
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
|
found := len(r.Resources.Names) == 0
|
||||||
for i := range r.Resources.Names {
|
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
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -185,21 +181,21 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
|
||||||
return NoRuleFound, false
|
return NoRuleFound, false
|
||||||
}
|
}
|
||||||
for i := range r.Actions.Names {
|
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 r.matchCondition(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NoRuleFound, false
|
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 {
|
if r.Any {
|
||||||
return r.matchAny(obj)
|
return r.matchAny(obj)
|
||||||
}
|
}
|
||||||
return r.matchAll(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 {
|
for i := range r.Condition {
|
||||||
if r.Condition[i].Match(obj) {
|
if r.Condition[i].Match(obj) {
|
||||||
return r.Status, true
|
return r.Status, true
|
||||||
|
@ -208,7 +204,7 @@ func (r *Rule) matchAny(obj Request) (status Status, matched bool) {
|
||||||
return NoRuleFound, false
|
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 {
|
for i := range r.Condition {
|
||||||
if !r.Condition[i].Match(obj) {
|
if !r.Condition[i].Match(obj) {
|
||||||
return NoRuleFound, false
|
return NoRuleFound, false
|
||||||
|
@ -217,7 +213,7 @@ func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
|
||||||
return r.Status, true
|
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 {
|
for i := range c.Rules {
|
||||||
status, matched := c.Rules[i].Match(req)
|
status, matched := c.Rules[i].Match(req)
|
||||||
if matched {
|
if matched {
|
|
@ -1,4 +1,4 @@
|
||||||
package policyengine
|
package chain
|
||||||
|
|
||||||
// Name represents the place in the request lifecycle where policy is applied.
|
// Name represents the place in the request lifecycle where policy is applied.
|
||||||
type Name string
|
type Name string
|
|
@ -1,4 +1,4 @@
|
||||||
package policyengine
|
package chain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -1,4 +1,4 @@
|
||||||
package policyengine
|
package chain
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
101
pkg/engine/chain_router.go
Normal file
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
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
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
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) {
|
||||||
|
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
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 {
|
||||||
|
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
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
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
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.).
|
// Request represents generic named resource (bucket, container etc.).
|
||||||
// Name is resource depenent but should be globally unique for any given
|
// 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 {
|
||||||
|
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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -9,7 +9,7 @@ import (
|
||||||
// ? in pattern correspond to any symbol.
|
// ? in pattern correspond to any symbol.
|
||||||
// * in pattern correspond to any sequence of symbols.
|
// * in pattern correspond to any sequence of symbols.
|
||||||
// Currently only '*' in the suffix is supported.
|
// Currently only '*' in the suffix is supported.
|
||||||
func globMatch(s, pattern string) bool {
|
func GlobMatch(s, pattern string) bool {
|
||||||
index := strings.IndexByte(pattern, '*')
|
index := strings.IndexByte(pattern, '*')
|
||||||
switch index {
|
switch index {
|
||||||
default:
|
default:
|
Loading…
Reference in a new issue