open-vault/vault/acl.go

524 lines
15 KiB
Go
Raw Normal View History

2015-03-18 01:31:20 +00:00
package vault
import (
2018-01-19 07:43:36 +00:00
"context"
2017-10-23 21:15:56 +00:00
"fmt"
"reflect"
2017-02-16 23:20:11 +00:00
"strings"
2018-09-18 03:03:00 +00:00
radix "github.com/armon/go-radix"
"github.com/hashicorp/errwrap"
2017-10-23 21:15:56 +00:00
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/helper/identity"
2018-09-18 03:03:00 +00:00
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/strutil"
2015-03-18 01:31:20 +00:00
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
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 {
// exactRules contains the path policies that are exact
exactRules *radix.Tree
// prefixRules contains the path policies that are a prefix
prefixRules *radix.Tree
2015-03-18 01:31:20 +00:00
// root is enabled if the "root" named policy is present.
root bool
2018-09-18 03:03:00 +00:00
// Stores policies that are actually RGPs for later fetching
rgpPolicies []*Policy
2015-03-18 01:31:20 +00:00
}
2017-10-23 20:42:56 +00:00
type PolicyCheckOpts struct {
RootPrivsRequired bool
Unauth bool
}
type AuthResults struct {
ACLResults *ACLResults
Allowed bool
RootPrivs bool
DeniedError bool
Error *multierror.Error
2017-10-23 20:42:56 +00:00
}
type ACLResults struct {
2018-09-18 03:03:00 +00:00
Allowed bool
RootPrivs bool
IsRoot bool
MFAMethods []string
ControlGroup *ControlGroup
CapabilitiesBitmap uint32
2017-10-23 20:42:56 +00:00
}
2018-09-18 03:03:00 +00:00
// NewACL is used to construct a policy based ACL from a set of policies.
func NewACL(ctx context.Context, policies []*Policy) (*ACL, error) {
2015-03-18 01:31:20 +00:00
// Initialize
a := &ACL{
exactRules: radix.New(),
prefixRules: radix.New(),
root: false,
2015-03-18 01:31:20 +00:00
}
2018-09-18 03:03:00 +00:00
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, err
}
if ns == nil {
return nil, namespace.ErrNoNamespace
}
2015-03-18 01:31:20 +00:00
// Inject each policy
for _, policy := range policies {
// Ignore a nil policy object
if policy == nil {
continue
}
2017-10-23 20:42:56 +00:00
switch policy.Type {
case PolicyTypeACL:
2018-09-18 03:03:00 +00:00
case PolicyTypeRGP:
a.rgpPolicies = append(a.rgpPolicies, policy)
continue
2017-10-23 20:42:56 +00:00
default:
return nil, fmt.Errorf("unable to parse policy (wrong type)")
}
2015-03-18 01:31:20 +00:00
// Check if this is root
if policy.Name == "root" {
2018-09-18 03:03:00 +00:00
if ns.ID != namespace.RootNamespaceID {
return nil, fmt.Errorf("root policy is only allowed in root namespace")
}
if len(policies) != 1 {
return nil, fmt.Errorf("other policies present along with root")
}
2015-03-18 01:31:20 +00:00
a.root = true
}
for _, pc := range policy.Paths {
// Check which tree to use
tree := a.exactRules
if pc.IsPrefix {
tree = a.prefixRules
}
2015-03-18 01:31:20 +00:00
// Check for an existing policy
raw, ok := tree.Get(pc.Path)
2015-03-18 01:31:20 +00:00
if !ok {
clonedPerms, err := pc.Permissions.Clone()
if err != nil {
return nil, errwrap.Wrapf("error cloning ACL permissions: {{err}}", err)
}
tree.Insert(pc.Path, clonedPerms)
2015-03-18 01:31:20 +00:00
continue
}
// these are the ones already in the tree
2017-10-23 20:42:56 +00:00
existingPerms := raw.(*ACLPermissions)
switch {
case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0:
// If we are explicitly denied in the existing capability set,
// don't save anything else
continue
case pc.Permissions.CapabilitiesBitmap&DenyCapabilityInt > 0:
// If this new policy explicitly denies, only save the deny value
existingPerms.CapabilitiesBitmap = DenyCapabilityInt
existingPerms.AllowedParameters = nil
existingPerms.DeniedParameters = nil
goto INSERT
2015-03-18 01:31:20 +00:00
default:
// Insert the capabilities in this new policy into the existing
2016-03-03 18:37:51 +00:00
// value
existingPerms.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap
2015-03-18 01:31:20 +00:00
}
// Note: In these stanzas, we're preferring minimum lifetimes. So
// we take the lesser of two specified max values, or we take the
// lesser of two specified min values, the idea being, allowing
// token lifetime to be minimum possible.
//
// If we have an existing max, and we either don't have a current
// max, or the current is greater than the previous, use the
// existing.
if pc.Permissions.MaxWrappingTTL > 0 &&
(existingPerms.MaxWrappingTTL == 0 ||
pc.Permissions.MaxWrappingTTL < existingPerms.MaxWrappingTTL) {
existingPerms.MaxWrappingTTL = pc.Permissions.MaxWrappingTTL
}
// If we have an existing min, and we either don't have a current
// min, or the current is greater than the previous, use the
// existing
if pc.Permissions.MinWrappingTTL > 0 &&
(existingPerms.MinWrappingTTL == 0 ||
pc.Permissions.MinWrappingTTL < existingPerms.MinWrappingTTL) {
existingPerms.MinWrappingTTL = pc.Permissions.MinWrappingTTL
}
if len(pc.Permissions.AllowedParameters) > 0 {
if existingPerms.AllowedParameters == nil {
clonedAllowed, err := copystructure.Copy(pc.Permissions.AllowedParameters)
if err != nil {
return nil, err
}
existingPerms.AllowedParameters = clonedAllowed.(map[string][]interface{})
2017-02-21 23:02:39 +00:00
} else {
for key, value := range pc.Permissions.AllowedParameters {
pcValue, ok := existingPerms.AllowedParameters[key]
// If an empty array exist it should overwrite any other
// value.
if len(value) == 0 || (ok && len(pcValue) == 0) {
existingPerms.AllowedParameters[key] = []interface{}{}
} else {
// Merge the two maps, appending values on key conflict.
existingPerms.AllowedParameters[key] = append(value, existingPerms.AllowedParameters[key]...)
}
2017-02-21 23:02:39 +00:00
}
}
}
if len(pc.Permissions.DeniedParameters) > 0 {
if existingPerms.DeniedParameters == nil {
clonedDenied, err := copystructure.Copy(pc.Permissions.DeniedParameters)
if err != nil {
return nil, err
}
existingPerms.DeniedParameters = clonedDenied.(map[string][]interface{})
2017-02-21 23:02:39 +00:00
} else {
for key, value := range pc.Permissions.DeniedParameters {
pcValue, ok := existingPerms.DeniedParameters[key]
// If an empty array exist it should overwrite any other
// value.
if len(value) == 0 || (ok && len(pcValue) == 0) {
existingPerms.DeniedParameters[key] = []interface{}{}
} else {
// Merge the two maps, appending values on key conflict.
existingPerms.DeniedParameters[key] = append(value, existingPerms.DeniedParameters[key]...)
}
2017-02-21 23:02:39 +00:00
}
}
}
if len(pc.Permissions.RequiredParameters) > 0 {
if len(existingPerms.RequiredParameters) == 0 {
existingPerms.RequiredParameters = pc.Permissions.RequiredParameters
} else {
for _, v := range pc.Permissions.RequiredParameters {
if !strutil.StrListContains(existingPerms.RequiredParameters, v) {
existingPerms.RequiredParameters = append(existingPerms.RequiredParameters, v)
}
}
}
}
2018-09-18 03:03:00 +00:00
if len(pc.Permissions.MFAMethods) > 0 {
if existingPerms.MFAMethods == nil {
existingPerms.MFAMethods = pc.Permissions.MFAMethods
} else {
for _, method := range pc.Permissions.MFAMethods {
existingPerms.MFAMethods = append(existingPerms.MFAMethods, method)
}
}
existingPerms.MFAMethods = strutil.RemoveDuplicates(existingPerms.MFAMethods, false)
}
// No need to dedupe this list since any authorization can satisfy any factor
if pc.Permissions.ControlGroup != nil {
if len(pc.Permissions.ControlGroup.Factors) > 0 {
if existingPerms.ControlGroup == nil {
existingPerms.ControlGroup = pc.Permissions.ControlGroup
} else {
for _, authz := range pc.Permissions.ControlGroup.Factors {
existingPerms.ControlGroup.Factors = append(existingPerms.ControlGroup.Factors, authz)
}
}
}
}
INSERT:
tree.Insert(pc.Path, existingPerms)
2015-03-18 01:31:20 +00:00
}
}
return a, nil
}
2018-09-18 03:03:00 +00:00
func (a *ACL) Capabilities(ctx context.Context, path string) (pathCapabilities []string) {
req := &logical.Request{
Path: path,
// doesn't matter, but use List to trigger fallback behavior so we can
// model real behavior
Operation: logical.ListOperation,
}
2018-09-18 03:03:00 +00:00
res := a.AllowOperation(ctx, req, true)
if res.IsRoot {
return []string{RootCapability}
}
2018-09-18 03:03:00 +00:00
capabilities := res.CapabilitiesBitmap
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)
}
// 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 {
pathCapabilities = []string{DenyCapability}
}
return
}
2017-10-23 20:42:56 +00:00
// AllowOperation is used to check if the given operation is permitted.
2018-09-18 03:03:00 +00:00
func (a *ACL) AllowOperation(ctx context.Context, req *logical.Request, capCheckOnly bool) (ret *ACLResults) {
2017-10-23 20:42:56 +00:00
ret = new(ACLResults)
2015-03-18 01:31:20 +00:00
// Fast-path root
if a.root {
2017-10-23 20:42:56 +00:00
ret.Allowed = true
ret.RootPrivs = true
ret.IsRoot = true
return
2015-03-18 01:31:20 +00:00
}
op := req.Operation
// Help is always allowed
if op == logical.HelpOperation {
2017-10-23 20:42:56 +00:00
ret.Allowed = true
return
}
2017-10-23 20:42:56 +00:00
var permissions *ACLPermissions
2016-10-22 04:12:02 +00:00
2018-09-18 03:03:00 +00:00
ns, err := namespace.FromContext(ctx)
if err != nil {
return
}
path := ns.Path + req.Path
// Find an exact matching rule, look for glob if no match
2016-01-12 22:24:01 +00:00
var capabilities uint32
raw, ok := a.exactRules.Get(path)
2015-03-18 01:31:20 +00:00
if ok {
2017-10-23 20:42:56 +00:00
permissions = raw.(*ACLPermissions)
2016-10-22 04:12:02 +00:00
capabilities = permissions.CapabilitiesBitmap
goto CHECK
2015-03-18 01:31:20 +00:00
}
if op == logical.ListOperation {
raw, ok = a.exactRules.Get(strings.TrimSuffix(path, "/"))
if ok {
permissions = raw.(*ACLPermissions)
capabilities = permissions.CapabilitiesBitmap
goto CHECK
}
}
2015-03-18 01:31:20 +00:00
// Find a glob rule, default deny if no match
_, raw, ok = a.prefixRules.LongestPrefix(path)
if !ok {
2017-10-23 20:42:56 +00:00
return
}
2018-09-18 03:03:00 +00:00
permissions = raw.(*ACLPermissions)
capabilities = permissions.CapabilitiesBitmap
2015-03-18 01:31:20 +00:00
CHECK:
2015-03-18 01:31:20 +00:00
// Check if the minimum permissions are met
// 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-10-23 20:42:56 +00:00
ret.RootPrivs = capabilities&SudoCapabilityInt > 0
2018-09-18 03:03:00 +00:00
// This is after the RootPrivs check so we can gate on it being from sudo
// rather than policy root
if capCheckOnly {
ret.CapabilitiesBitmap = capabilities
return ret
}
ret.MFAMethods = permissions.MFAMethods
ret.ControlGroup = permissions.ControlGroup
operationAllowed := false
switch op {
case logical.ReadOperation:
operationAllowed = capabilities&ReadCapabilityInt > 0
case logical.ListOperation:
operationAllowed = capabilities&ListCapabilityInt > 0
case logical.UpdateOperation:
operationAllowed = capabilities&UpdateCapabilityInt > 0
case logical.DeleteOperation:
operationAllowed = capabilities&DeleteCapabilityInt > 0
case logical.CreateOperation:
operationAllowed = capabilities&CreateCapabilityInt > 0
2016-01-12 22:10:48 +00:00
// These three re-use UpdateCapabilityInt since that's the most appropriate
// capability/operation mapping
case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
operationAllowed = capabilities&UpdateCapabilityInt > 0
2016-01-12 22:10:48 +00:00
2016-01-12 22:08:10 +00:00
default:
2017-10-23 20:42:56 +00:00
return
2016-01-12 22:08:10 +00:00
}
if !operationAllowed {
2017-10-23 20:42:56 +00:00
return
}
if permissions.MaxWrappingTTL > 0 {
if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL {
2017-10-23 20:42:56 +00:00
return
}
}
if permissions.MinWrappingTTL > 0 {
if req.WrapInfo == nil || req.WrapInfo.TTL < permissions.MinWrappingTTL {
2017-10-23 20:42:56 +00:00
return
}
}
// This situation can happen because of merging, even though in a single
// path statement we check on ingress
if permissions.MinWrappingTTL != 0 &&
permissions.MaxWrappingTTL != 0 &&
permissions.MaxWrappingTTL < permissions.MinWrappingTTL {
2017-10-23 20:42:56 +00:00
return
}
// Only check parameter permissions for operations that can modify
// parameters.
if op == logical.ReadOperation || op == logical.UpdateOperation || op == logical.CreateOperation {
for _, parameter := range permissions.RequiredParameters {
if _, ok := req.Data[strings.ToLower(parameter)]; !ok {
return
}
}
2017-02-21 23:02:39 +00:00
// If there are no data fields, allow
2017-02-16 23:20:11 +00:00
if len(req.Data) == 0 {
2017-10-23 20:42:56 +00:00
ret.Allowed = true
return
2017-02-16 23:20:11 +00:00
}
if len(permissions.DeniedParameters) == 0 {
goto ALLOWED_PARAMETERS
}
// Check if all parameters have been denied
if _, ok := permissions.DeniedParameters["*"]; ok {
2017-10-23 20:42:56 +00:00
return
}
for parameter, value := range req.Data {
2018-03-20 18:54:10 +00:00
// Check if parameter has been explicitly denied
2017-02-16 23:20:11 +00:00
if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok {
// If the value exists in denied values slice, deny
if valueInParameterList(value, valueSlice) {
2017-10-23 20:42:56 +00:00
return
}
}
2017-02-16 23:20:11 +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 {
2017-10-23 20:42:56 +00:00
ret.Allowed = true
return
2017-02-16 23:20:11 +00:00
}
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 {
2017-10-23 20:42:56 +00:00
ret.Allowed = true
return
2017-02-16 23:20:11 +00:00
}
for parameter, value := range req.Data {
valueSlice, ok := permissions.AllowedParameters[strings.ToLower(parameter)]
// Requested parameter is not in allowed list
if !ok && !allowedAll {
2017-10-23 20:42:56 +00:00
return
2017-02-16 23:20:11 +00:00
}
// If the value doesn't exists in the allowed values slice,
// deny
if ok && !valueInParameterList(value, valueSlice) {
2017-10-23 20:42:56 +00:00
return
}
}
}
2017-10-23 20:42:56 +00:00
ret.Allowed = true
return
}
2018-09-18 03:03:00 +00:00
func (c *Core) performPolicyChecks(ctx context.Context, acl *ACL, te *logical.TokenEntry, req *logical.Request, inEntity *identity.Entity, opts *PolicyCheckOpts) *AuthResults {
ret := new(AuthResults)
2017-10-23 20:42:56 +00:00
// First, perform normal ACL checks if requested. The only time no ACL
// should be applied is if we are only processing EGPs against a login
// path in which case opts.Unauth will be set.
if acl != nil && !opts.Unauth {
2018-09-18 03:03:00 +00:00
ret.ACLResults = acl.AllowOperation(ctx, req, false)
2017-10-23 20:42:56 +00:00
ret.RootPrivs = ret.ACLResults.RootPrivs
// Root is always allowed; skip Sentinel/MFA checks
if ret.ACLResults.IsRoot {
2018-09-18 03:03:00 +00:00
//logger.Warn("token is root, skipping checks")
2017-10-23 20:42:56 +00:00
ret.Allowed = true
2018-09-18 03:03:00 +00:00
return ret
2017-10-23 20:42:56 +00:00
}
if !ret.ACLResults.Allowed {
2018-09-18 03:03:00 +00:00
return ret
2017-10-23 20:42:56 +00:00
}
if !ret.RootPrivs && opts.RootPrivsRequired {
2018-09-18 03:03:00 +00:00
return ret
2017-10-23 20:42:56 +00:00
}
}
2018-09-18 03:03:00 +00:00
c.performEntPolicyChecks(ctx, acl, te, req, inEntity, opts, ret)
return ret
2015-03-18 01:31:20 +00:00
}
func valueInParameterList(v interface{}, list []interface{}) bool {
2017-02-22 00:06:00 +00:00
// Empty list is equivalent to the item always existing in the list
2017-02-16 23:20:11 +00:00
if len(list) == 0 {
return true
}
return valueInSlice(v, list)
}
func valueInSlice(v interface{}, list []interface{}) bool {
for _, el := range list {
if reflect.TypeOf(el).String() == "string" && reflect.TypeOf(v).String() == "string" {
item := el.(string)
val := v.(string)
if strutil.GlobbedStringsMatch(item, val) {
return true
}
} else if reflect.DeepEqual(el, v) {
return true
}
}
return false
}