Adds support to ACL package for node policies.

This commit is contained in:
James Phillips 2016-12-06 20:05:15 -08:00
parent 9b4f316b21
commit 7fa4ab3fd1
No known key found for this signature in database
GPG Key ID: 77183E682AC5FC11
4 changed files with 235 additions and 6 deletions

View File

@ -65,6 +65,13 @@ type ACL interface {
// KeyringWrite determines if the keyring can be manipulated
KeyringWrite() bool
// NodeRead checks for permission to read (discover) a given node.
NodeRead(string) bool
// NodeWrite checks for permission to create or update (register) a
// given node.
NodeWrite(string) bool
// OperatorRead determines if the read-only Consul operator functions
// can be used.
OperatorRead() bool
@ -84,7 +91,8 @@ type ACL interface {
// ServiceRead checks for permission to read a given service
ServiceRead(string) bool
// ServiceWrite checks for permission to read a given service
// ServiceWrite checks for permission to create or update a given
// service
ServiceWrite(string) bool
// Snapshot checks for permission to take and restore snapshots.
@ -135,6 +143,14 @@ func (s *StaticACL) KeyringWrite() bool {
return s.defaultAllow
}
func (s *StaticACL) NodeRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) NodeWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) OperatorRead() bool {
return s.defaultAllow
}
@ -202,6 +218,9 @@ type PolicyACL struct {
// keyRules contains the key policies
keyRules *radix.Tree
// nodeRules contains the node policies
nodeRules *radix.Tree
// serviceRules contains the service policies
serviceRules *radix.Tree
@ -226,6 +245,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
p := &PolicyACL{
parent: parent,
keyRules: radix.New(),
nodeRules: radix.New(),
serviceRules: radix.New(),
eventRules: radix.New(),
preparedQueryRules: radix.New(),
@ -236,6 +256,11 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
p.keyRules.Insert(kp.Prefix, kp.Policy)
}
// Load the node policy
for _, np := range policy.Nodes {
p.nodeRules.Insert(np.Name, np.Policy)
}
// Load the service policy
for _, sp := range policy.Services {
p.serviceRules.Insert(sp.Name, sp.Policy)
@ -404,6 +429,42 @@ func (p *PolicyACL) OperatorRead() bool {
}
}
// NodeRead checks if reading (discovery) of a node is allowed
func (p *PolicyACL) NodeRead(name string) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.nodeRules.LongestPrefix(name)
if ok {
switch rule {
case PolicyRead, PolicyWrite:
return true
default:
return false
}
}
// No matching rule, use the parent.
return p.parent.NodeRead(name)
}
// NodeWrite checks if writing (registering) a node is allowed
func (p *PolicyACL) NodeWrite(name string) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.nodeRules.LongestPrefix(name)
if ok {
switch rule {
case PolicyWrite:
return true
default:
return false
}
}
// No matching rule, use the parent.
return p.parent.NodeWrite(name)
}
// OperatorWrite determines if the state-changing operator functions are
// allowed.
func (p *PolicyACL) OperatorWrite() bool {

View File

@ -59,6 +59,12 @@ func TestStaticACL(t *testing.T) {
if !all.KeyringWrite() {
t.Fatalf("should allow")
}
if !all.NodeRead("foobar") {
t.Fatalf("should allow")
}
if !all.NodeWrite("foobar") {
t.Fatalf("should allow")
}
if !all.OperatorRead() {
t.Fatalf("should allow")
}
@ -111,6 +117,12 @@ func TestStaticACL(t *testing.T) {
if none.KeyringWrite() {
t.Fatalf("should not allow")
}
if none.NodeRead("foobar") {
t.Fatalf("should not allow")
}
if none.NodeWrite("foobar") {
t.Fatalf("should not allow")
}
if none.OperatorRead() {
t.Fatalf("should now allow")
}
@ -157,6 +169,12 @@ func TestStaticACL(t *testing.T) {
if !manage.KeyringWrite() {
t.Fatalf("should allow")
}
if !manage.NodeRead("foobar") {
t.Fatalf("should allow")
}
if !manage.NodeWrite("foobar") {
t.Fatalf("should allow")
}
if !manage.OperatorRead() {
t.Fatalf("should allow")
}
@ -560,3 +578,86 @@ func TestPolicyACL_Operator(t *testing.T) {
}
}
}
func TestPolicyACL_Node(t *testing.T) {
deny := DenyAll()
policyRoot := &Policy{
Nodes: []*NodePolicy{
&NodePolicy{
Name: "root-nope",
Policy: PolicyDeny,
},
&NodePolicy{
Name: "root-ro",
Policy: PolicyRead,
},
&NodePolicy{
Name: "root-rw",
Policy: PolicyWrite,
},
&NodePolicy{
Name: "override",
Policy: PolicyDeny,
},
},
}
root, err := New(deny, policyRoot)
if err != nil {
t.Fatalf("err: %v", err)
}
policy := &Policy{
Nodes: []*NodePolicy{
&NodePolicy{
Name: "child-nope",
Policy: PolicyDeny,
},
&NodePolicy{
Name: "child-ro",
Policy: PolicyRead,
},
&NodePolicy{
Name: "child-rw",
Policy: PolicyWrite,
},
&NodePolicy{
Name: "override",
Policy: PolicyWrite,
},
},
}
acl, err := New(root, policy)
if err != nil {
t.Fatalf("err: %v", err)
}
type nodecase struct {
inp string
read bool
write bool
}
cases := []nodecase{
{"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.NodeRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.NodeWrite(c.inp) {
t.Fatalf("Write fail: %#v", c)
}
}
}

View File

@ -17,6 +17,7 @@ const (
type Policy struct {
ID string `hcl:"-"`
Keys []*KeyPolicy `hcl:"key,expand"`
Nodes []*NodePolicy `hcl:"node,expand"`
Services []*ServicePolicy `hcl:"service,expand"`
Events []*EventPolicy `hcl:"event,expand"`
PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"`
@ -34,14 +35,24 @@ func (k *KeyPolicy) GoString() string {
return fmt.Sprintf("%#v", *k)
}
// NodePolicy represents a policy for a node
type NodePolicy struct {
Name string `hcl:",key"`
Policy string
}
func (n *NodePolicy) GoString() string {
return fmt.Sprintf("%#v", *n)
}
// ServicePolicy represents a policy for a service
type ServicePolicy struct {
Name string `hcl:",key"`
Policy string
}
func (k *ServicePolicy) GoString() string {
return fmt.Sprintf("%#v", *k)
func (s *ServicePolicy) GoString() string {
return fmt.Sprintf("%#v", *s)
}
// EventPolicy represents a user event policy.
@ -60,8 +71,8 @@ type PreparedQueryPolicy struct {
Policy string
}
func (e *PreparedQueryPolicy) GoString() string {
return fmt.Sprintf("%#v", *e)
func (p *PreparedQueryPolicy) GoString() string {
return fmt.Sprintf("%#v", *p)
}
// isPolicyValid makes sure the given string matches one of the valid policies.
@ -100,7 +111,14 @@ func Parse(rules string) (*Policy, error) {
}
}
// Validate the service policy
// Validate the node policies
for _, np := range p.Nodes {
if !isPolicyValid(np.Policy) {
return nil, fmt.Errorf("Invalid node policy: %#v", np)
}
}
// Validate the service policies
for _, sp := range p.Services {
if !isPolicyValid(sp.Policy) {
return nil, fmt.Errorf("Invalid service policy: %#v", sp)

View File

@ -30,6 +30,15 @@ key "foo/bar/baz" {
policy = "deny"
}
keyring = "deny"
node "" {
policy = "read"
}
node "foo" {
policy = "write"
}
node "bar" {
policy = "deny"
}
operator = "deny"
service "" {
policy = "write"
@ -81,6 +90,20 @@ query "bar" {
Policy: PolicyDeny,
},
},
Nodes: []*NodePolicy{
&NodePolicy{
Name: "",
Policy: PolicyRead,
},
&NodePolicy{
Name: "foo",
Policy: PolicyWrite,
},
&NodePolicy{
Name: "bar",
Policy: PolicyDeny,
},
},
Operator: PolicyDeny,
PreparedQueries: []*PreparedQueryPolicy{
&PreparedQueryPolicy{
@ -146,6 +169,17 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
}
},
"keyring": "deny",
"node": {
"": {
"policy": "read"
},
"foo": {
"policy": "write"
},
"bar": {
"policy": "deny"
}
},
"operator": "deny",
"query": {
"": {
@ -201,6 +235,20 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
Policy: PolicyDeny,
},
},
Nodes: []*NodePolicy{
&NodePolicy{
Name: "",
Policy: PolicyRead,
},
&NodePolicy{
Name: "foo",
Policy: PolicyWrite,
},
&NodePolicy{
Name: "bar",
Policy: PolicyDeny,
},
},
Operator: PolicyDeny,
PreparedQueries: []*PreparedQueryPolicy{
&PreparedQueryPolicy{
@ -279,6 +327,7 @@ func TestACLPolicy_Bad_Policy(t *testing.T) {
`event "" { policy = "nope" }`,
`key "" { policy = "nope" }`,
`keyring = "nope"`,
`node "" { policy = "nope" }`,
`operator = "nope"`,
`query "" { policy = "nope" }`,
`service "" { policy = "nope" }`,