diff --git a/acl/acl.go b/acl/acl.go index 0b1109418..442837340 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -35,9 +35,21 @@ func init() { // ACL is the interface for policy enforcement. type ACL interface { + // KeyRead checks for permission to read a given key KeyRead(string) bool + + // KeyWrite checks for permission to write a given key KeyWrite(string) bool + + // KeyWritePrefix checks for permission to write to an + // entire key prefix. This means there must be no sub-policies + // that deny a write. + KeyWritePrefix(string) bool + + // ACLList checks for permission to list all the ACLs ACLList() bool + + // ACLModify checks for permission to manipulate ACLs ACLModify() bool } @@ -57,6 +69,10 @@ func (s *StaticACL) KeyWrite(string) bool { return s.defaultAllow } +func (s *StaticACL) KeyWritePrefix(string) bool { + return s.defaultAllow +} + func (s *StaticACL) ACLList() bool { return s.allowManage } @@ -156,6 +172,39 @@ func (p *PolicyACL) KeyWrite(key string) bool { return p.parent.KeyWrite(key) } +// KeyWritePrefix returns if a prefix is allowed to be written +func (p *PolicyACL) KeyWritePrefix(prefix string) bool { + // Look for a matching rule that denies + _, rule, ok := p.keyRules.LongestPrefix(prefix) + if ok && rule.(string) != KeyPolicyWrite { + return false + } + + // Look if any of our children have a deny policy + deny := false + p.keyRules.WalkPrefix(prefix, func(path string, rule interface{}) bool { + // We have a rule to prevent a write in a sub-directory! + if rule.(string) != KeyPolicyWrite { + deny = true + return true + } + return false + }) + + // Deny the write if any sub-rules may be violated + if deny { + return false + } + + // If we had a matching rule, done + if ok { + return true + } + + // No matching rule, use the parent. + return p.parent.KeyWritePrefix(prefix) +} + // ACLList checks if listing of ACLs is allowed func (p *PolicyACL) ACLList() bool { return p.parent.ACLList() diff --git a/acl/acl_test.go b/acl/acl_test.go index 37cff4d07..9be0388db 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -103,16 +103,19 @@ func TestPolicyACL(t *testing.T) { } type tcase struct { - inp string - read bool - write bool + inp string + read bool + write bool + writePrefix bool } cases := []tcase{ - {"other", true, true}, - {"foo/test", true, true}, - {"foo/priv/test", false, false}, - {"bar/any", false, false}, - {"zip/test", true, false}, + {"other", true, true, true}, + {"foo/test", true, true, true}, + {"foo/priv/test", false, false, false}, + {"bar/any", false, false, false}, + {"zip/test", true, false, false}, + {"foo/", true, true, false}, + {"", true, true, false}, } for _, c := range cases { if c.read != acl.KeyRead(c.inp) { @@ -121,6 +124,9 @@ func TestPolicyACL(t *testing.T) { if c.write != acl.KeyWrite(c.inp) { t.Fatalf("Write fail: %#v", c) } + if c.writePrefix != acl.KeyWritePrefix(c.inp) { + t.Fatalf("Write prefix fail: %#v", c) + } } } @@ -165,16 +171,17 @@ func TestPolicyACL_Parent(t *testing.T) { } type tcase struct { - inp string - read bool - write bool + inp string + read bool + write bool + writePrefix bool } cases := []tcase{ - {"other", false, false}, - {"foo/test", true, true}, - {"foo/priv/test", true, false}, - {"bar/any", false, false}, - {"zip/test", true, false}, + {"other", false, false, false}, + {"foo/test", true, true, true}, + {"foo/priv/test", true, false, false}, + {"bar/any", false, false, false}, + {"zip/test", true, false, false}, } for _, c := range cases { if c.read != acl.KeyRead(c.inp) { @@ -183,5 +190,8 @@ func TestPolicyACL_Parent(t *testing.T) { if c.write != acl.KeyWrite(c.inp) { t.Fatalf("Write fail: %#v", c) } + if c.writePrefix != acl.KeyWritePrefix(c.inp) { + t.Fatalf("Write prefix fail: %#v", c) + } } }