mirror of https://github.com/status-im/consul.git
Adds support to the ACL package for agent policies.
This commit is contained in:
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() 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()
|
||||
|
|
101
acl/acl_test.go
101
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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"`,
|
||||
|
|
Loading…
Reference in New Issue