Add ability to require parameters in ACLs (#3510)

This commit is contained in:
Chris Hoffman 2017-11-02 07:18:49 -04:00 committed by GitHub
parent e0669746b6
commit 3d8d887676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 16 deletions

View File

@ -169,6 +169,18 @@ func NewACL(policies []*Policy) (*ACL, error) {
}
}
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)
}
}
}
}
INSERT:
tree.Insert(pc.Prefix, existingPerms)
}
@ -322,6 +334,12 @@ CHECK:
// Only check parameter permissions for operations that can modify
// parameters.
if op == logical.UpdateOperation || op == logical.CreateOperation {
for _, parameter := range permissions.RequiredParameters {
if _, ok := req.Data[strings.ToLower(parameter)]; !ok {
return
}
}
// If there are no data fields, allow
if len(req.Data) == 0 {
ret.Allowed = true

View File

@ -232,6 +232,7 @@ func TestACL_PolicyMerge(t *testing.T) {
maxWrappingTTL *time.Duration
allowed map[string][]interface{}
denied map[string][]interface{}
required []string
}
createDuration := func(seconds int) *time.Duration {
@ -240,14 +241,14 @@ func TestACL_PolicyMerge(t *testing.T) {
}
tcases := []tcase{
{"foo/bar", nil, nil, nil, map[string][]interface{}{"zip": []interface{}{}, "baz": []interface{}{}}},
{"hello/universe", createDuration(50), createDuration(200), map[string][]interface{}{"foo": []interface{}{}, "bar": []interface{}{}}, nil},
{"allow/all", nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil},
{"allow/all1", nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil},
{"deny/all", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}},
{"deny/all1", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}},
{"value/merge", nil, nil, map[string][]interface{}{"test": []interface{}{3, 4, 1, 2}}, map[string][]interface{}{"test": []interface{}{3, 4, 1, 2}}},
{"value/empty", nil, nil, map[string][]interface{}{"empty": []interface{}{}}, map[string][]interface{}{"empty": []interface{}{}}},
{"foo/bar", nil, nil, nil, map[string][]interface{}{"zip": []interface{}{}, "baz": []interface{}{}}, []string{"baz"}},
{"hello/universe", createDuration(50), createDuration(200), map[string][]interface{}{"foo": []interface{}{}, "bar": []interface{}{}}, nil, []string{"foo", "bar"}},
{"allow/all", nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil, nil},
{"allow/all1", nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}, "test1": []interface{}{"foo"}}, nil, nil},
{"deny/all", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}, nil},
{"deny/all1", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}, nil},
{"value/merge", nil, nil, map[string][]interface{}{"test": []interface{}{3, 4, 1, 2}}, map[string][]interface{}{"test": []interface{}{3, 4, 1, 2}}, nil},
{"value/empty", nil, nil, map[string][]interface{}{"empty": []interface{}{}}, map[string][]interface{}{"empty": []interface{}{}}, nil},
}
for _, tc := range tcases {
@ -263,6 +264,9 @@ func TestACL_PolicyMerge(t *testing.T) {
if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
t.Fatalf("Denied paramaters did not match, Expected: %#v, Got: %#v", tc.denied, p.DeniedParameters)
}
if !reflect.DeepEqual(tc.required, p.RequiredParameters) {
t.Fatalf("Required paramaters did not match, Expected: %#v, Got: %#v", tc.required, p.RequiredParameters)
}
if tc.minWrappingTTL != nil && *tc.minWrappingTTL != p.MinWrappingTTL {
t.Fatalf("Min wrapping TTL did not match, Expected: %#v, Got: %#v", tc.minWrappingTTL, p.MinWrappingTTL)
}
@ -319,6 +323,8 @@ func TestACL_AllowOperation(t *testing.T) {
{"fruit/apple", nil, []string{"one"}, false},
{"cold/weather", nil, []string{"four"}, true},
{"var/aws", nil, []string{"cold", "warm", "kitty"}, false},
{"var/req", nil, []string{"cold", "warm", "kitty"}, false},
{"var/req", nil, []string{"cold", "warm", "kitty", "foo"}, true},
}
for _, tc := range tcases {
@ -510,6 +516,7 @@ path "foo/bar" {
denied_parameters = {
"baz" = []
}
required_parameters = ["baz"]
}
path "foo/bar" {
policy = "write"
@ -522,6 +529,7 @@ path "hello/universe" {
allowed_parameters = {
"foo" = []
}
required_parameters = ["foo"]
max_wrapping_ttl = 300
min_wrapping_ttl = 100
}
@ -530,6 +538,7 @@ path "hello/universe" {
allowed_parameters = {
"bar" = []
}
required_parameters = ["bar"]
max_wrapping_ttl = 200
min_wrapping_ttl = 50
}
@ -705,6 +714,10 @@ path "var/aws" {
"kitty" = []
}
}
path "var/req" {
policy = "write"
required_parameters = ["foo"]
}
`
//allow operation testing

View File

@ -96,10 +96,11 @@ type PathRules struct {
// These keys are used at the top level to make the HCL nicer; we store in
// the ACLPermissions object though
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
RequiredParametersHCL []string `hcl:"required_parameters"`
}
type ACLPermissions struct {
@ -108,6 +109,7 @@ type ACLPermissions struct {
MaxWrappingTTL time.Duration
AllowedParameters map[string][]interface{}
DeniedParameters map[string][]interface{}
RequiredParameters []string
}
func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
@ -115,6 +117,7 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
CapabilitiesBitmap: p.CapabilitiesBitmap,
MinWrappingTTL: p.MinWrappingTTL,
MaxWrappingTTL: p.MaxWrappingTTL,
RequiredParameters: p.RequiredParameters[:],
}
switch {
@ -198,6 +201,7 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
"capabilities",
"allowed_parameters",
"denied_parameters",
"required_parameters",
"min_wrapping_ttl",
"max_wrapping_ttl",
}
@ -290,6 +294,9 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
pc.Permissions.MaxWrappingTTL < pc.Permissions.MinWrappingTTL {
return errors.New("max_wrapping_ttl cannot be less than min_wrapping_ttl")
}
if len(pc.RequiredParametersHCL) > 0 {
pc.Permissions.RequiredParameters = pc.RequiredParametersHCL[:]
}
PathFinished:
paths = append(paths, &pc)

View File

@ -85,6 +85,10 @@ path "test/types" {
"bool" = [false]
}
}
path "test/req" {
capabilities = ["create", "sudo"]
required_parameters = ["foo"]
}
`)
func TestPolicy_Parse(t *testing.T) {
@ -225,6 +229,20 @@ func TestPolicy_Parse(t *testing.T) {
},
Glob: false,
},
&PathRules{
Prefix: "test/req",
Policy: "",
Capabilities: []string{
"create",
"sudo",
},
RequiredParametersHCL: []string{"foo"},
Permissions: &ACLPermissions{
CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
RequiredParameters: []string{"foo"},
},
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)

View File

@ -113,9 +113,9 @@ path "secret/super-secret" {
capabilities = ["deny"]
}
# Policies can also specify allowed and disallowed parameters. Here the key
# "secret/restricted" can only contain "foo" (any value) and "bar" (one of "zip"
# or "zap").
# Policies can also specify allowed, disallowed, and required parameters. Here
# the key "secret/restricted" can only contain "foo" (any value) and "bar" (one
# of "zip" or "zap").
path "secret/restricted" {
capabilities = ["create"]
allowed_parameters = {
@ -217,13 +217,24 @@ In addition to the standard set of capabilities, Vault offers finer-grained
control over permissions at a given path. The capabilities associated with a
path take precedence over permissions on parameters.
### Allowed and Denied Parameters
### Parameter Constraints
In Vault, data is represented as `key=value` pairs. Vault policies can
optionally further restrict paths based on the keys and data at those keys when
evaluating the permissions for a path. The optional finer-grained control
options are:
* `required_parameters` - A list of parameters that must be specified.
```ruby
# This requires the user to create "secret/foo" with a parameter named
# "bar" and "baz".
path "secret/foo" {
capabilities = ["create"]
required_parameters = ["bar", "baz"]
}
```
* `allowed_parameters` - Whitelists a list of keys and values that are
permitted on the given path.