acl: support for user events

This commit is contained in:
Ryan Uber 2015-06-17 18:56:29 -07:00
parent 0381e1a253
commit 0c624350eb
4 changed files with 175 additions and 0 deletions

View File

@ -52,6 +52,12 @@ type ACL interface {
// ServiceRead checks for permission to read a given service
ServiceRead(string) bool
// EventRead determines if a specific event can be queried.
EventRead(string) bool
// EventWrite determines if a specific event may be fired.
EventWrite(string) bool
// ACLList checks for permission to list all the ACLs
ACLList() bool
@ -87,6 +93,14 @@ func (s *StaticACL) ServiceWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) EventRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) EventWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) ACLList() bool {
return s.allowManage
}
@ -136,6 +150,9 @@ type PolicyACL struct {
// serviceRules contains the service policies
serviceRules *radix.Tree
// eventRules contains the user event policies
eventRules *radix.Tree
}
// New is used to construct a policy based ACL from a set of policies
@ -145,6 +162,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
parent: parent,
keyRules: radix.New(),
serviceRules: radix.New(),
eventRules: radix.New(),
}
// Load the key policy
@ -156,6 +174,12 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
for _, sp := range policy.Services {
p.serviceRules.Insert(sp.Name, sp.Policy)
}
// Load the event policy
for _, ep := range policy.Events {
p.eventRules.Insert(ep.Event, ep.Policy)
}
return p, nil
}
@ -266,6 +290,37 @@ func (p *PolicyACL) ServiceWrite(name string) bool {
return p.parent.ServiceWrite(name)
}
// EventRead is used to determine if the policy allows for a
// specific user event to be read.
func (p *PolicyACL) EventRead(name string) bool {
// Longest-prefix match on event names
if _, rule, ok := p.eventRules.LongestPrefix(name); ok {
switch rule {
case EventPolicyRead:
return true
case EventPolicyWrite:
return true
default:
return false
}
}
// Nothing matched, use parent
return p.parent.EventRead(name)
}
// EventWrite is used to determine if new events can be created
// (fired) by the policy.
func (p *PolicyACL) EventWrite(name string) bool {
// Longest-prefix match event names
if _, rule, ok := p.eventRules.LongestPrefix(name); ok {
return rule == EventPolicyWrite
}
// No match, use parent
return p.parent.EventWrite(name)
}
// ACLList checks if listing of ACLs is allowed
func (p *PolicyACL) ACLList() bool {
return p.parent.ACLList()

View File

@ -66,6 +66,18 @@ func TestStaticACL(t *testing.T) {
if none.ServiceWrite("foobar") {
t.Fatalf("should not allow")
}
if none.EventRead("foobar") {
t.Fatalf("should not allow")
}
if none.EventRead("") {
t.Fatalf("should not allow")
}
if none.EventWrite("foobar") {
t.Fatalf("should not allow")
}
if none.EventWrite("") {
t.Fatalf("should not allow")
}
if none.ACLList() {
t.Fatalf("should not noneow")
}
@ -132,6 +144,20 @@ func TestPolicyACL(t *testing.T) {
Policy: ServicePolicyWrite,
},
},
Events: []*EventPolicy{
&EventPolicy{
Event: "",
Policy: EventPolicyRead,
},
&EventPolicy{
Event: "foo",
Policy: EventPolicyWrite,
},
&EventPolicy{
Event: "bar",
Policy: EventPolicyDeny,
},
},
}
acl, err := New(all, policy)
if err != nil {
@ -188,6 +214,27 @@ func TestPolicyACL(t *testing.T) {
t.Fatalf("Write fail: %#v", c)
}
}
type eventcase struct {
inp string
read bool
write bool
}
eventcases := []eventcase{
{"foo", true, true},
{"foobar", true, true},
{"bar", false, false},
{"barbaz", false, false},
{"baz", true, false},
}
for _, c := range eventcases {
if c.read != acl.EventRead(c.inp) {
t.Fatalf("Event fail: %#v", c)
}
if c.write != acl.EventWrite(c.inp) {
t.Fatalf("Event fail: %#v", c)
}
}
}
func TestPolicyACL_Parent(t *testing.T) {

View File

@ -13,6 +13,9 @@ const (
ServicePolicyDeny = "deny"
ServicePolicyRead = "read"
ServicePolicyWrite = "write"
EventPolicyRead = "read"
EventPolicyWrite = "write"
EventPolicyDeny = "deny"
)
// Policy is used to represent the policy specified by
@ -21,6 +24,7 @@ type Policy struct {
ID string `hcl:"-"`
Keys []*KeyPolicy `hcl:"key,expand"`
Services []*ServicePolicy `hcl:"service,expand"`
Events []*EventPolicy `hcl:"event,expand"`
}
// KeyPolicy represents a policy for a key
@ -43,6 +47,16 @@ func (k *ServicePolicy) GoString() string {
return fmt.Sprintf("%#v", *k)
}
// EventPolicy represents a user event policy.
type EventPolicy struct {
Event string `hcl:",key"`
Policy string
}
func (e *EventPolicy) GoString() string {
return fmt.Sprintf("%#v", *e)
}
// Parse is used to parse the specified ACL rules into an
// intermediary set of policies, before being compiled into
// the ACL
@ -80,5 +94,16 @@ func Parse(rules string) (*Policy, error) {
}
}
// Validate the user event policies
for _, ep := range p.Events {
switch ep.Policy {
case EventPolicyRead:
case EventPolicyWrite:
case EventPolicyDeny:
default:
return nil, fmt.Errorf("Invalid event policy: %#v", ep)
}
}
return p, nil
}

View File

@ -24,6 +24,15 @@ service "" {
}
service "foo" {
policy = "read"
}
event "" {
policy = "read"
}
event "foo" {
policy = "write"
}
event "bar" {
policy = "deny"
}
`
exp := &Policy{
@ -55,6 +64,20 @@ service "foo" {
Policy: ServicePolicyRead,
},
},
Events: []*EventPolicy{
&EventPolicy{
Event: "",
Policy: EventPolicyRead,
},
&EventPolicy{
Event: "foo",
Policy: EventPolicyWrite,
},
&EventPolicy{
Event: "bar",
Policy: EventPolicyDeny,
},
},
}
out, err := Parse(inp)
@ -90,6 +113,17 @@ func TestParse_JSON(t *testing.T) {
"foo": {
"policy": "read"
}
},
"event": {
"": {
"policy": "read"
},
"foo": {
"policy": "write"
},
"bar": {
"policy": "deny"
}
}
}`
exp := &Policy{
@ -121,6 +155,20 @@ func TestParse_JSON(t *testing.T) {
Policy: ServicePolicyRead,
},
},
Events: []*EventPolicy{
&EventPolicy{
Event: "",
Policy: EventPolicyRead,
},
&EventPolicy{
Event: "foo",
Policy: EventPolicyWrite,
},
&EventPolicy{
Event: "bar",
Policy: EventPolicyDeny,
},
},
}
out, err := Parse(inp)