diff --git a/acl/acl.go b/acl/acl.go index cfe1ff6b66..24b62d0b42 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -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() diff --git a/acl/acl_test.go b/acl/acl_test.go index d6da2f93e8..2c8b2048bb 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -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) { diff --git a/acl/policy.go b/acl/policy.go index 569570a9d0..1b14b61ac6 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -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 } diff --git a/acl/policy_test.go b/acl/policy_test.go index 0c270e7d57..11f815da2b 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -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)