open-vault/vault/acl.go

152 lines
3.5 KiB
Go

package vault
import (
"github.com/armon/go-radix"
"github.com/hashicorp/vault/logical"
)
var (
// Policy lists are used to restrict what is eligible for an operation
anyPolicy = []string{PathPolicyDeny}
readWriteSudo = []string{PathPolicyRead, PathPolicyWrite, PathPolicySudo}
writeSudo = []string{PathPolicyWrite, PathPolicySudo}
// permittedPolicyLevel is used to map each logical operation
// into the set of policies that allow the operation.
permittedPolicyLevels = map[logical.Operation][]string{
logical.ReadOperation: readWriteSudo,
logical.WriteOperation: writeSudo,
logical.DeleteOperation: writeSudo,
logical.ListOperation: readWriteSudo,
logical.HelpOperation: anyPolicy,
logical.RevokeOperation: writeSudo,
logical.RenewOperation: writeSudo,
logical.RollbackOperation: writeSudo,
}
)
// ACL is used to wrap a set of policies to provide
// an efficient interface for access control.
type ACL struct {
// exactRules contains the path policies that are exact
exactRules *radix.Tree
// globRules contains the path policies that glob
globRules *radix.Tree
// root is enabled if the "root" named policy is present.
root bool
}
// New is used to construct a policy based ACL from a set of policies.
func NewACL(policies []*Policy) (*ACL, error) {
// Initialize
a := &ACL{
exactRules: radix.New(),
globRules: radix.New(),
root: false,
}
// Inject each policy
for _, policy := range policies {
// Ignore a nil policy object
if policy == nil {
continue
}
// Check if this is root
if policy.Name == "root" {
a.root = true
}
for _, pp := range policy.Paths {
// Check which tree to use
tree := a.exactRules
if pp.Glob {
tree = a.globRules
}
// Check for an existing policy
raw, ok := tree.Get(pp.Prefix)
if !ok {
tree.Insert(pp.Prefix, pp)
continue
}
existing := raw.(*PathPolicy)
// Check if this policy is takes precedence
if pp.TakesPrecedence(existing) {
tree.Insert(pp.Prefix, pp)
}
}
}
return a, nil
}
// AllowOperation is used to check if the given operation is permitted
func (a *ACL) AllowOperation(op logical.Operation, path string) bool {
// Fast-path root
if a.root {
return true
}
// Check if any policy level allows this operation
permitted := permittedPolicyLevels[op]
if permitted[0] == PathPolicyDeny {
return true
}
// Find an exact matching rule, look for glob if no match
var policy *PathPolicy
raw, ok := a.exactRules.Get(path)
if ok {
policy = raw.(*PathPolicy)
goto CHECK
}
// Find a glob rule, default deny if no match
_, raw, ok = a.globRules.LongestPrefix(path)
if !ok {
return false
} else {
policy = raw.(*PathPolicy)
}
CHECK:
// Check if the minimum permissions are met
for _, allowed := range permitted {
if allowed == policy.Policy {
return true
}
}
return false
}
// RootPrivilege checks if the user has root level permission
// to given path. This requires that the user be root, or that
// sudo privilege is available on that path.
func (a *ACL) RootPrivilege(path string) bool {
// Fast-path root
if a.root {
return true
}
// Find an exact matching rule, look for glob if no match
var policy *PathPolicy
raw, ok := a.exactRules.Get(path)
if ok {
policy = raw.(*PathPolicy)
goto CHECK
}
// Check the rules for a match, default deny if no match
_, raw, ok = a.globRules.LongestPrefix(path)
if !ok {
return false
} else {
policy = raw.(*PathPolicy)
}
CHECK:
// Check the policy level
return policy.Policy == PathPolicySudo
}