package acl
import (
"github.com/armon/go-radix"
)
var (
// allowAll is a singleton policy which allows all
// non-management actions
allowAll ACL
// denyAll is a singleton policy which denies all actions
denyAll ACL
// manageAll is a singleton policy which allows all
// actions, including management
manageAll ACL
)
func init ( ) {
// Setup the singletons
allowAll = & StaticACL {
allowManage : false ,
defaultAllow : true ,
}
denyAll = & StaticACL {
allowManage : false ,
defaultAllow : false ,
}
manageAll = & StaticACL {
allowManage : true ,
defaultAllow : true ,
}
}
// 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
}
// 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 {
allowManage bool
defaultAllow bool
}
func ( s * StaticACL ) KeyRead ( string ) bool {
return s . defaultAllow
}
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
}
func ( s * StaticACL ) ACLModify ( ) bool {
return s . allowManage
}
// 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
}
// ManageAll returns an ACL rule that can manage all resources
func ManageAll ( ) ACL {
return manageAll
}
// RootACL returns a possible ACL if the ID matches a root policy
func RootACL ( id string ) ACL {
switch id {
case "allow" :
return allowAll
case "deny" :
return denyAll
case "manage" :
return manageAll
default :
return nil
}
}
// 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
// keyRules contains the key policies
keyRules * 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 ,
keyRules : radix . New ( ) ,
}
// Load the key policy
for _ , kp := range policy . Keys {
p . keyRules . Insert ( kp . Prefix , kp . Policy )
}
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 . keyRules . LongestPrefix ( key )
if ok {
switch rule . ( string ) {
case KeyPolicyRead :
return true
case KeyPolicyWrite :
return true
default :
return false
}
}
// 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 . keyRules . LongestPrefix ( key )
if ok {
switch rule . ( string ) {
case KeyPolicyWrite :
return true
default :
return false
}
}
// No matching rule, use the parent.
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 ( )
}
// ACLModify checks if modification of ACLs is allowed
func ( p * PolicyACL ) ACLModify ( ) bool {
return p . parent . ACLModify ( )
}