mirror of https://github.com/hashicorp/consul
Adds support to the ACL package for agent policies.
parent
34da7ccd64
commit
022baeea13
63
acl/acl.go
63
acl/acl.go
|
@ -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()
|
||||||
|
|
101
acl/acl_test.go
101
acl/acl_test.go
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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"`,
|
||||||
|
|
Loading…
Reference in New Issue