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:
|
INSERT:
|
||||||
tree.Insert(pc.Prefix, existingPerms)
|
tree.Insert(pc.Prefix, existingPerms)
|
||||||
}
|
}
|
||||||
|
@ -322,6 +334,12 @@ CHECK:
|
||||||
// Only check parameter permissions for operations that can modify
|
// Only check parameter permissions for operations that can modify
|
||||||
// parameters.
|
// parameters.
|
||||||
if op == logical.UpdateOperation || op == logical.CreateOperation {
|
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 there are no data fields, allow
|
||||||
if len(req.Data) == 0 {
|
if len(req.Data) == 0 {
|
||||||
ret.Allowed = true
|
ret.Allowed = true
|
||||||
|
|
|
@ -232,6 +232,7 @@ func TestACL_PolicyMerge(t *testing.T) {
|
||||||
maxWrappingTTL *time.Duration
|
maxWrappingTTL *time.Duration
|
||||||
allowed map[string][]interface{}
|
allowed map[string][]interface{}
|
||||||
denied map[string][]interface{}
|
denied map[string][]interface{}
|
||||||
|
required []string
|
||||||
}
|
}
|
||||||
|
|
||||||
createDuration := func(seconds int) *time.Duration {
|
createDuration := func(seconds int) *time.Duration {
|
||||||
|
@ -240,14 +241,14 @@ func TestACL_PolicyMerge(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tcases := []tcase{
|
tcases := []tcase{
|
||||||
{"foo/bar", nil, nil, nil, map[string][]interface{}{"zip": []interface{}{}, "baz": []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},
|
{"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},
|
{"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},
|
{"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{}{}}},
|
{"deny/all", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}, nil},
|
||||||
{"deny/all1", nil, nil, nil, map[string][]interface{}{"*": []interface{}{}, "test": []interface{}{}}},
|
{"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}}},
|
{"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{}{}}},
|
{"value/empty", nil, nil, map[string][]interface{}{"empty": []interface{}{}}, map[string][]interface{}{"empty": []interface{}{}}, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
|
@ -263,6 +264,9 @@ func TestACL_PolicyMerge(t *testing.T) {
|
||||||
if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
|
if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
|
||||||
t.Fatalf("Denied paramaters did not match, Expected: %#v, Got: %#v", 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 {
|
if tc.minWrappingTTL != nil && *tc.minWrappingTTL != p.MinWrappingTTL {
|
||||||
t.Fatalf("Min wrapping TTL did not match, Expected: %#v, Got: %#v", 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},
|
{"fruit/apple", nil, []string{"one"}, false},
|
||||||
{"cold/weather", nil, []string{"four"}, true},
|
{"cold/weather", nil, []string{"four"}, true},
|
||||||
{"var/aws", nil, []string{"cold", "warm", "kitty"}, false},
|
{"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 {
|
for _, tc := range tcases {
|
||||||
|
@ -510,6 +516,7 @@ path "foo/bar" {
|
||||||
denied_parameters = {
|
denied_parameters = {
|
||||||
"baz" = []
|
"baz" = []
|
||||||
}
|
}
|
||||||
|
required_parameters = ["baz"]
|
||||||
}
|
}
|
||||||
path "foo/bar" {
|
path "foo/bar" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
|
@ -522,6 +529,7 @@ path "hello/universe" {
|
||||||
allowed_parameters = {
|
allowed_parameters = {
|
||||||
"foo" = []
|
"foo" = []
|
||||||
}
|
}
|
||||||
|
required_parameters = ["foo"]
|
||||||
max_wrapping_ttl = 300
|
max_wrapping_ttl = 300
|
||||||
min_wrapping_ttl = 100
|
min_wrapping_ttl = 100
|
||||||
}
|
}
|
||||||
|
@ -530,6 +538,7 @@ path "hello/universe" {
|
||||||
allowed_parameters = {
|
allowed_parameters = {
|
||||||
"bar" = []
|
"bar" = []
|
||||||
}
|
}
|
||||||
|
required_parameters = ["bar"]
|
||||||
max_wrapping_ttl = 200
|
max_wrapping_ttl = 200
|
||||||
min_wrapping_ttl = 50
|
min_wrapping_ttl = 50
|
||||||
}
|
}
|
||||||
|
@ -705,6 +714,10 @@ path "var/aws" {
|
||||||
"kitty" = []
|
"kitty" = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path "var/req" {
|
||||||
|
policy = "write"
|
||||||
|
required_parameters = ["foo"]
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
//allow operation testing
|
//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
|
// These keys are used at the top level to make the HCL nicer; we store in
|
||||||
// the ACLPermissions object though
|
// the ACLPermissions object though
|
||||||
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
|
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
|
||||||
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
|
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
|
||||||
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
|
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
|
||||||
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
|
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
|
||||||
|
RequiredParametersHCL []string `hcl:"required_parameters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ACLPermissions struct {
|
type ACLPermissions struct {
|
||||||
|
@ -108,6 +109,7 @@ type ACLPermissions struct {
|
||||||
MaxWrappingTTL time.Duration
|
MaxWrappingTTL time.Duration
|
||||||
AllowedParameters map[string][]interface{}
|
AllowedParameters map[string][]interface{}
|
||||||
DeniedParameters map[string][]interface{}
|
DeniedParameters map[string][]interface{}
|
||||||
|
RequiredParameters []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
|
func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
|
||||||
|
@ -115,6 +117,7 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
|
||||||
CapabilitiesBitmap: p.CapabilitiesBitmap,
|
CapabilitiesBitmap: p.CapabilitiesBitmap,
|
||||||
MinWrappingTTL: p.MinWrappingTTL,
|
MinWrappingTTL: p.MinWrappingTTL,
|
||||||
MaxWrappingTTL: p.MaxWrappingTTL,
|
MaxWrappingTTL: p.MaxWrappingTTL,
|
||||||
|
RequiredParameters: p.RequiredParameters[:],
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -198,6 +201,7 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
|
||||||
"capabilities",
|
"capabilities",
|
||||||
"allowed_parameters",
|
"allowed_parameters",
|
||||||
"denied_parameters",
|
"denied_parameters",
|
||||||
|
"required_parameters",
|
||||||
"min_wrapping_ttl",
|
"min_wrapping_ttl",
|
||||||
"max_wrapping_ttl",
|
"max_wrapping_ttl",
|
||||||
}
|
}
|
||||||
|
@ -290,6 +294,9 @@ func parsePaths(result *Policy, list *ast.ObjectList) error {
|
||||||
pc.Permissions.MaxWrappingTTL < pc.Permissions.MinWrappingTTL {
|
pc.Permissions.MaxWrappingTTL < pc.Permissions.MinWrappingTTL {
|
||||||
return errors.New("max_wrapping_ttl cannot be less than min_wrapping_ttl")
|
return errors.New("max_wrapping_ttl cannot be less than min_wrapping_ttl")
|
||||||
}
|
}
|
||||||
|
if len(pc.RequiredParametersHCL) > 0 {
|
||||||
|
pc.Permissions.RequiredParameters = pc.RequiredParametersHCL[:]
|
||||||
|
}
|
||||||
|
|
||||||
PathFinished:
|
PathFinished:
|
||||||
paths = append(paths, &pc)
|
paths = append(paths, &pc)
|
||||||
|
|
|
@ -85,6 +85,10 @@ path "test/types" {
|
||||||
"bool" = [false]
|
"bool" = [false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path "test/req" {
|
||||||
|
capabilities = ["create", "sudo"]
|
||||||
|
required_parameters = ["foo"]
|
||||||
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
func TestPolicy_Parse(t *testing.T) {
|
func TestPolicy_Parse(t *testing.T) {
|
||||||
|
@ -225,6 +229,20 @@ func TestPolicy_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
Glob: false,
|
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) {
|
if !reflect.DeepEqual(p.Paths, expect) {
|
||||||
t.Errorf("expected \n\n%#v\n\n to be \n\n%#v\n\n", 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"]
|
capabilities = ["deny"]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Policies can also specify allowed and disallowed parameters. Here the key
|
# Policies can also specify allowed, disallowed, and required parameters. Here
|
||||||
# "secret/restricted" can only contain "foo" (any value) and "bar" (one of "zip"
|
# the key "secret/restricted" can only contain "foo" (any value) and "bar" (one
|
||||||
# or "zap").
|
# of "zip" or "zap").
|
||||||
path "secret/restricted" {
|
path "secret/restricted" {
|
||||||
capabilities = ["create"]
|
capabilities = ["create"]
|
||||||
allowed_parameters = {
|
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
|
control over permissions at a given path. The capabilities associated with a
|
||||||
path take precedence over permissions on parameters.
|
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
|
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
|
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
|
evaluating the permissions for a path. The optional finer-grained control
|
||||||
options are:
|
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
|
* `allowed_parameters` - Whitelists a list of keys and values that are
|
||||||
permitted on the given path.
|
permitted on the given path.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue