diff --git a/acl/acl.go b/acl/acl.go index 46288a9d7d..3ade9d4055 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -41,6 +41,14 @@ type ACL interface { // ACLModify checks for permission to manipulate ACLs ACLModify() bool + // AgentRead checks for permission to read from agent endpoints for a + // given node. + AgentRead(string) bool + + // AgentWrite checks for permission to make changes via agent endpoints + // for a given node. + AgentWrite(string) bool + // EventRead determines if a specific event can be queried. EventRead(string) bool @@ -122,6 +130,14 @@ func (s *StaticACL) ACLModify() bool { return s.allowManage } +func (s *StaticACL) AgentRead(string) bool { + return s.defaultAllow +} + +func (s *StaticACL) AgentWrite(string) bool { + return s.defaultAllow +} + func (s *StaticACL) EventRead(string) bool { return s.defaultAllow } @@ -230,6 +246,9 @@ type PolicyACL struct { // no matching rule. parent ACL + // agentRules contains the agent policies + agentRules *radix.Tree + // keyRules contains the key policies keyRules *radix.Tree @@ -262,6 +281,7 @@ type PolicyACL struct { func New(parent ACL, policy *Policy) (*PolicyACL, error) { p := &PolicyACL{ parent: parent, + agentRules: radix.New(), keyRules: radix.New(), nodeRules: radix.New(), serviceRules: radix.New(), @@ -270,6 +290,11 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) { preparedQueryRules: radix.New(), } + // Load the agent policy + for _, ap := range policy.Agents { + p.agentRules.Insert(ap.Node, ap.Policy) + } + // Load the key policy for _, kp := range policy.Keys { p.keyRules.Insert(kp.Prefix, kp.Policy) @@ -319,6 +344,44 @@ func (p *PolicyACL) ACLModify() bool { return p.parent.ACLModify() } +// AgentRead checks for permission to read from agent endpoints for a given +// node. +func (p *PolicyACL) AgentRead(node string) bool { + // Check for an exact rule or catch-all + _, rule, ok := p.agentRules.LongestPrefix(node) + + if ok { + switch rule { + case PolicyRead, PolicyWrite: + return true + default: + return false + } + } + + // No matching rule, use the parent. + return p.parent.AgentRead(node) +} + +// AgentWrite checks for permission to make changes via agent endpoints for a +// given node. +func (p *PolicyACL) AgentWrite(node string) bool { + // Check for an exact rule or catch-all + _, rule, ok := p.agentRules.LongestPrefix(node) + + if ok { + switch rule { + case PolicyWrite: + return true + default: + return false + } + } + + // No matching rule, use the parent. + return p.parent.AgentWrite(node) +} + // Snapshot checks if taking and restoring snapshots is allowed. func (p *PolicyACL) Snapshot() bool { return p.parent.Snapshot() diff --git a/acl/acl_test.go b/acl/acl_test.go index 197f9390cf..b008846fc7 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -41,6 +41,12 @@ func TestStaticACL(t *testing.T) { if all.ACLModify() { t.Fatalf("should not allow") } + if !all.AgentRead("foobar") { + t.Fatalf("should allow") + } + if !all.AgentWrite("foobar") { + t.Fatalf("should allow") + } if !all.EventRead("foobar") { t.Fatalf("should allow") } @@ -99,6 +105,12 @@ func TestStaticACL(t *testing.T) { if none.ACLModify() { t.Fatalf("should not allow") } + if none.AgentRead("foobar") { + t.Fatalf("should not allow") + } + if none.AgentWrite("foobar") { + t.Fatalf("should not allow") + } if none.EventRead("foobar") { t.Fatalf("should not allow") } @@ -163,6 +175,12 @@ func TestStaticACL(t *testing.T) { if !manage.ACLModify() { t.Fatalf("should allow") } + if !manage.AgentRead("foobar") { + t.Fatalf("should allow") + } + if !manage.AgentWrite("foobar") { + t.Fatalf("should allow") + } if !manage.EventRead("foobar") { t.Fatalf("should allow") } @@ -545,6 +563,89 @@ func TestPolicyACL_Parent(t *testing.T) { } } +func TestPolicyACL_Agent(t *testing.T) { + deny := DenyAll() + policyRoot := &Policy{ + Agents: []*AgentPolicy{ + &AgentPolicy{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &AgentPolicy{ + Node: "root-ro", + Policy: PolicyRead, + }, + &AgentPolicy{ + Node: "root-rw", + Policy: PolicyWrite, + }, + &AgentPolicy{ + Node: "override", + Policy: PolicyDeny, + }, + }, + } + root, err := New(deny, policyRoot) + if err != nil { + t.Fatalf("err: %v", err) + } + + policy := &Policy{ + Agents: []*AgentPolicy{ + &AgentPolicy{ + Node: "child-nope", + Policy: PolicyDeny, + }, + &AgentPolicy{ + Node: "child-ro", + Policy: PolicyRead, + }, + &AgentPolicy{ + Node: "child-rw", + Policy: PolicyWrite, + }, + &AgentPolicy{ + Node: "override", + Policy: PolicyWrite, + }, + }, + } + acl, err := New(root, policy) + if err != nil { + t.Fatalf("err: %v", err) + } + + type agentcase struct { + inp string + read bool + write bool + } + cases := []agentcase{ + {"nope", false, false}, + {"root-nope", false, false}, + {"root-ro", true, false}, + {"root-rw", true, true}, + {"root-nope-prefix", false, false}, + {"root-ro-prefix", true, false}, + {"root-rw-prefix", true, true}, + {"child-nope", false, false}, + {"child-ro", true, false}, + {"child-rw", true, true}, + {"child-nope-prefix", false, false}, + {"child-ro-prefix", true, false}, + {"child-rw-prefix", true, true}, + {"override", true, true}, + } + for _, c := range cases { + if c.read != acl.AgentRead(c.inp) { + t.Fatalf("Read fail: %#v", c) + } + if c.write != acl.AgentWrite(c.inp) { + t.Fatalf("Write fail: %#v", c) + } + } +} + func TestPolicyACL_Keyring(t *testing.T) { type keyringcase struct { inp string diff --git a/acl/policy.go b/acl/policy.go index 13d0d99124..f7781b81e7 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -16,6 +16,7 @@ const ( // an ACL configuration. type Policy struct { ID string `hcl:"-"` + Agents []*AgentPolicy `hcl:"agent,expand"` Keys []*KeyPolicy `hcl:"key,expand"` Nodes []*NodePolicy `hcl:"node,expand"` Services []*ServicePolicy `hcl:"service,expand"` @@ -26,6 +27,17 @@ type Policy struct { Operator string `hcl:"operator"` } +// AgentPolicy represents a policy for working with agent endpoints on nodes +// with specific name prefixes. +type AgentPolicy struct { + Node string `hcl:",key"` + Policy string +} + +func (a *AgentPolicy) GoString() string { + return fmt.Sprintf("%#v", *a) +} + // KeyPolicy represents a policy for a key type KeyPolicy struct { Prefix string `hcl:",key"` @@ -116,6 +128,13 @@ func Parse(rules string) (*Policy, error) { return nil, fmt.Errorf("Failed to parse ACL rules: %v", err) } + // Validate the agent policy + for _, ap := range p.Agents { + if !isPolicyValid(ap.Policy) { + return nil, fmt.Errorf("Invalid agent policy: %#v", ap) + } + } + // Validate the key policy for _, kp := range p.Keys { if !isPolicyValid(kp.Policy) { diff --git a/acl/policy_test.go b/acl/policy_test.go index 9120049168..d7b8138e9f 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -8,6 +8,12 @@ import ( func TestACLPolicy_Parse_HCL(t *testing.T) { inp := ` +agent "foo" { + policy = "read" +} +agent "bar" { + policy = "write" +} event "" { policy = "read" } @@ -63,6 +69,16 @@ query "bar" { } ` exp := &Policy{ + Agents: []*AgentPolicy{ + &AgentPolicy{ + Node: "foo", + Policy: PolicyRead, + }, + &AgentPolicy{ + Node: "bar", + Policy: PolicyWrite, + }, + }, Events: []*EventPolicy{ &EventPolicy{ Event: "", @@ -159,6 +175,14 @@ query "bar" { func TestACLPolicy_Parse_JSON(t *testing.T) { inp := `{ + "agent": { + "foo": { + "policy": "write" + }, + "bar": { + "policy": "deny" + } + }, "event": { "": { "policy": "read" @@ -226,6 +250,16 @@ func TestACLPolicy_Parse_JSON(t *testing.T) { } }` exp := &Policy{ + Agents: []*AgentPolicy{ + &AgentPolicy{ + Node: "foo", + Policy: PolicyWrite, + }, + &AgentPolicy{ + Node: "bar", + Policy: PolicyDeny, + }, + }, Events: []*EventPolicy{ &EventPolicy{ Event: "", @@ -358,6 +392,7 @@ operator = "" func TestACLPolicy_Bad_Policy(t *testing.T) { cases := []string{ + `agent "" { policy = "nope" }`, `event "" { policy = "nope" }`, `key "" { policy = "nope" }`, `keyring = "nope"`,