Adds support to the ACL package for agent policies.

pull/2594/head
James Phillips 2016-12-12 23:05:11 -08:00
parent 34da7ccd64
commit 022baeea13
No known key found for this signature in database
GPG Key ID: 77183E682AC5FC11
4 changed files with 218 additions and 0 deletions

View File

@ -41,6 +41,14 @@ type ACL interface {
// ACLModify checks for permission to manipulate ACLs // ACLModify checks for permission to manipulate ACLs
ACLModify() bool 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 determines if a specific event can be queried.
EventRead(string) bool EventRead(string) bool
@ -122,6 +130,14 @@ func (s *StaticACL) ACLModify() bool {
return s.allowManage 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 { func (s *StaticACL) EventRead(string) bool {
return s.defaultAllow return s.defaultAllow
} }
@ -230,6 +246,9 @@ type PolicyACL struct {
// no matching rule. // no matching rule.
parent ACL parent ACL
// agentRules contains the agent policies
agentRules *radix.Tree
// keyRules contains the key policies // keyRules contains the key policies
keyRules *radix.Tree keyRules *radix.Tree
@ -262,6 +281,7 @@ type PolicyACL struct {
func New(parent ACL, policy *Policy) (*PolicyACL, error) { func New(parent ACL, policy *Policy) (*PolicyACL, error) {
p := &PolicyACL{ p := &PolicyACL{
parent: parent, parent: parent,
agentRules: radix.New(),
keyRules: radix.New(), keyRules: radix.New(),
nodeRules: radix.New(), nodeRules: radix.New(),
serviceRules: radix.New(), serviceRules: radix.New(),
@ -270,6 +290,11 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
preparedQueryRules: radix.New(), preparedQueryRules: radix.New(),
} }
// Load the agent policy
for _, ap := range policy.Agents {
p.agentRules.Insert(ap.Node, ap.Policy)
}
// Load the key policy // Load the key policy
for _, kp := range policy.Keys { for _, kp := range policy.Keys {
p.keyRules.Insert(kp.Prefix, kp.Policy) p.keyRules.Insert(kp.Prefix, kp.Policy)
@ -319,6 +344,44 @@ func (p *PolicyACL) ACLModify() bool {
return p.parent.ACLModify() 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. // Snapshot checks if taking and restoring snapshots is allowed.
func (p *PolicyACL) Snapshot() bool { func (p *PolicyACL) Snapshot() bool {
return p.parent.Snapshot() return p.parent.Snapshot()

View File

@ -41,6 +41,12 @@ func TestStaticACL(t *testing.T) {
if all.ACLModify() { if all.ACLModify() {
t.Fatalf("should not allow") 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") { if !all.EventRead("foobar") {
t.Fatalf("should allow") t.Fatalf("should allow")
} }
@ -99,6 +105,12 @@ func TestStaticACL(t *testing.T) {
if none.ACLModify() { if none.ACLModify() {
t.Fatalf("should not allow") 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") { if none.EventRead("foobar") {
t.Fatalf("should not allow") t.Fatalf("should not allow")
} }
@ -163,6 +175,12 @@ func TestStaticACL(t *testing.T) {
if !manage.ACLModify() { if !manage.ACLModify() {
t.Fatalf("should allow") t.Fatalf("should allow")
} }
if !manage.AgentRead("foobar") {
t.Fatalf("should allow")
}
if !manage.AgentWrite("foobar") {
t.Fatalf("should allow")
}
if !manage.EventRead("foobar") { if !manage.EventRead("foobar") {
t.Fatalf("should allow") 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) { func TestPolicyACL_Keyring(t *testing.T) {
type keyringcase struct { type keyringcase struct {
inp string inp string

View File

@ -16,6 +16,7 @@ const (
// an ACL configuration. // an ACL configuration.
type Policy struct { type Policy struct {
ID string `hcl:"-"` ID string `hcl:"-"`
Agents []*AgentPolicy `hcl:"agent,expand"`
Keys []*KeyPolicy `hcl:"key,expand"` Keys []*KeyPolicy `hcl:"key,expand"`
Nodes []*NodePolicy `hcl:"node,expand"` Nodes []*NodePolicy `hcl:"node,expand"`
Services []*ServicePolicy `hcl:"service,expand"` Services []*ServicePolicy `hcl:"service,expand"`
@ -26,6 +27,17 @@ type Policy struct {
Operator string `hcl:"operator"` 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 // KeyPolicy represents a policy for a key
type KeyPolicy struct { type KeyPolicy struct {
Prefix string `hcl:",key"` 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) 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 // Validate the key policy
for _, kp := range p.Keys { for _, kp := range p.Keys {
if !isPolicyValid(kp.Policy) { if !isPolicyValid(kp.Policy) {

View File

@ -8,6 +8,12 @@ import (
func TestACLPolicy_Parse_HCL(t *testing.T) { func TestACLPolicy_Parse_HCL(t *testing.T) {
inp := ` inp := `
agent "foo" {
policy = "read"
}
agent "bar" {
policy = "write"
}
event "" { event "" {
policy = "read" policy = "read"
} }
@ -63,6 +69,16 @@ query "bar" {
} }
` `
exp := &Policy{ exp := &Policy{
Agents: []*AgentPolicy{
&AgentPolicy{
Node: "foo",
Policy: PolicyRead,
},
&AgentPolicy{
Node: "bar",
Policy: PolicyWrite,
},
},
Events: []*EventPolicy{ Events: []*EventPolicy{
&EventPolicy{ &EventPolicy{
Event: "", Event: "",
@ -159,6 +175,14 @@ query "bar" {
func TestACLPolicy_Parse_JSON(t *testing.T) { func TestACLPolicy_Parse_JSON(t *testing.T) {
inp := `{ inp := `{
"agent": {
"foo": {
"policy": "write"
},
"bar": {
"policy": "deny"
}
},
"event": { "event": {
"": { "": {
"policy": "read" "policy": "read"
@ -226,6 +250,16 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
} }
}` }`
exp := &Policy{ exp := &Policy{
Agents: []*AgentPolicy{
&AgentPolicy{
Node: "foo",
Policy: PolicyWrite,
},
&AgentPolicy{
Node: "bar",
Policy: PolicyDeny,
},
},
Events: []*EventPolicy{ Events: []*EventPolicy{
&EventPolicy{ &EventPolicy{
Event: "", Event: "",
@ -358,6 +392,7 @@ operator = ""
func TestACLPolicy_Bad_Policy(t *testing.T) { func TestACLPolicy_Bad_Policy(t *testing.T) {
cases := []string{ cases := []string{
`agent "" { policy = "nope" }`,
`event "" { policy = "nope" }`, `event "" { policy = "nope" }`,
`key "" { policy = "nope" }`, `key "" { policy = "nope" }`,
`keyring = "nope"`, `keyring = "nope"`,