forked from TrueCloudLab/policy-engine
[#11] Support inverted action and resource
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
5eee1a7334
commit
8d291039d8
3 changed files with 107 additions and 31 deletions
63
chain.go
63
chain.go
|
@ -37,15 +37,25 @@ func (c *Chain) DecodeBytes(b []byte) error {
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Status Status
|
Status Status
|
||||||
// Actions the operation is applied to.
|
// Actions the operation is applied to.
|
||||||
Action []string
|
Actions Actions
|
||||||
// List of the resources the operation is applied to.
|
// List of the resources the operation is applied to.
|
||||||
Resource []string
|
Resources Resources
|
||||||
// True iff individual conditions must be combined with the logical OR.
|
// True iff individual conditions must be combined with the logical OR.
|
||||||
// By default AND is used, so _each_ condition must pass.
|
// By default AND is used, so _each_ condition must pass.
|
||||||
Any bool
|
Any bool
|
||||||
Condition []Condition
|
Condition []Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Actions struct {
|
||||||
|
Inverted bool
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resources struct {
|
||||||
|
Inverted bool
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
type Condition struct {
|
type Condition struct {
|
||||||
Op ConditionType
|
Op ConditionType
|
||||||
Object ObjectType
|
Object ObjectType
|
||||||
|
@ -88,6 +98,45 @@ const (
|
||||||
CondNumericGreaterThanEquals
|
CondNumericGreaterThanEquals
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c ConditionType) String() string {
|
||||||
|
switch c {
|
||||||
|
case CondStringEquals:
|
||||||
|
return "StringEquals"
|
||||||
|
case CondStringNotEquals:
|
||||||
|
return "StringNotEquals"
|
||||||
|
case CondStringEqualsIgnoreCase:
|
||||||
|
return "StringEqualsIgnoreCase"
|
||||||
|
case CondStringNotEqualsIgnoreCase:
|
||||||
|
return "StringNotEqualsIgnoreCase"
|
||||||
|
case CondStringLike:
|
||||||
|
return "StringLike"
|
||||||
|
case CondStringNotLike:
|
||||||
|
return "StringNotLike"
|
||||||
|
case CondStringLessThan:
|
||||||
|
return "StringLessThan"
|
||||||
|
case CondStringLessThanEquals:
|
||||||
|
return "StringLessThanEquals"
|
||||||
|
case CondStringGreaterThan:
|
||||||
|
return "StringGreaterThan"
|
||||||
|
case CondStringGreaterThanEquals:
|
||||||
|
return "StringGreaterThanEquals"
|
||||||
|
case CondNumericEquals:
|
||||||
|
return "NumericEquals"
|
||||||
|
case CondNumericNotEquals:
|
||||||
|
return "NumericNotEquals"
|
||||||
|
case CondNumericLessThan:
|
||||||
|
return "NumericLessThan"
|
||||||
|
case CondNumericLessThanEquals:
|
||||||
|
return "NumericLessThanEquals"
|
||||||
|
case CondNumericGreaterThan:
|
||||||
|
return "NumericGreaterThan"
|
||||||
|
case CondNumericGreaterThanEquals:
|
||||||
|
return "NumericGreaterThanEquals"
|
||||||
|
default:
|
||||||
|
return "unknown condition type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Condition) Match(req Request) bool {
|
func (c *Condition) Match(req Request) bool {
|
||||||
var val string
|
var val string
|
||||||
switch c.Object {
|
switch c.Object {
|
||||||
|
@ -126,9 +175,9 @@ func (c *Condition) Match(req Request) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rule) Match(req Request) (status Status, matched bool) {
|
func (r *Rule) Match(req Request) (status Status, matched bool) {
|
||||||
found := len(r.Resource) == 0
|
found := len(r.Resources.Names) == 0
|
||||||
for i := range r.Resource {
|
for i := range r.Resources.Names {
|
||||||
if globMatch(req.Resource().Name(), r.Resource[i]) {
|
if globMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -136,8 +185,8 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
|
||||||
if !found {
|
if !found {
|
||||||
return NoRuleFound, false
|
return NoRuleFound, false
|
||||||
}
|
}
|
||||||
for i := range r.Action {
|
for i := range r.Actions.Names {
|
||||||
if globMatch(req.Operation(), r.Action[i]) {
|
if globMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
|
||||||
return r.matchCondition(req)
|
return r.matchCondition(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ func TestEncodeDecode(t *testing.T) {
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{
|
{
|
||||||
Status: Allow,
|
Status: Allow,
|
||||||
Action: []string{
|
Actions: Actions{Names: []string{
|
||||||
"native::PutObject",
|
"native::PutObject",
|
||||||
},
|
}},
|
||||||
Resource: []string{"*"},
|
Resources: Resources{Names: []string{"*"}},
|
||||||
Condition: []Condition{
|
Condition: []Condition{
|
||||||
{
|
{
|
||||||
Op: CondStringEquals,
|
Op: CondStringEquals,
|
||||||
|
|
|
@ -11,6 +11,7 @@ func TestInmemory(t *testing.T) {
|
||||||
object = "native::object::abc/xyz"
|
object = "native::object::abc/xyz"
|
||||||
container = "native::object::abc/*"
|
container = "native::object::abc/*"
|
||||||
namespace = "Tenant1"
|
namespace = "Tenant1"
|
||||||
|
namespace2 = "Tenant2"
|
||||||
actor1 = "owner1"
|
actor1 = "owner1"
|
||||||
actor2 = "owner2"
|
actor2 = "owner2"
|
||||||
)
|
)
|
||||||
|
@ -33,13 +34,13 @@ func TestInmemory(t *testing.T) {
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
{ // Restrict to remove ANY object from the namespace.
|
||||||
Status: AccessDenied,
|
Status: AccessDenied,
|
||||||
Action: []string{"native::object::delete"},
|
Actions: Actions{Names: []string{"native::object::delete"}},
|
||||||
Resource: []string{"native::object::*"},
|
Resources: Resources{Names: []string{"native::object::*"}},
|
||||||
},
|
},
|
||||||
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
||||||
Status: AccessDenied,
|
Status: AccessDenied,
|
||||||
Action: []string{"native::object::put"},
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
Resource: []string{"native::object::*"},
|
Resources: Resources{Names: []string{"native::object::*"}},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []Condition{
|
Condition: []Condition{
|
||||||
{
|
{
|
||||||
|
@ -59,12 +60,22 @@ func TestInmemory(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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{
|
s.AddResourceChain(Ingress, container, &Chain{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
||||||
Status: Allow,
|
Status: Allow,
|
||||||
Action: []string{"native::object::get"},
|
Actions: Actions{Names: []string{"native::object::get"}},
|
||||||
Resource: []string{"native::object::abc/*"},
|
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||||
Condition: []Condition{
|
Condition: []Condition{
|
||||||
{
|
{
|
||||||
Op: CondStringEquals,
|
Op: CondStringEquals,
|
||||||
|
@ -140,8 +151,8 @@ func TestInmemory(t *testing.T) {
|
||||||
s.AddOverride(Ingress, &Chain{
|
s.AddOverride(Ingress, &Chain{
|
||||||
Rules: []Rule{{
|
Rules: []Rule{{
|
||||||
Status: QuotaLimitReached,
|
Status: QuotaLimitReached,
|
||||||
Action: []string{"native::object::put"},
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
Resource: []string{"native::object::cba/*"},
|
Resources: Resources{Names: []string{"native::object::cba/*"}},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -153,8 +164,8 @@ func TestInmemory(t *testing.T) {
|
||||||
s.AddOverride(Ingress, &Chain{
|
s.AddOverride(Ingress, &Chain{
|
||||||
Rules: []Rule{{
|
Rules: []Rule{{
|
||||||
Status: QuotaLimitReached,
|
Status: QuotaLimitReached,
|
||||||
Action: []string{"native::object::put"},
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
Resource: []string{"native::object::abc/*"},
|
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -163,4 +174,20 @@ func TestInmemory(t *testing.T) {
|
||||||
require.True(t, ok)
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue