Add ability to require parameters in ACLs (#3510)
This commit is contained in:
parent
e0669746b6
commit
3d8d887676
18
vault/acl.go
18
vault/acl.go
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue