Support identity engine metadata from okta auth
Is your feature request related to a problem? Please describe.
While strictly not a problem, to achieve our objective it isn't exactly DRY. In order to allow teams to transport secrets amongst each other via (for instance) kv2 paths, we have to write a lot of duplicate policy which differs only in path which is mapped to okta groups. Ideally, we'd be able to use templated policy. Looking to the templated example using the mount accessor for the kubernetes auth engine, I thought that this might be useful for the okta auth engine as well to export your group membership, thus making policy templated, therefore much more DRY.
Describe the solution you'd like
I'd like to see okta auth users have their group membership extended to their metadata entity entries. Given we can already extend policy via okta auth already, it would be great to do something like
path "secret/data/{{identity.entity.alias.auth_okta_xxxxxxxx.metadata.groups.<GROUP_NAME>}}/+" {
capabilities = ["create", "delete", "list", "read", "update"]
}
This sort of automatic association allows okta auth'd members of the same group to store static material in vault. https://developer.okta.com/docs/reference/api/users/#get-user-s-groups describes getting an okta user's group membership. The follow jq filter pulls the id and group name: '.[] | (.id + " " + .profile.name)'
Describe alternatives you've considered
We could map each individual user to a committed group entry in the identity engine, but that's just duplicating what we get from okta, and that's a massive waste of time, even if we schedule a repeated process to do this it'll be mean to the vault API. There's also changing our human login path, but keeping humans mapped to a central auth engine like okta keeps us sane.
Explain any additional use-cases
Beyond templated policy, I cannot immediately see any other benefits, however fewer policies and standard templated practice will reduce policy count, and likely engine evaluation time by Vault in the long run if you have lots of policy. Also reduces writes of policy, and funky automation workarounds to cover this lack of templating.
Additional context
N/A
Thanks for this thought-provoking report, @mengesb. I'll take it to our engineering teams to chew on - I'll report back when I can. :)
Hi @mengesb,
I wonder if you could spell out what you have in mind in a little bit more detail, with example inputs and outputs? I'm struggling to understand how <GROUP_NAME> gets in there, and if it's hard-coded, what this feature would give you. If you're always templating based on a fixed group name, which every user of the policy must be a member of, how's this any more powerful than the templating options we already provide? Are you maybe assuming that each user is a member of exactly one group?
Hi @mengesb,
I wonder if you could spell out what you have in mind in a little bit more detail, with example inputs and outputs? I'm struggling to understand how
<GROUP_NAME>gets in there, and if it's hard-coded, what this feature would give you. If you're always templating based on a fixed group name, which every user of the policy must be a member of, how's this any more powerful than the templating options we already provide? Are you maybe assuming that each user is a member of exactly one group?
No, in fact I'd assume that a user would be a member of many groups, and that each group would be a templatable path. Currently without changing my auth method, or duplicating my Okta tree into the identity engine groups, there's no way to natively support this workflow.
This is the corresponding identity engine entry for an okta entity:
{
"request_id": "28b4ea36-cb56-e67b-06c5-046916c4b137",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"canonical_id": "e026c944-efde-a93a-d7ac-d2ae61cacc43",
"creation_time": "2020-08-27T16:23:14.272683191Z",
"id": "11c82d52-57ff-a316-03c8-4e100955854f",
"last_update_time": "2020-08-27T16:23:14.272683191Z",
"merged_from_canonical_ids": null,
"metadata": null,
"mount_accessor": "auth_okta_xxxxxxxx",
"mount_path": "auth/okta/",
"mount_type": "okta",
"name": "[USERNAME]@[DOMAIN]",
"namespace_id": "root"
},
"warnings": null
}
As you can see, there's no metadata for the templating engine to use or operate on. in relation to a directory match or other organization unit structure, it'd be very beneficial if we could template secret paths using okta auth, which is based on groups.
Currently these are the templating options, and as mentioned above replicating groups into the identity.groups. path doesn't make sense. If paths matching a okta group were possible that'd simplify things, such as identity.entity.alias.auth_okta_xxxxxxxx.metadata.Some_Okta_Group being one of a list
{
"request_id": "28b4ea36-cb56-e67b-06c5-046916c4b137",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"canonical_id": "e026c944-efde-a93a-d7ac-d2ae61cacc43",
"creation_time": "2020-08-27T16:23:14.272683191Z",
"id": "11c82d52-57ff-a316-03c8-4e100955854f",
"last_update_time": "2020-08-27T16:23:14.272683191Z",
"merged_from_canonical_ids": null,
"metadata": {
"Some_Okta_Group": "Some_Okta_Group",
"Another_Okta_Group": "Another_Okta_Group",
"Random_Okta_Group": "Random_Okta_Group"
},
"mount_accessor": "auth_okta_xxxxxxxx",
"mount_path": "auth/okta/",
"mount_type": "okta",
"name": "[USERNAME]@[DOMAIN]",
"namespace_id": "root"
},
"warnings": null
}
The assumption is that the path identity.entity.alias.auth_okta_xxxxxxxx.metadata.Some_Okta_Group results in the value, since what I want is the group's name as part of the path, the K/V is the same in the above response, however if the entries made can be parsed from a list, that'd make the data object prettier and easier.
The above assumes there's no flexibility in parsing a list in metadata with the templating engine... it would make logical sense to have a single key user_group with a list of that user's groups ("user_group": ["Some_Okta_Group", ...]) but I'm not sure how that might work with the templating engine or if that matters. There are of course OKTA_GROUP and APP_GROUP types however the API call I cited provides the membership of the user to any type of group, including BUILT_IN type, which is still "objectClass": ["okta:user_group"] (type and objectClass differs at times)
Does this make sense @ncabatoff ?
Conversely, automatic population of OKTA group IDs and names into the identity.groups. segment can potentially be an option there, however that would likely produce a lot of dead objects faster than the metadata of an existing entity entry, or add complexity in needing to know the OKTA IDs of things... as the first three options of identity.groups. requires knowledge of the ID - this is not common knowledge unless you have OKTA API access (wouldn't expect a common user to have this -- barely expect them to know their OKTA group membership name as it is). The last one presents the same problem, needing to base it off some key... perhaps identity.groups.names.<group name>.metadata.name, that'd be one funky json response though; though so is my construction of one.
There are two independent parts to what you're trying to do. The first is having Vault know about Okta group memberships. I don't see it as likely that we'd support a second way of doing this, when we have identity external groups. I'm not sure what you mean about "produce a lot of dead objects", can you elaborate on this concern?
The bigger question I have is, assuming we had the group memberships available for interpolation within a template, by whatever mechanism, how would that help you? When we've had requests like this in the past, they wanted something like path "x/y/z/{{ some template that would yield a group name }}", but it sounds like you're okay with hard-coding the group name in the template and just having the policy not apply to those not in the group?
There are two independent parts to what you're trying to do. The first is having Vault know about Okta group memberships.
Definitely don't want to have Vault configured to know about groups or group names, however would like to make use of them in templating.
we have identity external groups.
Could you elaborate on external groups?
I'm not sure what you mean about "produce a lot of dead objects", can you elaborate on this concern?
Groups change in Okta, and I've noticed identity engine entries populating our Vault in relation to our OKTA users, some with a different (expired/old) okta path with a different mount accessor. I've cleaned those up, however they were persisting until I did so. The other object I've noticed residing is one where the username is just the first part of their email address (username) and the other is their full email ([email protected]) under the same mount accessor. I didn't want to add many dead entries in the identity.group. path given that those groups will come/go and their membership will or can rapidly change ... unsure when identity. paths get updated.
The bigger question I have is, assuming we had the group memberships available for interpolation within a template, by whatever mechanism, how would that help you? When we've had requests like this in the past, they wanted something like
path "x/y/z/{{ some template that would yield a group name }}", but it sounds like you're okay with hard-coding the group name in the template and just having the policy not apply to those not in the group?
No, not trying to hard code that group name, and would like to yield a group name for pathing as you describe. Given a user is in many groups, they'd have templated many paths with an interpolation/template versus having to render policy for every group, and corresponding matching auth policy binding them to their group membership path.
Could you elaborate on external groups?
Identity has both internal groups (entirely managed by vault admin) and external groups (sourced from auth methods, group membership for user X updated whenever X logs in).
No, not trying to hard code that group name, and would like to yield a group name for pathing as you describe. Given a user is in many groups, they'd have templated many paths with an interpolation/template versus having to render policy for every group, and corresponding matching auth policy binding them to their group membership path.
Ok, then I'm back to not understanding how you expect this to work. Let's say we have user X who belongs to groups A,B, and we have secrets foo/A, foo/B, foo/C, where only members of the appropriate groups should be allowed to read secrets, so members of A can read foo/A, etc.
Given policy P1:
path "foo/{{ templateExpr }}" {}
when user X logs in, what should templateExpr yield, A or B, and why? I'm guessing you would answer "both", and here's where I would reply: that's not how Go templates work. A template expression has to produce a single value. In principle we could change that, but it'd be challenging.
An easier option would be to allow templates to be used more generally in policies, like
{{ range identity.entity.group_memberships }}
path "foo/{{ . }}" {
capabilities = ["create", "read"]
{{ end }}
I'm not saying we'll do that, just that it seems a lot more feasible.
what should templateExpr yield, A or B, and why? I'm guessing you would answer "both", and here's where I would reply: that's not how Go templates work.
Ideally, it would produce multiple paths which you elude to later on. Ideally I wanted a matching expression however users authenticating with our OKTA mount have multiple group membership, which may be allowed via different auth policies, making this explicit matching complex - certainly a bad idea.
A template expression has to produce a single value
This is exactly what I feared, and imagined this was the problem implementing something to this effect.
{{ range identity.entity.group_memberships }}
path "foo/{{ . }}" {
capabilities = ["create", "read"]
{{ end }}
Something to the above effect would likely work out better; this would be more in line with how I expected things to turn out.
In OKTA auth policy, you allow explicit groups and assign policy --- is there a way to utilize that explicitly allowed group for a singleton reference?
Ex. vault write auth/okta/groups/Some_Okta_Group policies=Some_Policy (though we use terraform) --- is there anyway to continue that group into the policy, or am I left with only doing this via Terraform potentially? I can retain the group name (comes from tfvars in our IaC) and write an explicit policy and additionally assign it at-time, though admittedly I'd rather not have that complex templating occuring in Terraform, where there might be a high potential of unintended collisions... this just came to me but might not work out well...
Identity has both internal groups (entirely managed by vault admin) and external groups (sourced from auth methods, group membership for user X updated whenever X logs in).
So Okta auth doesn't update the identity engine with groups it knows - i.e. external groups; this is the core of the ask. The question is - how to best support templating such a method when lists aren't exactly a thing per the above. As mentioned above, it could be that there's a PR amending the identity.groups tree, or the identity.entity tree... but ideally I would like to settle on a templatable way to allow members of groups allowed from OKTA auth to share some secret engine(s)
To use Vault's entity external groups with okta auth, you have to manually/explicitly add the group in 2 places:
- Under the Okta auth method
- As an entity external group with a group alias that points to the okta auth method + the group name.
So far I only use a small set of common groups because configuring groups would be rather annoying otherwise.
Maybe the Okta auth backend could gain an ability to implicitly create those entity external groups under some situations:
- whenever a group is added to the okta auth method
- when the group name matches some configurable pattern/regex (which could be
.*if you want to auto create all the groups) Then, the group names would end up inidentity.groups.
That doesn't solve the templating aspect of this issue, but maybe it would improve the situation with using identity.groups.