[#11] Support inverted action and resource

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
feature/7-revise_iface
Denis Kirillov 2023-10-30 16:34:11 +03:00
parent 5eee1a7334
commit 8d291039d8
3 changed files with 107 additions and 31 deletions

View File

@ -37,15 +37,25 @@ func (c *Chain) DecodeBytes(b []byte) error {
type Rule struct {
Status Status
// Actions the operation is applied to.
Action []string
Actions Actions
// List of the resources the operation is applied to.
Resource []string
Resources Resources
// True iff individual conditions must be combined with the logical OR.
// By default AND is used, so _each_ condition must pass.
Any bool
Condition []Condition
}
type Actions struct {
Inverted bool
Names []string
}
type Resources struct {
Inverted bool
Names []string
}
type Condition struct {
Op ConditionType
Object ObjectType
@ -88,6 +98,45 @@ const (
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 {
var val string
switch c.Object {
@ -126,9 +175,9 @@ func (c *Condition) Match(req Request) bool {
}
func (r *Rule) Match(req Request) (status Status, matched bool) {
found := len(r.Resource) == 0
for i := range r.Resource {
if globMatch(req.Resource().Name(), r.Resource[i]) {
found := len(r.Resources.Names) == 0
for i := range r.Resources.Names {
if globMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
found = true
break
}
@ -136,8 +185,8 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
if !found {
return NoRuleFound, false
}
for i := range r.Action {
if globMatch(req.Operation(), r.Action[i]) {
for i := range r.Actions.Names {
if globMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
return r.matchCondition(req)
}
}

View File

@ -11,10 +11,10 @@ func TestEncodeDecode(t *testing.T) {
Rules: []Rule{
{
Status: Allow,
Action: []string{
Actions: Actions{Names: []string{
"native::PutObject",
},
Resource: []string{"*"},
}},
Resources: Resources{Names: []string{"*"}},
Condition: []Condition{
{
Op: CondStringEquals,

View File

@ -8,11 +8,12 @@ import (
func TestInmemory(t *testing.T) {
const (
object = "native::object::abc/xyz"
container = "native::object::abc/*"
namespace = "Tenant1"
actor1 = "owner1"
actor2 = "owner2"
object = "native::object::abc/xyz"
container = "native::object::abc/*"
namespace = "Tenant1"
namespace2 = "Tenant2"
actor1 = "owner1"
actor2 = "owner2"
)
s := NewInMemory()
@ -32,15 +33,15 @@ func TestInmemory(t *testing.T) {
s.AddNameSpaceChain(Ingress, namespace, &Chain{
Rules: []Rule{
{ // Restrict to remove ANY object from the namespace.
Status: AccessDenied,
Action: []string{"native::object::delete"},
Resource: []string{"native::object::*"},
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,
Action: []string{"native::object::put"},
Resource: []string{"native::object::*"},
Any: true,
Status: AccessDenied,
Actions: Actions{Names: []string{"native::object::put"}},
Resources: Resources{Names: []string{"native::object::*"}},
Any: true,
Condition: []Condition{
{
Op: CondStringNotLike,
@ -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{
Rules: []Rule{
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
Status: Allow,
Action: []string{"native::object::get"},
Resource: []string{"native::object::abc/*"},
Status: Allow,
Actions: Actions{Names: []string{"native::object::get"}},
Resources: Resources{Names: []string{"native::object::abc/*"}},
Condition: []Condition{
{
Op: CondStringEquals,
@ -139,9 +150,9 @@ func TestInmemory(t *testing.T) {
t.Run("quota on a different container", func(t *testing.T) {
s.AddOverride(Ingress, &Chain{
Rules: []Rule{{
Status: QuotaLimitReached,
Action: []string{"native::object::put"},
Resource: []string{"native::object::cba/*"},
Status: QuotaLimitReached,
Actions: Actions{Names: []string{"native::object::put"}},
Resources: Resources{Names: []string{"native::object::cba/*"}},
}},
})
@ -152,9 +163,9 @@ func TestInmemory(t *testing.T) {
t.Run("quota on the request container", func(t *testing.T) {
s.AddOverride(Ingress, &Chain{
Rules: []Rule{{
Status: QuotaLimitReached,
Action: []string{"native::object::put"},
Resource: []string{"native::object::abc/*"},
Status: QuotaLimitReached,
Actions: Actions{Names: []string{"native::object::put"}},
Resources: Resources{Names: []string{"native::object::abc/*"}},
}},
})
@ -163,4 +174,20 @@ func TestInmemory(t *testing.T) {
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)
})
}