forked from TrueCloudLab/policy-engine
Compare commits
No commits in common. "init" and "master" have entirely different histories.
13 changed files with 0 additions and 673 deletions
193
chain.go
193
chain.go
|
@ -1,193 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Chain struct {
|
|
||||||
Rules []Rule
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// FIXME #1 (@fyrchik): Introduce more optimal serialization format.
|
|
||||||
gob.Register(Chain{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) Bytes() []byte {
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
e := gob.NewEncoder(b)
|
|
||||||
if err := e.Encode(c); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) DecodeBytes(b []byte) error {
|
|
||||||
r := bytes.NewReader(b)
|
|
||||||
d := gob.NewDecoder(r)
|
|
||||||
return d.Decode(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
|
||||||
Status Status
|
|
||||||
// Actions the operation is applied to.
|
|
||||||
Action []string
|
|
||||||
// List of the resources the operation is applied to.
|
|
||||||
Resource []string
|
|
||||||
// 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 Condition struct {
|
|
||||||
Op ConditionType
|
|
||||||
Object ObjectType
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ObjectType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ObjectResource ObjectType = iota
|
|
||||||
ObjectRequest
|
|
||||||
ObjectActor
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO @fyrchik: replace string with int-like type.
|
|
||||||
type ConditionType string
|
|
||||||
|
|
||||||
// TODO @fyrchik: reduce the number of conditions.
|
|
||||||
// Everything from here should be expressable, but we do not need them all.
|
|
||||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
|
|
||||||
const (
|
|
||||||
// String condition operators.
|
|
||||||
CondStringEquals ConditionType = "StringEquals"
|
|
||||||
CondStringNotEquals ConditionType = "StringNotEquals"
|
|
||||||
CondStringEqualsIgnoreCase ConditionType = "StringEqualsIgnoreCase"
|
|
||||||
CondStringNotEqualsIgnoreCase ConditionType = "StringNotEqualsIgnoreCase"
|
|
||||||
CondStringLike ConditionType = "StringLike"
|
|
||||||
CondStringNotLike ConditionType = "StringNotLike"
|
|
||||||
|
|
||||||
// Numeric condition operators.
|
|
||||||
CondNumericEquals ConditionType = "NumericEquals"
|
|
||||||
CondNumericNotEquals ConditionType = "NumericNotEquals"
|
|
||||||
CondNumericLessThan ConditionType = "NumericLessThan"
|
|
||||||
CondNumericLessThanEquals ConditionType = "NumericLessThanEquals"
|
|
||||||
CondNumericGreaterThan ConditionType = "NumericGreaterThan"
|
|
||||||
CondNumericGreaterThanEquals ConditionType = "NumericGreaterThanEquals"
|
|
||||||
|
|
||||||
// Date condition operators.
|
|
||||||
CondDateEquals ConditionType = "DateEquals"
|
|
||||||
CondDateNotEquals ConditionType = "DateNotEquals"
|
|
||||||
CondDateLessThan ConditionType = "DateLessThan"
|
|
||||||
CondDateLessThanEquals ConditionType = "DateLessThanEquals"
|
|
||||||
CondDateGreaterThan ConditionType = "DateGreaterThan"
|
|
||||||
CondDateGreaterThanEquals ConditionType = "DateGreaterThanEquals"
|
|
||||||
|
|
||||||
// Bolean condition operators.
|
|
||||||
CondBool ConditionType = "Bool"
|
|
||||||
|
|
||||||
// IP address condition operators.
|
|
||||||
CondIPAddress ConditionType = "IpAddress"
|
|
||||||
CondNotIPAddress ConditionType = "NotIpAddress"
|
|
||||||
|
|
||||||
// ARN condition operators.
|
|
||||||
CondArnEquals ConditionType = "ArnEquals"
|
|
||||||
CondArnLike ConditionType = "ArnLike"
|
|
||||||
CondArnNotEquals ConditionType = "ArnNotEquals"
|
|
||||||
CondArnNotLike ConditionType = "ArnNotLike"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Condition) Match(obj Request) bool {
|
|
||||||
var val string
|
|
||||||
switch c.Object {
|
|
||||||
case ObjectResource:
|
|
||||||
val = obj.Resource().Property(c.Key)
|
|
||||||
case ObjectRequest:
|
|
||||||
val = obj.Property(c.Key)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Op {
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented: %s", c.Op))
|
|
||||||
case CondStringEquals:
|
|
||||||
return val == c.Value
|
|
||||||
case CondStringNotEquals:
|
|
||||||
return val != c.Value
|
|
||||||
case CondStringEqualsIgnoreCase:
|
|
||||||
return strings.EqualFold(val, c.Value)
|
|
||||||
case CondStringNotEqualsIgnoreCase:
|
|
||||||
return !strings.EqualFold(val, c.Value)
|
|
||||||
case CondStringLike:
|
|
||||||
return globMatch(val, c.Value)
|
|
||||||
case CondStringNotLike:
|
|
||||||
return !globMatch(val, c.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
for i := range r.Action {
|
|
||||||
if globMatch(r.Action[i], req.Operation()) {
|
|
||||||
return r.matchCondition(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) matchCondition(obj 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) {
|
|
||||||
for i := range r.Condition {
|
|
||||||
if r.Condition[i].Match(obj) {
|
|
||||||
|
|
||||||
return r.Status, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
|
|
||||||
for i := range r.Condition {
|
|
||||||
if !r.Condition[i].Match(obj) {
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.Status, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) Match(req Request) (status Status, matched bool) {
|
|
||||||
for i := range c.Rules {
|
|
||||||
status, matched := c.Rules[i].Match(req)
|
|
||||||
if matched {
|
|
||||||
return status, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
// Name represents the place in the request lifecycle where policy is applied.
|
|
||||||
type Name string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Ingress represents chains applied when crossing user/storage network boundary.
|
|
||||||
// It is not applied when talking between nodes.
|
|
||||||
Ingress Name = "ingress"
|
|
||||||
)
|
|
|
@ -1,33 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncodeDecode(t *testing.T) {
|
|
||||||
expected := Chain{
|
|
||||||
Rules: []Rule{
|
|
||||||
{
|
|
||||||
Status: Allow,
|
|
||||||
Action: []string{
|
|
||||||
"native::PutObject",
|
|
||||||
},
|
|
||||||
Resource: []string{"*"},
|
|
||||||
Condition: []Condition{
|
|
||||||
{
|
|
||||||
Op: CondStringEquals,
|
|
||||||
Key: "Name",
|
|
||||||
Value: "NNS",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data := expected.Bytes()
|
|
||||||
|
|
||||||
var actual Chain
|
|
||||||
require.NoError(t, actual.DecodeBytes(data))
|
|
||||||
require.Equal(t, expected, actual)
|
|
||||||
}
|
|
35
error.go
35
error.go
|
@ -1,35 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Status is the status for policy application
|
|
||||||
type Status byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
Allow Status = iota
|
|
||||||
NoRuleFound
|
|
||||||
AccessDenied
|
|
||||||
QuotaLimitReached
|
|
||||||
last
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valid returns true if the status is valid.
|
|
||||||
func (s Status) Valid() bool {
|
|
||||||
return s < last
|
|
||||||
}
|
|
||||||
|
|
||||||
// String implements the fmt.Stringer interface.
|
|
||||||
func (s Status) String() string {
|
|
||||||
switch s {
|
|
||||||
case Allow:
|
|
||||||
return "Allowed"
|
|
||||||
case NoRuleFound:
|
|
||||||
return "NoRuleFound"
|
|
||||||
case AccessDenied:
|
|
||||||
return "Access denied"
|
|
||||||
case QuotaLimitReached:
|
|
||||||
return "Quota limit reached"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("Denied with status: %d", s)
|
|
||||||
}
|
|
||||||
}
|
|
22
glob.go
22
glob.go
|
@ -1,22 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Matches s against the pattern.
|
|
||||||
// ? in pattern correspond to any symbol.
|
|
||||||
// * in pattern correspond to any sequence of symbols.
|
|
||||||
// Currently only '*' in the suffix is supported.
|
|
||||||
func globMatch(s, pattern string) bool {
|
|
||||||
index := strings.IndexByte(pattern, '*')
|
|
||||||
switch index {
|
|
||||||
default:
|
|
||||||
panic("unimplemented")
|
|
||||||
case -1:
|
|
||||||
return pattern == s
|
|
||||||
case utf8.RuneCountInString(pattern) - 1:
|
|
||||||
return strings.HasPrefix(s, pattern[:len(pattern)-1])
|
|
||||||
}
|
|
||||||
}
|
|
11
go.mod
11
go.mod
|
@ -1,11 +0,0 @@
|
||||||
module git.frostfs.info/TrueCloudLab/policy-engine
|
|
||||||
|
|
||||||
go 1.20
|
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.8.1
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
17
go.sum
17
go.sum
|
@ -1,17 +0,0 @@
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
76
inmemory.go
76
inmemory.go
|
@ -1,76 +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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO параметры для actor (IP)
|
|
||||||
// TODO
|
|
||||||
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)
|
|
||||||
}
|
|
166
inmemory_test.go
166
inmemory_test.go
|
@ -1,166 +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"
|
|
||||||
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,
|
|
||||||
Action: []string{"native::object::delete"},
|
|
||||||
Resource: []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,
|
|
||||||
Condition: []Condition{
|
|
||||||
{
|
|
||||||
Op: CondStringNotLike,
|
|
||||||
Object: ObjectRequest,
|
|
||||||
Key: "SourceIP",
|
|
||||||
Value: "10.1.1.*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Op: CondStringNotEquals,
|
|
||||||
Object: ObjectRequest,
|
|
||||||
Key: "Actor",
|
|
||||||
Value: actor1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
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/*"},
|
|
||||||
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("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,
|
|
||||||
Action: []string{"native::object::put"},
|
|
||||||
Resource: []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,
|
|
||||||
Action: []string{"native::object::put"},
|
|
||||||
Resource: []string{"native::object::abc/*"},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
|
||||||
require.Equal(t, QuotaLimitReached, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
12
interface.go
12
interface.go
|
@ -1,12 +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)
|
|
||||||
}
|
|
30
policy.go
30
policy.go
|
@ -1,30 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
//{
|
|
||||||
// "Version": "xyz",
|
|
||||||
// "Policy": [
|
|
||||||
// {
|
|
||||||
// "Effect": "Allow",
|
|
||||||
// "Action": [
|
|
||||||
// "native:*",
|
|
||||||
// "s3:PutObject",
|
|
||||||
// "s3:GetObject"
|
|
||||||
// ],
|
|
||||||
// "Resource": ["*"],
|
|
||||||
// "Principal": ["did:frostfs:039e3ee771a223361fe7862f532e9511b57baaae3c3e2622682e99d0e660f7671"],
|
|
||||||
// "Condition": [ {"StringEquals": {"native::object::attribute", "iamuser-admin"}]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
//}
|
|
||||||
|
|
||||||
// type Policy struct {
|
|
||||||
// Rules []Rule `json:"Policy"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type AWSRule struct {
|
|
||||||
// Effect string `json:"Effect"`
|
|
||||||
// Action []string `json:"Action"`
|
|
||||||
// Resource []string `json:"Resource"`
|
|
||||||
// Principal []string `json:"Principal"`
|
|
||||||
// Condition []Condition `json:"Condition"`
|
|
||||||
// }
|
|
19
resource.go
19
resource.go
|
@ -1,19 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
// Request represents generic named resource (bucket, container etc.).
|
|
||||||
// Name is resource depenent but should be globally unique for any given
|
|
||||||
// type of resource.
|
|
||||||
type Request interface {
|
|
||||||
// Name is the operation name, such as Object.Put. Must not include wildcards.
|
|
||||||
Operation() string
|
|
||||||
// Property returns request properties, such as IP address of the origin.
|
|
||||||
Property(string) string
|
|
||||||
// Resource returns resource the operation is applied to.
|
|
||||||
Resource() Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource represents the resource operation is applied to.
|
|
||||||
type Resource interface {
|
|
||||||
Name() string
|
|
||||||
Property(string) string
|
|
||||||
}
|
|
|
@ -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