2015-03-18 01:31:20 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/armon/go-radix"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
)
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
)
|
2015-03-18 01:31:20 +00:00
|
|
|
|
|
|
|
// ACL is used to wrap a set of policies to provide
|
|
|
|
// an efficient interface for access control.
|
|
|
|
type ACL struct {
|
2015-07-05 23:31:30 +00:00
|
|
|
// exactRules contains the path policies that are exact
|
|
|
|
exactRules *radix.Tree
|
|
|
|
|
|
|
|
// globRules contains the path policies that glob
|
|
|
|
globRules *radix.Tree
|
2015-03-18 01:31:20 +00:00
|
|
|
|
|
|
|
// 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{
|
2015-07-05 23:31:30 +00:00
|
|
|
exactRules: radix.New(),
|
|
|
|
globRules: radix.New(),
|
|
|
|
root: false,
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Inject each policy
|
|
|
|
for _, policy := range policies {
|
2015-03-24 22:49:17 +00:00
|
|
|
// Ignore a nil policy object
|
|
|
|
if policy == nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-03-18 01:31:20 +00:00
|
|
|
// Check if this is root
|
|
|
|
if policy.Name == "root" {
|
|
|
|
a.root = true
|
|
|
|
}
|
|
|
|
for _, pp := range policy.Paths {
|
2015-07-05 23:31:30 +00:00
|
|
|
// Check which tree to use
|
|
|
|
tree := a.exactRules
|
|
|
|
if pp.Glob {
|
|
|
|
tree = a.globRules
|
|
|
|
}
|
2015-03-18 01:31:20 +00:00
|
|
|
|
|
|
|
// Check for an existing policy
|
2015-07-05 23:31:30 +00:00
|
|
|
raw, ok := tree.Get(pp.Prefix)
|
2015-03-18 01:31:20 +00:00
|
|
|
if !ok {
|
2015-07-05 23:31:30 +00:00
|
|
|
tree.Insert(pp.Prefix, pp)
|
2015-03-18 01:31:20 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-07-05 23:31:30 +00:00
|
|
|
existing := raw.(*PathPolicy)
|
2015-03-18 01:31:20 +00:00
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
// Check if this policy is takes precedence
|
|
|
|
if pp.TakesPrecedence(existing) {
|
|
|
|
tree.Insert(pp.Prefix, pp)
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
// 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)
|
2015-03-18 01:31:20 +00:00
|
|
|
if ok {
|
2015-07-05 23:31:30 +00:00
|
|
|
policy = raw.(*PathPolicy)
|
|
|
|
goto CHECK
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
// Find a glob rule, default deny if no match
|
|
|
|
_, raw, ok = a.globRules.LongestPrefix(path)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
policy = raw.(*PathPolicy)
|
|
|
|
}
|
2015-03-18 01:31:20 +00:00
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
CHECK:
|
2015-03-18 01:31:20 +00:00
|
|
|
// Check if the minimum permissions are met
|
2015-07-05 23:31:30 +00:00
|
|
|
for _, allowed := range permitted {
|
|
|
|
if allowed == policy.Policy {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
// 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)
|
2015-03-18 01:31:20 +00:00
|
|
|
if !ok {
|
|
|
|
return false
|
2015-07-05 23:31:30 +00:00
|
|
|
} else {
|
|
|
|
policy = raw.(*PathPolicy)
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
CHECK:
|
2015-03-18 01:31:20 +00:00
|
|
|
// Check the policy level
|
2015-07-05 23:31:30 +00:00
|
|
|
return policy.Policy == PathPolicySudo
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|