2015-03-18 01:31:20 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2017-01-18 00:40:21 +00:00
|
|
|
"reflect"
|
2017-02-16 23:20:11 +00:00
|
|
|
"strings"
|
2017-01-18 00:40:21 +00:00
|
|
|
|
2015-03-18 01:31:20 +00:00
|
|
|
"github.com/armon/go-radix"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2016-01-07 20:10:05 +00:00
|
|
|
for _, pc := range policy.Paths {
|
2015-07-05 23:31:30 +00:00
|
|
|
// Check which tree to use
|
|
|
|
tree := a.exactRules
|
2016-01-07 20:10:05 +00:00
|
|
|
if pc.Glob {
|
2015-07-05 23:31:30 +00:00
|
|
|
tree = a.globRules
|
|
|
|
}
|
2016-10-22 01:38:05 +00:00
|
|
|
|
2015-03-18 01:31:20 +00:00
|
|
|
// Check for an existing policy
|
2016-01-07 20:10:05 +00:00
|
|
|
raw, ok := tree.Get(pc.Prefix)
|
2015-03-18 01:31:20 +00:00
|
|
|
if !ok {
|
2016-10-09 22:39:58 +00:00
|
|
|
tree.Insert(pc.Prefix, pc.Permissions)
|
2015-03-18 01:31:20 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-10-28 19:33:50 +00:00
|
|
|
|
|
|
|
// these are the ones already in the tree
|
2016-12-07 02:14:15 +00:00
|
|
|
existingPerms := raw.(*Permissions)
|
2016-01-07 20:10:05 +00:00
|
|
|
|
|
|
|
switch {
|
2016-12-07 02:14:15 +00:00
|
|
|
case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0:
|
2016-01-07 20:10:05 +00:00
|
|
|
// If we are explicitly denied in the existing capability set,
|
|
|
|
// don't save anything else
|
2016-12-07 02:14:15 +00:00
|
|
|
continue
|
2016-01-07 20:10:05 +00:00
|
|
|
|
2016-10-09 22:39:58 +00:00
|
|
|
case pc.Permissions.CapabilitiesBitmap&DenyCapabilityInt > 0:
|
2016-01-07 20:10:05 +00:00
|
|
|
// If this new policy explicitly denies, only save the deny value
|
2016-10-14 17:22:00 +00:00
|
|
|
pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
|
2016-12-07 02:14:15 +00:00
|
|
|
pc.Permissions.AllowedParameters = nil
|
|
|
|
pc.Permissions.DeniedParameters = nil
|
|
|
|
goto INSERT
|
2015-03-18 01:31:20 +00:00
|
|
|
|
2016-01-07 20:10:05 +00:00
|
|
|
default:
|
|
|
|
// Insert the capabilities in this new policy into the existing
|
2016-03-03 18:37:51 +00:00
|
|
|
// value
|
2016-12-07 02:14:15 +00:00
|
|
|
pc.Permissions.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
2016-10-16 22:24:32 +00:00
|
|
|
|
2016-12-07 02:14:15 +00:00
|
|
|
if len(existingPerms.AllowedParameters) > 0 {
|
|
|
|
if pc.Permissions.AllowedParameters == nil {
|
2017-02-21 23:02:39 +00:00
|
|
|
pc.Permissions.AllowedParameters = existingPerms.AllowedParameters
|
|
|
|
} else {
|
|
|
|
// Merge the two maps, appending values on key conflict.
|
|
|
|
for key, value := range existingPerms.AllowedParameters {
|
|
|
|
pc.Permissions.AllowedParameters[key] = append(value, pc.Permissions.AllowedParameters[key]...)
|
|
|
|
}
|
2016-12-07 02:14:15 +00:00
|
|
|
}
|
2016-10-22 01:38:05 +00:00
|
|
|
}
|
|
|
|
|
2016-12-07 02:14:15 +00:00
|
|
|
if len(existingPerms.DeniedParameters) > 0 {
|
|
|
|
if pc.Permissions.DeniedParameters == nil {
|
2017-02-21 23:02:39 +00:00
|
|
|
pc.Permissions.DeniedParameters = existingPerms.DeniedParameters
|
|
|
|
} else {
|
2017-01-20 02:41:15 +00:00
|
|
|
|
2017-02-21 23:02:39 +00:00
|
|
|
// Merge the two maps, appending values on key conflict.
|
|
|
|
for key, value := range existingPerms.DeniedParameters {
|
|
|
|
pc.Permissions.DeniedParameters[key] = append(value, pc.Permissions.DeniedParameters[key]...)
|
|
|
|
}
|
2016-12-07 02:14:15 +00:00
|
|
|
}
|
2016-10-22 01:38:05 +00:00
|
|
|
}
|
|
|
|
|
2016-10-28 19:33:50 +00:00
|
|
|
INSERT:
|
|
|
|
|
2016-10-16 22:24:32 +00:00
|
|
|
tree.Insert(pc.Prefix, pc.Permissions)
|
|
|
|
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2016-03-04 17:04:26 +00:00
|
|
|
func (a *ACL) Capabilities(path string) (pathCapabilities []string) {
|
|
|
|
// Fast-path root
|
|
|
|
if a.root {
|
2016-03-04 18:21:07 +00:00
|
|
|
return []string{RootCapability}
|
2016-03-04 17:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find an exact matching rule, look for glob if no match
|
|
|
|
var capabilities uint32
|
|
|
|
raw, ok := a.exactRules.Get(path)
|
2016-10-09 22:39:58 +00:00
|
|
|
|
2016-03-04 17:04:26 +00:00
|
|
|
if ok {
|
2016-10-22 06:45:39 +00:00
|
|
|
perm := raw.(*Permissions)
|
2016-10-22 01:38:05 +00:00
|
|
|
capabilities = perm.CapabilitiesBitmap
|
2016-03-04 17:04:26 +00:00
|
|
|
goto CHECK
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find a glob rule, default deny if no match
|
|
|
|
_, raw, ok = a.globRules.LongestPrefix(path)
|
|
|
|
if !ok {
|
2016-03-04 18:21:07 +00:00
|
|
|
return []string{DenyCapability}
|
2016-03-04 17:04:26 +00:00
|
|
|
} else {
|
2016-10-22 06:45:39 +00:00
|
|
|
perm := raw.(*Permissions)
|
2016-10-14 17:22:00 +00:00
|
|
|
capabilities = perm.CapabilitiesBitmap
|
2016-03-04 17:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CHECK:
|
|
|
|
if capabilities&SudoCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, SudoCapability)
|
|
|
|
}
|
|
|
|
if capabilities&ReadCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, ReadCapability)
|
|
|
|
}
|
|
|
|
if capabilities&ListCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, ListCapability)
|
|
|
|
}
|
|
|
|
if capabilities&UpdateCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, UpdateCapability)
|
|
|
|
}
|
|
|
|
if capabilities&DeleteCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, DeleteCapability)
|
|
|
|
}
|
|
|
|
if capabilities&CreateCapabilityInt > 0 {
|
|
|
|
pathCapabilities = append(pathCapabilities, CreateCapability)
|
|
|
|
}
|
2016-03-04 18:21:07 +00:00
|
|
|
|
|
|
|
// If "deny" is explicitly set or if the path has no capabilities at all,
|
|
|
|
// set the path capabilities to "deny"
|
|
|
|
if capabilities&DenyCapabilityInt > 0 || len(pathCapabilities) == 0 {
|
2016-03-04 17:04:26 +00:00
|
|
|
pathCapabilities = []string{DenyCapability}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-12 22:08:10 +00:00
|
|
|
// AllowOperation is used to check if the given operation is permitted. The
|
|
|
|
// first bool indicates if an op is allowed, the second whether sudo priviliges
|
|
|
|
// exist for that op and path.
|
2017-02-21 23:02:39 +00:00
|
|
|
func (a *ACL) AllowOperation(req *logical.Request) (bool, bool) {
|
2015-03-18 01:31:20 +00:00
|
|
|
// Fast-path root
|
|
|
|
if a.root {
|
2016-01-07 20:10:05 +00:00
|
|
|
return true, true
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
2016-10-22 01:38:05 +00:00
|
|
|
op := req.Operation
|
|
|
|
path := req.Path
|
2016-10-09 22:39:58 +00:00
|
|
|
|
2016-01-07 20:10:05 +00:00
|
|
|
// Help is always allowed
|
|
|
|
if op == logical.HelpOperation {
|
|
|
|
return true, false
|
2015-07-05 23:31:30 +00:00
|
|
|
}
|
|
|
|
|
2016-10-22 04:12:02 +00:00
|
|
|
var permissions *Permissions
|
|
|
|
|
2015-07-05 23:31:30 +00:00
|
|
|
// Find an exact matching rule, look for glob if no match
|
2016-01-12 22:24:01 +00:00
|
|
|
var capabilities uint32
|
2015-07-05 23:31:30 +00:00
|
|
|
raw, ok := a.exactRules.Get(path)
|
2015-03-18 01:31:20 +00:00
|
|
|
if ok {
|
2016-10-22 04:12:02 +00:00
|
|
|
permissions = raw.(*Permissions)
|
|
|
|
capabilities = permissions.CapabilitiesBitmap
|
2015-07-05 23:31:30 +00:00
|
|
|
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 {
|
2016-01-07 20:10:05 +00:00
|
|
|
return false, false
|
2015-07-05 23:31:30 +00:00
|
|
|
} else {
|
2016-10-22 04:12:02 +00:00
|
|
|
permissions = raw.(*Permissions)
|
|
|
|
capabilities = permissions.CapabilitiesBitmap
|
2015-07-05 23:31:30 +00:00
|
|
|
}
|
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
|
2016-01-07 20:10:05 +00:00
|
|
|
// If "deny" has been explicitly set, only deny will be in the map, so we
|
|
|
|
// only need to check for the existence of other values
|
2017-02-21 23:02:39 +00:00
|
|
|
sudo := capabilities&SudoCapabilityInt > 0
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed := false
|
2016-01-13 02:16:31 +00:00
|
|
|
switch op {
|
|
|
|
case logical.ReadOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&ReadCapabilityInt > 0
|
2016-01-13 02:16:31 +00:00
|
|
|
case logical.ListOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&ListCapabilityInt > 0
|
2016-01-13 02:16:31 +00:00
|
|
|
case logical.UpdateOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
2016-01-13 02:16:31 +00:00
|
|
|
case logical.DeleteOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&DeleteCapabilityInt > 0
|
2016-01-13 02:16:31 +00:00
|
|
|
case logical.CreateOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&CreateCapabilityInt > 0
|
2016-01-12 22:10:48 +00:00
|
|
|
|
2017-02-16 06:13:18 +00:00
|
|
|
// These three re-use UpdateCapabilityInt since that's the most appropriate
|
|
|
|
// capability/operation mapping
|
2016-01-13 02:16:31 +00:00
|
|
|
case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
|
2016-10-10 18:21:25 +00:00
|
|
|
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
2016-01-12 22:10:48 +00:00
|
|
|
|
2016-01-12 22:08:10 +00:00
|
|
|
default:
|
|
|
|
return false, false
|
|
|
|
}
|
2016-10-10 18:21:25 +00:00
|
|
|
|
|
|
|
if !operationAllowed {
|
|
|
|
return false, sudo
|
|
|
|
}
|
|
|
|
|
2017-02-16 06:13:18 +00:00
|
|
|
// Only check parameter permissions for operations that can modify
|
|
|
|
// parameters.
|
2017-02-21 23:02:39 +00:00
|
|
|
if op == logical.UpdateOperation || op == logical.CreateOperation {
|
|
|
|
// If there are no data fields, allow
|
2017-02-16 23:20:11 +00:00
|
|
|
if len(req.Data) == 0 {
|
|
|
|
return true, sudo
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(permissions.DeniedParameters) == 0 {
|
|
|
|
goto ALLOWED_PARAMETERS
|
|
|
|
}
|
|
|
|
|
2016-10-22 01:38:05 +00:00
|
|
|
// Check if all parameters have been denied
|
2016-10-22 06:45:39 +00:00
|
|
|
if _, ok := permissions.DeniedParameters["*"]; ok {
|
2016-10-22 01:38:05 +00:00
|
|
|
return false, sudo
|
|
|
|
}
|
2017-01-17 01:48:22 +00:00
|
|
|
|
|
|
|
for parameter, value := range req.Data {
|
2017-02-16 06:19:35 +00:00
|
|
|
// Check if parameter has been explictly denied
|
2017-02-16 23:20:11 +00:00
|
|
|
if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok {
|
2017-01-17 01:48:22 +00:00
|
|
|
// If the value exists in denied values slice, deny
|
2017-02-16 06:13:18 +00:00
|
|
|
if valueInParameterList(value, valueSlice) {
|
|
|
|
return false, sudo
|
|
|
|
}
|
2016-10-22 01:38:05 +00:00
|
|
|
}
|
2017-02-16 23:20:11 +00:00
|
|
|
}
|
2017-01-17 01:48:22 +00:00
|
|
|
|
2017-02-16 23:20:11 +00:00
|
|
|
ALLOWED_PARAMETERS:
|
|
|
|
// If we don't have any allowed parameters set, allow
|
|
|
|
if len(permissions.AllowedParameters) == 0 {
|
|
|
|
return true, sudo
|
|
|
|
}
|
2017-02-16 06:18:20 +00:00
|
|
|
|
2017-02-16 23:20:11 +00:00
|
|
|
_, allowedAll := permissions.AllowedParameters["*"]
|
|
|
|
if len(permissions.AllowedParameters) == 1 && allowedAll {
|
|
|
|
return true, sudo
|
|
|
|
}
|
|
|
|
|
|
|
|
for parameter, value := range req.Data {
|
|
|
|
valueSlice, ok := permissions.AllowedParameters[strings.ToLower(parameter)]
|
|
|
|
// Requested parameter is not in allowed list
|
|
|
|
if !ok && !allowedAll {
|
|
|
|
return false, sudo
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the value doesn't exists in the allowed values slice,
|
|
|
|
// deny
|
|
|
|
if ok && !valueInParameterList(value, valueSlice) {
|
|
|
|
return false, sudo
|
2016-10-22 01:38:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-10 18:21:25 +00:00
|
|
|
|
2017-02-16 06:13:18 +00:00
|
|
|
return true, sudo
|
2015-03-18 01:31:20 +00:00
|
|
|
}
|
2017-01-17 01:48:22 +00:00
|
|
|
|
|
|
|
func valueInParameterList(v interface{}, list []interface{}) bool {
|
2017-02-21 23:02:39 +00:00
|
|
|
// Empty list is equivalent to the item always existing in the list and "*"
|
2017-02-16 23:20:11 +00:00
|
|
|
if len(list) == 0 {
|
2017-01-17 01:48:22 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return valueInSlice(v, list)
|
|
|
|
}
|
|
|
|
|
|
|
|
func valueInSlice(v interface{}, list []interface{}) bool {
|
|
|
|
for _, el := range list {
|
2017-01-18 00:40:21 +00:00
|
|
|
if reflect.DeepEqual(el, v) {
|
2017-01-17 01:48:22 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|