vault
vault copied to clipboard
Feature Request: complex policies
Is your feature request related to a problem? Please describe. Currently, policies are declared through HCL with a few available parameters, implemented here https://github.com/hashicorp/vault/blob/4d605638c819e66559ffc4166dec031a4dcc1096/vault/policy.go#L114-L132
Note that allowed_parameters
and denied_parameters
are map[string][]interface{}
, which allows the user to specify a wide array of values. The corresponding documentation is hosted at https://www.vaultproject.io/docs/concepts/policies.html#parameter-constraints but does not detail the data types expected.
The acl checking for these 2 policy parameters is done in vault/acl.go
, and key fragments are:
https://github.com/hashicorp/vault/blob/4d605638c819e66559ffc4166dec031a4dcc1096/vault/acl.go#L475-L501
https://github.com/hashicorp/vault/blob/4d605638c819e66559ffc4166dec031a4dcc1096/vault/acl.go#L701-L732
The user will likely specify strings most of the time, and that case is very well handled: the policy parameter list is a list of strings, so comparison is very easy.
Other data types however will fall to the reflect.DeepEqual
case which makes expressing some constraints very painful.
Here are a few reasons:
- policy checking seems to happen before data types conversion, so for example
framework.TypeCommaStringSlice
will need to handle both the strings and the slice data type - using
reflect.DeepEqual
where order is not relevant will make slice comparison very tedious as you need to list all possible orders - composing policies defaults to extending on key conflict, so it's currently not possible to extend a value inside the list:
# one.hcl
path "..." {
allowed_parameters = [["foo"]]
}
# two.hcl
path "..." {
allowed_parameters = [["bar"]]
}
# composes into
path "..." {
allowed_parameters = [["foo"], ["bar"]]
}
# impossible to end up with
path "..." {
allowed_parameters = [["foo", "bar"]]
}
A concrete use-case where I wanted to use these policies was signing SSH certificates: the allowed_users
is a comma-separated string describing the allowed values the valid_principals
parameter the user provides can take.
The implementation is found here https://github.com/hashicorp/vault/blob/4d605638c819e66559ffc4166dec031a4dcc1096/builtin/logical/ssh/path_sign.go#L259-L264
In this specific case, the allowed_users
is unordered, casted to a set, and each valid_principals
value is tested for membership in the allowed_users
set. I believe it is currently impossible to describe a policy allowing a user to sign for a restricted amount of values in the allowed_users
array.
Given the following configuration:
vault server -dev
vault secrets enable -path=ssh ssh
vault write ssh/config/ca generate_signing_key=true
vault write ssh/roles/role - <<EOF
{
"allow_user_certificates": true,
"allowed_users": "foo,bar,baz",
"key_type": "ca"
}
EOF
I tried the following:
path "ssh/sign/role" {
capabilities = ["read", "list", "create", "update", "delete"]
allowed_parameters = {
"*" = []
"valid_principals" = ["foo", "bar"]
}
}
# works
vault write ssh/sign/my-role public_key=@pubkey valid_principals=foo
# works
vault write ssh/sign/my-role public_key=@pubkey valid_principals=bar
# fails
vault write ssh/sign/my-role public_key=@pubkey valid_principals=foo,bar
A trick that could work would be to make all valid_principals
values to be checked, so you could use
valid_principals = ["*foo*", "*bar*"]
But that does not seem safe at all
I also tried to convert the data type of allowed_users
and valid_principals
from strings into []string
with framework.TypeCommaStringSlice
https://github.com/hashicorp/vault/blob/4d605638c819e66559ffc4166dec031a4dcc1096/builtin/logical/ssh/path_roles.go#L170-L185
This allows me to write
path "..." {
"valid_principals" = [["foo", "bar"]]
}
But I still hit the issue of reflect.DeepEqual
: ["bar", "foo"]
gets denied, ["bar"]
gets denied, "foo,bar"
get denied. I am also unable to compose policies:
# one.hcl
path "..." {
"valid_principals" = [["foo", "bar"]]
}
# two.hcl
path "..." {
"valid_principals" = [["baz"]]
}
# fails
valid_principals = ["foo", "bar", "baz"]
Describe the solution you'd like As the policy checking happens very early in the request flow, I understand it is hard to guess the data types that are expected at a specific path. I did not see any easy path to solve this.
Describe alternatives you've considered Currently, I split the equivalent of policies into multiple roles, so users have to sign multiple roles to get multiple certificates to get access to all they need. This is tedious since most SSH servers will deny authentication after a few fails, so users have to input which certificate is valid for which server.
Explain any additional use-cases
KV v2 is also hit by this, as the actual data is nested in the data
attribute, which makes it hard to describe allowed/denied parameters.
Additional context Related to #4368
Thanks!
This is an old request but I found it while searching for duplicates before creating my own similar request.
My use case is members of a team have a policy like this which lets them manage their own App Roles with a limited set of policies:
path "auth/team1/approle/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
allowed_parameters = {
token_policies = [
"foo",
"bar",
"baz",
]
}
}
The issue is that it only allows single, non-array values of token_policies
:
PUT /v1/auth/team1/approle/role/foo
{
"token_policies": "foo"
}
Result: 204
It does not allow more than one of the values specified as allowed:
PUT /v1/auth/team1/approle/role/foobar
{
"token_policies": [
"foo",
"bar"
]
}
Result: 403
The only work around is to specify all the permutations and require alphabetically sorted values. This example only has three values.. imagine 10 or more!
path "auth/team1/approle/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
allowed_parameters = {
token_policies = [
"foo",
"bar",
"baz",
["foo","bar"],
["foo","baz"],
["foo","bar", "baz"],
]
}
}
Related: https://github.com/hashicorp/vault/issues/7150