mirror of https://github.com/hashicorp/consul
Armon Dadgar
10 years ago
4 changed files with 362 additions and 0 deletions
@ -0,0 +1,118 @@
|
||||
package acl |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/armon/go-radix" |
||||
) |
||||
|
||||
var ( |
||||
// allowAll is a singleton policy which allows all actions
|
||||
allowAll ACL |
||||
|
||||
// denyAll is a singleton policy which denies all actions
|
||||
denyAll ACL |
||||
) |
||||
|
||||
func init() { |
||||
// Setup the singletons
|
||||
allowAll = &StaticACL{defaultAllow: true} |
||||
denyAll = &StaticACL{defaultAllow: false} |
||||
} |
||||
|
||||
// ACL is the interface for policy enforcement.
|
||||
type ACL interface { |
||||
KeyRead(string) bool |
||||
KeyWrite(string) bool |
||||
} |
||||
|
||||
// StaticACL is used to implement a base ACL policy. It either
|
||||
// allows or denies all requests. This can be used as a parent
|
||||
// ACL to act in a blacklist or whitelist mode.
|
||||
type StaticACL struct { |
||||
defaultAllow bool |
||||
} |
||||
|
||||
func (s *StaticACL) KeyRead(string) bool { |
||||
return s.defaultAllow |
||||
} |
||||
|
||||
func (s *StaticACL) KeyWrite(string) bool { |
||||
return s.defaultAllow |
||||
} |
||||
|
||||
// AllowAll returns an ACL rule that allows all operations
|
||||
func AllowAll() ACL { |
||||
return allowAll |
||||
} |
||||
|
||||
// DenyAll returns an ACL rule that denies all operations
|
||||
func DenyAll() ACL { |
||||
return denyAll |
||||
} |
||||
|
||||
// PolicyACL is used to wrap a set of ACL policies to provide
|
||||
// the ACL interface.
|
||||
type PolicyACL struct { |
||||
// parent is used to resolve policy if we have
|
||||
// no matching rule.
|
||||
parent ACL |
||||
|
||||
// keyRead contains the read policies
|
||||
keyRead *radix.Tree |
||||
|
||||
// keyWrite contains the write policies
|
||||
keyWrite *radix.Tree |
||||
} |
||||
|
||||
// New is used to construct a policy based ACL from a set of policies
|
||||
// and a parent policy to resolve missing cases.
|
||||
func New(parent ACL, policy *Policy) (*PolicyACL, error) { |
||||
p := &PolicyACL{ |
||||
parent: parent, |
||||
keyRead: radix.New(), |
||||
keyWrite: radix.New(), |
||||
} |
||||
|
||||
// Load the key policy
|
||||
for _, kp := range policy.Keys { |
||||
switch kp.Policy { |
||||
case KeyPolicyDeny: |
||||
p.keyRead.Insert(kp.Prefix, false) |
||||
p.keyWrite.Insert(kp.Prefix, false) |
||||
case KeyPolicyRead: |
||||
p.keyRead.Insert(kp.Prefix, true) |
||||
p.keyWrite.Insert(kp.Prefix, false) |
||||
case KeyPolicyWrite: |
||||
p.keyRead.Insert(kp.Prefix, true) |
||||
p.keyWrite.Insert(kp.Prefix, true) |
||||
default: |
||||
return nil, fmt.Errorf("Invalid key policy: %#v", kp) |
||||
} |
||||
} |
||||
return p, nil |
||||
} |
||||
|
||||
// KeyRead returns if a key is allowed to be read
|
||||
func (p *PolicyACL) KeyRead(key string) bool { |
||||
// Look for a matching rule
|
||||
_, rule, ok := p.keyRead.LongestPrefix(key) |
||||
if ok { |
||||
return rule.(bool) |
||||
} |
||||
|
||||
// No matching rule, use the parent.
|
||||
return p.parent.KeyRead(key) |
||||
} |
||||
|
||||
// KeyWrite returns if a key is allowed to be written
|
||||
func (p *PolicyACL) KeyWrite(key string) bool { |
||||
// Look for a matching rule
|
||||
_, rule, ok := p.keyWrite.LongestPrefix(key) |
||||
if ok { |
||||
return rule.(bool) |
||||
} |
||||
|
||||
// No matching rule, use the parent.
|
||||
return p.parent.KeyWrite(key) |
||||
} |
@ -0,0 +1,142 @@
|
||||
package acl |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestStaticACL(t *testing.T) { |
||||
all := AllowAll() |
||||
if _, ok := all.(*StaticACL); !ok { |
||||
t.Fatalf("expected static") |
||||
} |
||||
|
||||
none := DenyAll() |
||||
if _, ok := none.(*StaticACL); !ok { |
||||
t.Fatalf("expected static") |
||||
} |
||||
|
||||
if !all.KeyRead("foobar") { |
||||
t.Fatalf("should allow") |
||||
} |
||||
if !all.KeyWrite("foobar") { |
||||
t.Fatalf("should allow") |
||||
} |
||||
|
||||
if none.KeyRead("foobar") { |
||||
t.Fatalf("should not allow") |
||||
} |
||||
if none.KeyWrite("foobar") { |
||||
t.Fatalf("should not allow") |
||||
} |
||||
} |
||||
|
||||
func TestPolicyACL(t *testing.T) { |
||||
all := AllowAll() |
||||
policy := &Policy{ |
||||
Keys: []*KeyPolicy{ |
||||
&KeyPolicy{ |
||||
Prefix: "foo/", |
||||
Policy: KeyPolicyWrite, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "foo/priv/", |
||||
Policy: KeyPolicyDeny, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "bar/", |
||||
Policy: KeyPolicyDeny, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "zip/", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
}, |
||||
} |
||||
acl, err := New(all, policy) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
type tcase struct { |
||||
inp string |
||||
read bool |
||||
write bool |
||||
} |
||||
cases := []tcase{ |
||||
{"other", true, true}, |
||||
{"foo/test", true, true}, |
||||
{"foo/priv/test", false, false}, |
||||
{"bar/any", false, false}, |
||||
{"zip/test", true, false}, |
||||
} |
||||
for _, c := range cases { |
||||
if c.read != acl.KeyRead(c.inp) { |
||||
t.Fatalf("Read fail: %#v", c) |
||||
} |
||||
if c.write != acl.KeyWrite(c.inp) { |
||||
t.Fatalf("Write fail: %#v", c) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestPolicyACL_Parent(t *testing.T) { |
||||
deny := DenyAll() |
||||
policyRoot := &Policy{ |
||||
Keys: []*KeyPolicy{ |
||||
&KeyPolicy{ |
||||
Prefix: "foo/", |
||||
Policy: KeyPolicyWrite, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "bar/", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
}, |
||||
} |
||||
root, err := New(deny, policyRoot) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
policy := &Policy{ |
||||
Keys: []*KeyPolicy{ |
||||
&KeyPolicy{ |
||||
Prefix: "foo/priv/", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "bar/", |
||||
Policy: KeyPolicyDeny, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "zip/", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
}, |
||||
} |
||||
acl, err := New(root, policy) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
type tcase struct { |
||||
inp string |
||||
read bool |
||||
write bool |
||||
} |
||||
cases := []tcase{ |
||||
{"other", false, false}, |
||||
{"foo/test", true, true}, |
||||
{"foo/priv/test", true, false}, |
||||
{"bar/any", false, false}, |
||||
{"zip/test", true, false}, |
||||
} |
||||
for _, c := range cases { |
||||
if c.read != acl.KeyRead(c.inp) { |
||||
t.Fatalf("Read fail: %#v", c) |
||||
} |
||||
if c.write != acl.KeyWrite(c.inp) { |
||||
t.Fatalf("Write fail: %#v", c) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,50 @@
|
||||
package acl |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/hashicorp/hcl" |
||||
) |
||||
|
||||
// KeyPolicyType controls the various access levels for keys
|
||||
type KeyPolicyType string |
||||
|
||||
const ( |
||||
KeyPolicyDeny = "deny" |
||||
KeyPolicyRead = "read" |
||||
KeyPolicyWrite = "write" |
||||
) |
||||
|
||||
// Policy is used to represent the policy specified by
|
||||
// an ACL configuration.
|
||||
type Policy struct { |
||||
Keys []*KeyPolicy `hcl:"key"` |
||||
} |
||||
|
||||
// KeyPolicy represents a policy for a key
|
||||
type KeyPolicy struct { |
||||
Prefix string `hcl:",key"` |
||||
Policy KeyPolicyType |
||||
} |
||||
|
||||
// Parse is used to parse the specified ACL rules into an
|
||||
// intermediary set of policies, before being compiled into
|
||||
// the ACL
|
||||
func Parse(rules string) (*Policy, error) { |
||||
// Decode the rules
|
||||
p := &Policy{} |
||||
if err := hcl.Decode(p, rules); err != nil { |
||||
return nil, fmt.Errorf("Failed to parse ACL rules: %v", err) |
||||
} |
||||
|
||||
// Validate the key policy
|
||||
for _, kp := range p.Keys { |
||||
switch kp.Policy { |
||||
case KeyPolicyDeny: |
||||
case KeyPolicyRead: |
||||
case KeyPolicyWrite: |
||||
default: |
||||
return nil, fmt.Errorf("Invalid key policy: %#v", kp) |
||||
} |
||||
} |
||||
return p, nil |
||||
} |
@ -0,0 +1,52 @@
|
||||
package acl |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
func TestParse(t *testing.T) { |
||||
inp := ` |
||||
key "" { |
||||
policy = "read" |
||||
} |
||||
key "foo/" { |
||||
policy = "write" |
||||
} |
||||
key "foo/bar/" { |
||||
policy = "read" |
||||
} |
||||
key "foo/bar/baz" { |
||||
polizy = "deny" |
||||
} |
||||
` |
||||
exp := &Policy{ |
||||
Keys: []*KeyPolicy{ |
||||
&KeyPolicy{ |
||||
Prefix: "", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "foo/", |
||||
Policy: KeyPolicyWrite, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "foo/bar/", |
||||
Policy: KeyPolicyRead, |
||||
}, |
||||
&KeyPolicy{ |
||||
Prefix: "foo/bar/baz", |
||||
Policy: KeyPolicyDeny, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
out, err := Parse(inp) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
if reflect.DeepEqual(out, exp) { |
||||
t.Fatalf("bad: %#v", out) |
||||
} |
||||
} |
Loading…
Reference in new issue