forked from TrueCloudLab/policy-engine
Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
c1ac4ad957 |
13 changed files with 673 additions and 0 deletions
193
chain.go
Normal file
193
chain.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
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(req.Operation(), r.Action[i]) {
|
||||
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
|
||||
}
|
10
chain_names.go
Normal file
10
chain_names.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
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"
|
||||
)
|
33
chain_test.go
Normal file
33
chain_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
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
Normal file
35
error.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
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
Normal file
22
glob.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
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
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
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
Normal file
17
go.sum
Normal file
|
@ -0,0 +1,17 @@
|
|||
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
Normal file
76
inmemory.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
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
Normal file
166
inmemory_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
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
Normal file
12
interface.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
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
Normal file
30
policy.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
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
Normal file
19
resource.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
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
|
||||
}
|
49
resource_test.go
Normal file
49
resource_test.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
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