vault icon indicating copy to clipboard operation
vault copied to clipboard

Feature Request: complex policies

Open adongy opened this issue 4 years ago • 1 comments

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!

adongy avatar Apr 21 '20 12:04 adongy

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

hrobertson avatar Nov 21 '22 12:11 hrobertson