Merge pull request #2290 from hashicorp/acl-parameters-permission
Acl parameters permission
This commit is contained in:
commit
77cacfdac9
178
vault/acl.go
178
vault/acl.go
|
@ -1,6 +1,9 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/armon/go-radix"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
@ -47,25 +50,72 @@ func NewACL(policies []*Policy) (*ACL, error) {
|
|||
// Check for an existing policy
|
||||
raw, ok := tree.Get(pc.Prefix)
|
||||
if !ok {
|
||||
tree.Insert(pc.Prefix, pc.CapabilitiesBitmap)
|
||||
tree.Insert(pc.Prefix, pc.Permissions)
|
||||
continue
|
||||
}
|
||||
existing := raw.(uint32)
|
||||
|
||||
// these are the ones already in the tree
|
||||
existingPerms := raw.(*Permissions)
|
||||
|
||||
switch {
|
||||
case existing&DenyCapabilityInt > 0:
|
||||
case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0:
|
||||
// If we are explicitly denied in the existing capability set,
|
||||
// don't save anything else
|
||||
continue
|
||||
|
||||
case pc.CapabilitiesBitmap&DenyCapabilityInt > 0:
|
||||
case pc.Permissions.CapabilitiesBitmap&DenyCapabilityInt > 0:
|
||||
// If this new policy explicitly denies, only save the deny value
|
||||
tree.Insert(pc.Prefix, DenyCapabilityInt)
|
||||
pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
|
||||
pc.Permissions.AllowedParameters = nil
|
||||
pc.Permissions.DeniedParameters = nil
|
||||
goto INSERT
|
||||
|
||||
default:
|
||||
// Insert the capabilities in this new policy into the existing
|
||||
// value
|
||||
tree.Insert(pc.Prefix, existing|pc.CapabilitiesBitmap)
|
||||
pc.Permissions.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap
|
||||
}
|
||||
|
||||
if len(existingPerms.AllowedParameters) > 0 {
|
||||
if pc.Permissions.AllowedParameters == nil {
|
||||
pc.Permissions.AllowedParameters = existingPerms.AllowedParameters
|
||||
} else {
|
||||
for key, value := range existingPerms.AllowedParameters {
|
||||
pcValue, ok := pc.Permissions.AllowedParameters[key]
|
||||
// If an empty array exist it should overwrite any other
|
||||
// value.
|
||||
if len(value) == 0 || (ok && len(pcValue) == 0) {
|
||||
pc.Permissions.AllowedParameters[key] = []interface{}{}
|
||||
} else {
|
||||
// Merge the two maps, appending values on key conflict.
|
||||
pc.Permissions.AllowedParameters[key] = append(value, pc.Permissions.AllowedParameters[key]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(existingPerms.DeniedParameters) > 0 {
|
||||
if pc.Permissions.DeniedParameters == nil {
|
||||
pc.Permissions.DeniedParameters = existingPerms.DeniedParameters
|
||||
} else {
|
||||
for key, value := range existingPerms.DeniedParameters {
|
||||
pcValue, ok := pc.Permissions.DeniedParameters[key]
|
||||
// If an empty array exist it should overwrite any other
|
||||
// value.
|
||||
if len(value) == 0 || (ok && len(pcValue) == 0) {
|
||||
pc.Permissions.DeniedParameters[key] = []interface{}{}
|
||||
} else {
|
||||
// Merge the two maps, appending values on key conflict.
|
||||
pc.Permissions.DeniedParameters[key] = append(value, pc.Permissions.DeniedParameters[key]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSERT:
|
||||
|
||||
tree.Insert(pc.Prefix, pc.Permissions)
|
||||
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
|
@ -80,8 +130,10 @@ func (a *ACL) Capabilities(path string) (pathCapabilities []string) {
|
|||
// Find an exact matching rule, look for glob if no match
|
||||
var capabilities uint32
|
||||
raw, ok := a.exactRules.Get(path)
|
||||
|
||||
if ok {
|
||||
capabilities = raw.(uint32)
|
||||
perm := raw.(*Permissions)
|
||||
capabilities = perm.CapabilitiesBitmap
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
|
@ -90,7 +142,8 @@ func (a *ACL) Capabilities(path string) (pathCapabilities []string) {
|
|||
if !ok {
|
||||
return []string{DenyCapability}
|
||||
} else {
|
||||
capabilities = raw.(uint32)
|
||||
perm := raw.(*Permissions)
|
||||
capabilities = perm.CapabilitiesBitmap
|
||||
}
|
||||
|
||||
CHECK:
|
||||
|
@ -124,22 +177,27 @@ CHECK:
|
|||
// 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.
|
||||
func (a *ACL) AllowOperation(op logical.Operation, path string) (allowed bool, sudo bool) {
|
||||
func (a *ACL) AllowOperation(req *logical.Request) (bool, bool) {
|
||||
// Fast-path root
|
||||
if a.root {
|
||||
return true, true
|
||||
}
|
||||
op := req.Operation
|
||||
path := req.Path
|
||||
|
||||
// Help is always allowed
|
||||
if op == logical.HelpOperation {
|
||||
return true, false
|
||||
}
|
||||
|
||||
var permissions *Permissions
|
||||
|
||||
// Find an exact matching rule, look for glob if no match
|
||||
var capabilities uint32
|
||||
raw, ok := a.exactRules.Get(path)
|
||||
if ok {
|
||||
capabilities = raw.(uint32)
|
||||
permissions = raw.(*Permissions)
|
||||
capabilities = permissions.CapabilitiesBitmap
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
|
@ -148,32 +206,112 @@ func (a *ACL) AllowOperation(op logical.Operation, path string) (allowed bool, s
|
|||
if !ok {
|
||||
return false, false
|
||||
} else {
|
||||
capabilities = raw.(uint32)
|
||||
permissions = raw.(*Permissions)
|
||||
capabilities = permissions.CapabilitiesBitmap
|
||||
}
|
||||
|
||||
CHECK:
|
||||
// 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
|
||||
sudo = capabilities&SudoCapabilityInt > 0
|
||||
sudo := capabilities&SudoCapabilityInt > 0
|
||||
operationAllowed := false
|
||||
switch op {
|
||||
case logical.ReadOperation:
|
||||
allowed = capabilities&ReadCapabilityInt > 0
|
||||
operationAllowed = capabilities&ReadCapabilityInt > 0
|
||||
case logical.ListOperation:
|
||||
allowed = capabilities&ListCapabilityInt > 0
|
||||
operationAllowed = capabilities&ListCapabilityInt > 0
|
||||
case logical.UpdateOperation:
|
||||
allowed = capabilities&UpdateCapabilityInt > 0
|
||||
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
||||
case logical.DeleteOperation:
|
||||
allowed = capabilities&DeleteCapabilityInt > 0
|
||||
operationAllowed = capabilities&DeleteCapabilityInt > 0
|
||||
case logical.CreateOperation:
|
||||
allowed = capabilities&CreateCapabilityInt > 0
|
||||
operationAllowed = capabilities&CreateCapabilityInt > 0
|
||||
|
||||
// These three re-use UpdateCapabilityInt since that's the most appropriate capability/operation mapping
|
||||
// These three re-use UpdateCapabilityInt since that's the most appropriate
|
||||
// capability/operation mapping
|
||||
case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
|
||||
allowed = capabilities&UpdateCapabilityInt > 0
|
||||
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
||||
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
return
|
||||
|
||||
if !operationAllowed {
|
||||
return false, sudo
|
||||
}
|
||||
|
||||
// Only check parameter permissions for operations that can modify
|
||||
// parameters.
|
||||
if op == logical.UpdateOperation || op == logical.CreateOperation {
|
||||
// If there are no data fields, allow
|
||||
if len(req.Data) == 0 {
|
||||
return true, sudo
|
||||
}
|
||||
|
||||
if len(permissions.DeniedParameters) == 0 {
|
||||
goto ALLOWED_PARAMETERS
|
||||
}
|
||||
|
||||
// Check if all parameters have been denied
|
||||
if _, ok := permissions.DeniedParameters["*"]; ok {
|
||||
return false, sudo
|
||||
}
|
||||
|
||||
for parameter, value := range req.Data {
|
||||
// Check if parameter has been explictly denied
|
||||
if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok {
|
||||
// If the value exists in denied values slice, deny
|
||||
if valueInParameterList(value, valueSlice) {
|
||||
return false, sudo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALLOWED_PARAMETERS:
|
||||
// If we don't have any allowed parameters set, allow
|
||||
if len(permissions.AllowedParameters) == 0 {
|
||||
return true, sudo
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, sudo
|
||||
}
|
||||
|
||||
func valueInParameterList(v interface{}, list []interface{}) bool {
|
||||
// Empty list is equivalent to the item always existing in the list
|
||||
if len(list) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return valueInSlice(v, list)
|
||||
}
|
||||
|
||||
func valueInSlice(v interface{}, list []interface{}) bool {
|
||||
for _, el := range list {
|
||||
if reflect.DeepEqual(el, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -59,12 +59,15 @@ func TestACL_Root(t *testing.T) {
|
|||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
allowed, rootPrivs := acl.AllowOperation(logical.UpdateOperation, "sys/mount/foo")
|
||||
request := new(logical.Request)
|
||||
request.Operation = logical.UpdateOperation
|
||||
request.Path = "sys/mount/foo"
|
||||
allowed, rootPrivs := acl.AllowOperation(request)
|
||||
if !rootPrivs {
|
||||
t.Fatalf("expected root")
|
||||
}
|
||||
if !allowed {
|
||||
t.Fatalf("expected permission")
|
||||
t.Fatalf("expected permissions")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +84,10 @@ func TestACL_Single(t *testing.T) {
|
|||
|
||||
// Type of operation is not important here as we only care about checking
|
||||
// sudo/root
|
||||
_, rootPrivs := acl.AllowOperation(logical.ReadOperation, "sys/mount/foo")
|
||||
request := new(logical.Request)
|
||||
request.Operation = logical.ReadOperation
|
||||
request.Path = "sys/mount/foo"
|
||||
_, rootPrivs := acl.AllowOperation(request)
|
||||
if rootPrivs {
|
||||
t.Fatalf("unexpected root")
|
||||
}
|
||||
|
@ -117,7 +123,10 @@ func TestACL_Single(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
allowed, rootPrivs := acl.AllowOperation(tc.op, tc.path)
|
||||
request := new(logical.Request)
|
||||
request.Operation = tc.op
|
||||
request.Path = tc.path
|
||||
allowed, rootPrivs := acl.AllowOperation(request)
|
||||
if allowed != tc.allowed {
|
||||
t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs)
|
||||
}
|
||||
|
@ -148,7 +157,10 @@ func TestACL_Layered(t *testing.T) {
|
|||
func testLayeredACL(t *testing.T, acl *ACL) {
|
||||
// Type of operation is not important here as we only care about checking
|
||||
// sudo/root
|
||||
_, rootPrivs := acl.AllowOperation(logical.ReadOperation, "sys/mount/foo")
|
||||
request := new(logical.Request)
|
||||
request.Operation = logical.ReadOperation
|
||||
request.Path = "sys/mount/foo"
|
||||
_, rootPrivs := acl.AllowOperation(request)
|
||||
if rootPrivs {
|
||||
t.Fatalf("unexpected root")
|
||||
}
|
||||
|
@ -189,7 +201,10 @@ func testLayeredACL(t *testing.T, acl *ACL) {
|
|||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
allowed, rootPrivs := acl.AllowOperation(tc.op, tc.path)
|
||||
request := new(logical.Request)
|
||||
request.Operation = tc.op
|
||||
request.Path = tc.path
|
||||
allowed, rootPrivs := acl.AllowOperation(request)
|
||||
if allowed != tc.allowed {
|
||||
t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs)
|
||||
}
|
||||
|
@ -199,6 +214,169 @@ func testLayeredACL(t *testing.T, acl *ACL) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestACL_PolicyMerge(t *testing.T) {
|
||||
policy, err := Parse(mergingPolicies)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
acl, err := NewACL([]*Policy{policy})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
type tcase struct {
|
||||
path string
|
||||
allowed map[string][]interface{}
|
||||
denied map[string][]interface{}
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{"foo/bar", nil, map[string][]interface{}{"zip": []interface{}{}, "baz": []interface{}{}}},
|
||||
{"hello/universe", map[string][]interface{}{"foo": []interface{}{}, "bar": []interface{}{}}, nil},
|
||||
{"allow/all", map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil},
|
||||
{"allow/all1", map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil},
|
||||
{"deny/all", nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}},
|
||||
{"deny/all1", nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}},
|
||||
{"value/merge", map[string][]interface{}{"test": []interface{}{1, 2, 3, 4}}, map[string][]interface{}{"test": []interface{}{1, 2, 3, 4}}},
|
||||
{"value/empty", map[string][]interface{}{"empty": []interface{}{}}, map[string][]interface{}{"empty": []interface{}{}}},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
raw, ok := acl.exactRules.Get(tc.path)
|
||||
if !ok {
|
||||
t.Fatalf("Could not find acl entry for path %s", tc.path)
|
||||
}
|
||||
|
||||
p := raw.(*Permissions)
|
||||
if !reflect.DeepEqual(tc.allowed, p.AllowedParameters) {
|
||||
t.Fatalf("Allowed paramaters did not match, Expected: %#v, Got: %#v", tc.allowed, p.AllowedParameters)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
|
||||
t.Fatalf("Denied paramaters did not match, Expected: %#v, Got: %#v", tc.denied, p.DeniedParameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL_AllowOperation(t *testing.T) {
|
||||
policy, err := Parse(permissionsPolicy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
acl, err := NewACL([]*Policy{policy})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
toperations := []logical.Operation{
|
||||
logical.UpdateOperation,
|
||||
logical.CreateOperation,
|
||||
}
|
||||
type tcase struct {
|
||||
path string
|
||||
parameters []string
|
||||
allowed bool
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{"dev/ops", []string{"zip"}, true},
|
||||
{"foo/bar", []string{"zap"}, false},
|
||||
{"foo/baz", []string{"hello"}, true},
|
||||
{"foo/baz", []string{"zap"}, false},
|
||||
{"broken/phone", []string{"steve"}, false},
|
||||
{"hello/world", []string{"one"}, false},
|
||||
{"tree/fort", []string{"one"}, true},
|
||||
{"tree/fort", []string{"foo"}, false},
|
||||
{"fruit/apple", []string{"pear"}, false},
|
||||
{"fruit/apple", []string{"one"}, false},
|
||||
{"cold/weather", []string{"four"}, true},
|
||||
{"var/aws", []string{"cold", "warm", "kitty"}, false},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
request := logical.Request{Path: tc.path, Data: make(map[string]interface{})}
|
||||
for _, parameter := range tc.parameters {
|
||||
request.Data[parameter] = ""
|
||||
}
|
||||
for _, op := range toperations {
|
||||
request.Operation = op
|
||||
allowed, _ := acl.AllowOperation(&request)
|
||||
if allowed != tc.allowed {
|
||||
t.Fatalf("bad: case %#v: %v", tc, allowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL_ValuePermissions(t *testing.T) {
|
||||
policy, err := Parse(valuePermissionsPolicy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
acl, err := NewACL([]*Policy{policy})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
toperations := []logical.Operation{
|
||||
logical.UpdateOperation,
|
||||
logical.CreateOperation,
|
||||
}
|
||||
type tcase struct {
|
||||
path string
|
||||
parameters []string
|
||||
values []interface{}
|
||||
allowed bool
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{"dev/ops", []string{"allow"}, []interface{}{"good"}, true},
|
||||
{"dev/ops", []string{"allow"}, []interface{}{"bad"}, false},
|
||||
{"foo/bar", []string{"deny"}, []interface{}{"bad"}, false},
|
||||
{"foo/bar", []string{"deny"}, []interface{}{"good"}, true},
|
||||
{"foo/bar", []string{"allow"}, []interface{}{"good"}, true},
|
||||
{"foo/baz", []string{"aLLow"}, []interface{}{"good"}, true},
|
||||
{"foo/baz", []string{"deny"}, []interface{}{"bad"}, false},
|
||||
{"foo/baz", []string{"deny"}, []interface{}{"good"}, false},
|
||||
{"foo/baz", []string{"allow", "deny"}, []interface{}{"good", "bad"}, false},
|
||||
{"foo/baz", []string{"deny", "allow"}, []interface{}{"good", "bad"}, false},
|
||||
{"foo/baz", []string{"deNy", "allow"}, []interface{}{"bad", "good"}, false},
|
||||
{"foo/baz", []string{"aLLow"}, []interface{}{"bad"}, false},
|
||||
{"foo/baz", []string{"Neither"}, []interface{}{"bad"}, false},
|
||||
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good"}, true},
|
||||
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good1"}, true},
|
||||
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good2"}, true},
|
||||
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"bad"}, false},
|
||||
{"fizz/buzz", []string{"allow_multi", "allow"}, []interface{}{"good1", "good"}, true},
|
||||
{"fizz/buzz", []string{"deny_multi"}, []interface{}{"bad2"}, false},
|
||||
{"fizz/buzz", []string{"deny_multi", "allow_multi"}, []interface{}{"good", "good2"}, false},
|
||||
// {"test/types", []string{"array"}, []interface{}{[1]string{"good"}}, true},
|
||||
{"test/types", []string{"map"}, []interface{}{map[string]interface{}{"good": "one"}}, true},
|
||||
{"test/types", []string{"map"}, []interface{}{map[string]interface{}{"bad": "one"}}, false},
|
||||
{"test/types", []string{"int"}, []interface{}{1}, true},
|
||||
{"test/types", []string{"int"}, []interface{}{3}, false},
|
||||
{"test/types", []string{"bool"}, []interface{}{false}, true},
|
||||
{"test/types", []string{"bool"}, []interface{}{true}, false},
|
||||
{"test/star", []string{"anything"}, []interface{}{true}, true},
|
||||
{"test/star", []string{"foo"}, []interface{}{true}, true},
|
||||
{"test/star", []string{"bar"}, []interface{}{false}, true},
|
||||
{"test/star", []string{"bar"}, []interface{}{true}, false},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
request := logical.Request{Path: tc.path, Data: make(map[string]interface{})}
|
||||
for i, parameter := range tc.parameters {
|
||||
request.Data[parameter] = tc.values[i]
|
||||
}
|
||||
for _, op := range toperations {
|
||||
request.Operation = op
|
||||
allowed, _ := acl.AllowOperation(&request)
|
||||
if allowed != tc.allowed {
|
||||
t.Fatalf("bad: case %#v: %v", tc, allowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tokenCreationPolicy = `
|
||||
name = "tokenCreation"
|
||||
path "auth/token/create*" {
|
||||
|
@ -255,3 +433,254 @@ path "foo/bar" {
|
|||
capabilities = ["deny"]
|
||||
}
|
||||
`
|
||||
|
||||
//test merging
|
||||
var mergingPolicies = `
|
||||
name = "ops"
|
||||
path "foo/bar" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"baz" = []
|
||||
}
|
||||
}
|
||||
path "foo/bar" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"zip" = []
|
||||
}
|
||||
}
|
||||
path "hello/universe" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"foo" = []
|
||||
}
|
||||
}
|
||||
path "hello/universe" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"bar" = []
|
||||
}
|
||||
}
|
||||
path "allow/all" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"test" = []
|
||||
"test1" = ["foo"]
|
||||
}
|
||||
}
|
||||
path "allow/all" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "allow/all1" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "allow/all1" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"test" = []
|
||||
"test1" = ["foo"]
|
||||
}
|
||||
}
|
||||
path "deny/all" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"test" = []
|
||||
}
|
||||
}
|
||||
path "deny/all" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "deny/all1" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "deny/all1" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"test" = []
|
||||
}
|
||||
}
|
||||
path "value/merge" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"test" = [1, 2]
|
||||
}
|
||||
denied_parameters = {
|
||||
"test" = [1, 2]
|
||||
}
|
||||
}
|
||||
path "value/merge" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"test" = [3, 4]
|
||||
}
|
||||
denied_parameters = {
|
||||
"test" = [3, 4]
|
||||
}
|
||||
}
|
||||
path "value/empty" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"empty" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"empty" = [1]
|
||||
}
|
||||
}
|
||||
path "value/empty" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"empty" = [1]
|
||||
}
|
||||
denied_parameters = {
|
||||
"empty" = []
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
//allow operation testing
|
||||
var permissionsPolicy = `
|
||||
name = "dev"
|
||||
path "dev/*" {
|
||||
policy = "write"
|
||||
|
||||
allowed_parameters = {
|
||||
"zip" = []
|
||||
}
|
||||
}
|
||||
path "foo/bar" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"zap" = []
|
||||
}
|
||||
}
|
||||
path "foo/baz" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"hello" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"zap" = []
|
||||
}
|
||||
}
|
||||
path "broken/phone" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"steve" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"steve" = []
|
||||
}
|
||||
}
|
||||
path "hello/world" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "tree/fort" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"foo" = []
|
||||
}
|
||||
}
|
||||
path "fruit/apple" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"pear" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
}
|
||||
path "cold/weather" {
|
||||
policy = "write"
|
||||
allowed_parameters = {}
|
||||
denied_parameters = {}
|
||||
}
|
||||
path "var/aws" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"soft" = []
|
||||
"warm" = []
|
||||
"kitty" = []
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
//allow operation testing
|
||||
var valuePermissionsPolicy = `
|
||||
name = "op"
|
||||
path "dev/*" {
|
||||
policy = "write"
|
||||
|
||||
allowed_parameters = {
|
||||
"allow" = ["good"]
|
||||
}
|
||||
}
|
||||
path "foo/bar" {
|
||||
policy = "write"
|
||||
denied_parameters = {
|
||||
"deny" = ["bad"]
|
||||
}
|
||||
}
|
||||
path "foo/baz" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"ALLOW" = ["good"]
|
||||
}
|
||||
denied_parameters = {
|
||||
"dEny" = ["bad"]
|
||||
}
|
||||
}
|
||||
path "fizz/buzz" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"allow_multi" = ["good", "good1", "good2"]
|
||||
"allow" = ["good"]
|
||||
}
|
||||
denied_parameters = {
|
||||
"deny_multi" = ["bad", "bad1", "bad2"]
|
||||
}
|
||||
}
|
||||
path "test/types" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"map" = [{"good" = "one"}]
|
||||
"int" = [1, 2]
|
||||
"bool" = [false]
|
||||
}
|
||||
denied_parameters = {
|
||||
}
|
||||
}
|
||||
path "test/star" {
|
||||
policy = "write"
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
"foo" = []
|
||||
"bar" = [false]
|
||||
}
|
||||
denied_parameters = {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -628,7 +628,7 @@ func (c *Core) checkToken(req *logical.Request) (*logical.Auth, *TokenEntry, err
|
|||
|
||||
// Check the standard non-root ACLs. Return the token entry if it's not
|
||||
// allowed so we can decrement the use count.
|
||||
allowed, rootPrivs := acl.AllowOperation(req.Operation, req.Path)
|
||||
allowed, rootPrivs := acl.AllowOperation(req)
|
||||
if !allowed {
|
||||
return nil, te, logical.ErrPermissionDenied
|
||||
}
|
||||
|
@ -1037,7 +1037,7 @@ func (c *Core) sealInitCommon(req *logical.Request) (retErr error) {
|
|||
}
|
||||
|
||||
// Verify that this operation is allowed
|
||||
allowed, rootPrivs := acl.AllowOperation(req.Operation, req.Path)
|
||||
allowed, rootPrivs := acl.AllowOperation(req)
|
||||
if !allowed {
|
||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
||||
return retErr
|
||||
|
@ -1122,7 +1122,7 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
|||
}
|
||||
|
||||
// Verify that this operation is allowed
|
||||
allowed, rootPrivs := acl.AllowOperation(req.Operation, req.Path)
|
||||
allowed, rootPrivs := acl.AllowOperation(req)
|
||||
if !allowed {
|
||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
||||
return retErr
|
||||
|
|
|
@ -46,7 +46,10 @@ func (d dynamicSystemView) SudoPrivilege(path string, token string) bool {
|
|||
// The operation type isn't important here as this is run from a path the
|
||||
// user has already been given access to; we only care about whether they
|
||||
// have sudo
|
||||
_, rootPrivs := acl.AllowOperation(logical.ReadOperation, path)
|
||||
req := new(logical.Request)
|
||||
req.Operation = logical.ReadOperation
|
||||
req.Path = path
|
||||
_, rootPrivs := acl.AllowOperation(req)
|
||||
return rootPrivs
|
||||
}
|
||||
|
||||
|
|
|
@ -58,11 +58,22 @@ type Policy struct {
|
|||
|
||||
// PathCapabilities represents a policy for a path in the namespace.
|
||||
type PathCapabilities struct {
|
||||
Prefix string
|
||||
Policy string
|
||||
Capabilities []string
|
||||
CapabilitiesBitmap uint32 `hcl:"-"`
|
||||
Glob bool
|
||||
Prefix string
|
||||
Policy string
|
||||
Permissions *Permissions
|
||||
Glob bool
|
||||
Capabilities []string
|
||||
|
||||
// These two keys are used at the top level to make the HCL nicer; we store
|
||||
// in the Permissions object though
|
||||
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
|
||||
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
CapabilitiesBitmap uint32
|
||||
AllowedParameters map[string][]interface{}
|
||||
DeniedParameters map[string][]interface{}
|
||||
}
|
||||
|
||||
// Parse is used to parse the specified ACL rules into an
|
||||
|
@ -113,16 +124,21 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
|
|||
if len(item.Keys) > 0 {
|
||||
key = item.Keys[0].Token.Value().(string)
|
||||
}
|
||||
|
||||
valid := []string{
|
||||
"policy",
|
||||
"capabilities",
|
||||
"allowed_parameters",
|
||||
"denied_parameters",
|
||||
}
|
||||
if err := checkHCLKeys(item.Val, valid); err != nil {
|
||||
return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
|
||||
}
|
||||
|
||||
var pc PathCapabilities
|
||||
|
||||
// allocate memory so that DecodeObject can initialize the Permissions struct
|
||||
pc.Permissions = new(Permissions)
|
||||
|
||||
pc.Prefix = key
|
||||
if err := hcl.DecodeObject(&pc, item.Val); err != nil {
|
||||
return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
|
||||
|
@ -156,23 +172,36 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
|
|||
}
|
||||
|
||||
// Initialize the map
|
||||
pc.CapabilitiesBitmap = 0
|
||||
pc.Permissions.CapabilitiesBitmap = 0
|
||||
for _, cap := range pc.Capabilities {
|
||||
switch cap {
|
||||
// If it's deny, don't include any other capability
|
||||
case DenyCapability:
|
||||
pc.Capabilities = []string{DenyCapability}
|
||||
pc.CapabilitiesBitmap = DenyCapabilityInt
|
||||
pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
|
||||
goto PathFinished
|
||||
case CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability:
|
||||
pc.CapabilitiesBitmap |= cap2Int[cap]
|
||||
pc.Permissions.CapabilitiesBitmap |= cap2Int[cap]
|
||||
default:
|
||||
return fmt.Errorf("path %q: invalid capability '%s'", key, cap)
|
||||
}
|
||||
}
|
||||
|
||||
PathFinished:
|
||||
if pc.AllowedParametersHCL != nil {
|
||||
pc.Permissions.AllowedParameters = make(map[string][]interface{}, len(pc.AllowedParametersHCL))
|
||||
for key, val := range pc.AllowedParametersHCL {
|
||||
pc.Permissions.AllowedParameters[strings.ToLower(key)] = val
|
||||
}
|
||||
}
|
||||
if pc.DeniedParametersHCL != nil {
|
||||
pc.Permissions.DeniedParameters = make(map[string][]interface{}, len(pc.DeniedParametersHCL))
|
||||
|
||||
for key, val := range pc.DeniedParametersHCL {
|
||||
pc.Permissions.DeniedParameters[strings.ToLower(key)] = val
|
||||
}
|
||||
}
|
||||
|
||||
PathFinished:
|
||||
paths = append(paths, &pc)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,48 @@ path "/foo/bar" {
|
|||
path "foo/bar" {
|
||||
capabilities = ["create", "sudo"]
|
||||
}
|
||||
|
||||
# Check that only allowed_parameters are being added to foobar
|
||||
path "foo/bar" {
|
||||
capabilities = ["create", "sudo"]
|
||||
allowed_parameters = {
|
||||
"zip" = []
|
||||
"zap" = []
|
||||
}
|
||||
}
|
||||
|
||||
# Check that only denied_parameters are being added to bazbar
|
||||
path "baz/bar" {
|
||||
capabilities = ["create", "sudo"]
|
||||
denied_parameters = {
|
||||
"zip" = []
|
||||
"zap" = []
|
||||
}
|
||||
}
|
||||
|
||||
# Check that both allowed and denied parameters are being added to bizbar
|
||||
path "biz/bar" {
|
||||
capabilities = ["create", "sudo"]
|
||||
allowed_parameters = {
|
||||
"zim" = []
|
||||
"zam" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"zip" = []
|
||||
"zap" = []
|
||||
}
|
||||
}
|
||||
path "test/types" {
|
||||
capabilities = ["create", "sudo"]
|
||||
allowed_parameters = {
|
||||
"map" = [{"good" = "one"}]
|
||||
"int" = [1, 2]
|
||||
}
|
||||
denied_parameters = {
|
||||
"string" = ["test"]
|
||||
"bool" = [false]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
func TestPolicy_Parse(t *testing.T) {
|
||||
|
@ -49,35 +91,121 @@ func TestPolicy_Parse(t *testing.T) {
|
|||
}
|
||||
|
||||
expect := []*PathCapabilities{
|
||||
&PathCapabilities{"", "deny",
|
||||
[]string{
|
||||
&PathCapabilities{
|
||||
Prefix: "",
|
||||
Policy: "deny",
|
||||
Capabilities: []string{
|
||||
"deny",
|
||||
}, DenyCapabilityInt, true},
|
||||
&PathCapabilities{"stage/", "sudo",
|
||||
[]string{
|
||||
},
|
||||
Permissions: &Permissions{CapabilitiesBitmap: DenyCapabilityInt},
|
||||
Glob: true,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "stage/",
|
||||
Policy: "sudo",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list",
|
||||
"sudo",
|
||||
}, CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt |
|
||||
DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt, true},
|
||||
&PathCapabilities{"prod/version", "read",
|
||||
[]string{
|
||||
},
|
||||
Permissions: &Permissions{
|
||||
CapabilitiesBitmap: (CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt | DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt),
|
||||
},
|
||||
Glob: true,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "prod/version",
|
||||
Policy: "read",
|
||||
Capabilities: []string{
|
||||
"read",
|
||||
"list",
|
||||
}, ReadCapabilityInt | ListCapabilityInt, false},
|
||||
&PathCapabilities{"foo/bar", "read",
|
||||
[]string{
|
||||
},
|
||||
Permissions: &Permissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "foo/bar",
|
||||
Policy: "read",
|
||||
Capabilities: []string{
|
||||
"read",
|
||||
"list",
|
||||
}, ReadCapabilityInt | ListCapabilityInt, false},
|
||||
&PathCapabilities{"foo/bar", "",
|
||||
[]string{
|
||||
},
|
||||
Permissions: &Permissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "foo/bar",
|
||||
Policy: "",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"sudo",
|
||||
}, CreateCapabilityInt | SudoCapabilityInt, false},
|
||||
},
|
||||
Permissions: &Permissions{CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt)},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "foo/bar",
|
||||
Policy: "",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"sudo",
|
||||
},
|
||||
AllowedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
|
||||
Permissions: &Permissions{
|
||||
CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
|
||||
AllowedParameters: map[string][]interface{}{"zip": {}, "zap": {}},
|
||||
},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "baz/bar",
|
||||
Policy: "",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"sudo",
|
||||
},
|
||||
DeniedParametersHCL: map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
|
||||
Permissions: &Permissions{
|
||||
CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
|
||||
DeniedParameters: map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
|
||||
},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "biz/bar",
|
||||
Policy: "",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"sudo",
|
||||
},
|
||||
AllowedParametersHCL: map[string][]interface{}{"zim": {}, "zam": {}},
|
||||
DeniedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
|
||||
Permissions: &Permissions{
|
||||
CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
|
||||
AllowedParameters: map[string][]interface{}{"zim": {}, "zam": {}},
|
||||
DeniedParameters: map[string][]interface{}{"zip": {}, "zap": {}},
|
||||
},
|
||||
Glob: false,
|
||||
},
|
||||
&PathCapabilities{
|
||||
Prefix: "test/types",
|
||||
Policy: "",
|
||||
Capabilities: []string{
|
||||
"create",
|
||||
"sudo",
|
||||
},
|
||||
AllowedParametersHCL: map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
|
||||
DeniedParametersHCL: map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
|
||||
Permissions: &Permissions{
|
||||
CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
|
||||
AllowedParameters: map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
|
||||
DeniedParameters: map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
|
||||
},
|
||||
Glob: false,
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(p.Paths, expect) {
|
||||
t.Errorf("expected \n\n%#v\n\n to be \n\n%#v\n\n", p.Paths, expect)
|
||||
|
|
|
@ -34,6 +34,18 @@ path "secret/foo" {
|
|||
path "secret/super-secret" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
|
||||
path "secret/bar" {
|
||||
capabilities = ["create"]
|
||||
permissions = {
|
||||
allowed_parameters = {
|
||||
"*" = []
|
||||
}
|
||||
denied_parameters = {
|
||||
"foo" = ["bar"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Policies use path based matching to apply rules. A policy may be an exact
|
||||
|
@ -99,6 +111,31 @@ capabilities. These mappings are as follows:
|
|||
|
||||
* `read` - `["read", "list"]`
|
||||
|
||||
## Permissions
|
||||
|
||||
Paths offer an optional `permissions` field for fine-grain control over the
|
||||
parameters and values a path is able to write. The capabilities associated with
|
||||
this path take precedence over permissions on parameters.
|
||||
|
||||
* `allowed_parameters` - Acts as a whitelist. Setting to "*" will allow a
|
||||
parameter by any name to be changed. Otherwise setting a key with an `[]`
|
||||
value will allow changes to parameters with that name. Setting a key with a
|
||||
populated value array will allow that parameter to only be set to one of the
|
||||
values in the array. If keys exist in the `allowed_parameters` object all
|
||||
keys not specified will be denied.
|
||||
* `denied_parameters` - Acts as a blacklist, and takes precedence over
|
||||
`allowed_parameters`. Setting to "*" will deny any attempt to change any
|
||||
parameter. Otherwise setting a key with an `[]` value will deny any changes
|
||||
to parameters with that name. Setting a key with a populated value array
|
||||
will deny any attempt to set a parameter with that name and value. If keys
|
||||
exist in the `denied_parameters` object all keys not specified will be
|
||||
allowed.
|
||||
|
||||
If both `allowed_parameters` and `denied_parameters` contain keys and the keys
|
||||
in `denied_parameters` contain values: All specified `allowed_parameters` will
|
||||
be allowed, and specified `denied_parameters` will be allowed to be anything not
|
||||
included in their array of denied values.
|
||||
|
||||
## Root Policy
|
||||
|
||||
The "root" policy is a special policy that can not be modified or removed.
|
||||
|
|
Loading…
Reference in a new issue