hashicorp / vault

A tool for secrets management, encryption as a service, and privileged access management
https://www.vaultproject.io/
Other
31.17k stars 4.21k forks source link

Proposal: JWT Claim-based OIDC Auth Backend #2525

Closed mwitkow closed 6 years ago

mwitkow commented 7 years ago

Kubernetes supports authentication (and group extraction forth authorization) using OICD (OpenID Connect) JWT id_tokens tokens, see here for docs. Basically JWT tokens are crypto-verifiable JSON key-value pairs called "claims".

For Kubernetes Auth, two such claims are used:

Both KeyCloak and Dex are configurable OpenID Connect servers that can delegate to upstream identity providers (e.g. Azure or Google).

This proposal is about introducing an Auth Backend that is a configurable, generic OICD backend that uses JWT token validation.

Contrary to what's been discussed previously in #465, OIDC doesn't require browser flows to be used, and such is not an obstacle for Vault adoption. They can be used in exactly the same fashion as GitHub personal tokens, by copy-pasting.

 vault auth -method=oidc token=<id_token>

In fact this is exactly what K8S's kubectl is expected to be used, with --token flag.

A couple of other considerations:

The K8S oicd plugin seems fairly straightforward and could act as a basis for this work. We'd actually be willing to send in PRs for this if Vault maintainers would accept them :)

panga commented 7 years ago

@mwitkow Thank for your valuable contribution, it fits exactly my requirements.

I really liked the OIDC approach (OpenID Connect) rather than an OAuth2 flow, the later is far more complex to configure.

I'm using Keycloak SSO as OIDC provider and the following Vault config:

vault auth-enable oidc
vault write auth/oidc/config \
    issuer_url="http://localhost:8080/auth/realms/master" \
    client_ids="testclient" \
    username_claim="preferred_username" \
    groups_claim="groups"
vault policy-write secret-policy secret-policy.hcl
vault write auth/oidc/groups/users policies=secret-policy
vault auth -method=oidc token=<bearer_access_token>

For the folks that want test this change, I've pushed a Vault 0.7.2 Docker image with merge of PR #2796 to Dockerhub as panga/vault:0.7.2-oidc (https://hub.docker.com/r/panga/vault/)

EDIT: I'm using a fork (https://github.com/panga/vault/tree/oidc) with OIDC backend provided by @mwitkow for now.

mwitkow commented 7 years ago

@jefferai I understand that the reasons for closing #2796 where as you cited in #2571 "In fact, the proliferation of backends that have come up in the last couple of months around this are exactly why we want to take the approach we took with the combined database backend."

However, I wanted to follow up here on what you said in #2796 :

Someone that wants to put in some minor effort to make it work the way we need (ref #2525) should get in contact.

If you're looking for an external contributor to implement a generic, pluggable OAuth2 backend, may I suggest closing this centi-thread and filing a new bug marked with "Help Needed" a clear description of what exactly needs implementing, in what incremental stages and what is the minimum feature set for acceptance? Or maybe phrase it in a separate design doc?

I've been following this discussion since the very beginning, and it is still unclear exactly what you guys are looking for. I think spending some time to clarify this, and adding pointers to "piggy-backable" code for plugins in Vault, would go a very long way if you're looking for an external contributor to build this generic pluggable backend.

For anyone else interested, @pidah @zdoherty @panga, we'll be tryibng out #2796 in testing, and if that proves successful we'll probably by maintaining a public fork of Vault with #2796 in it until the generic OAuth2 backend becomes a substitute.

jefferai commented 7 years ago

@mwitkow Nobody has indicated to this point that what we are looking for is unclear. But I'll try to clarify:

There are a lot of relatively incompatible ways to get OIDC information. Sometimes you already have an OIDC token. Sometimes you have credentials that can be used to get an OIDC token; or in other cases, such as Ping, which won't get an OIDC token for password owner workflows but can fetch the equivalent UserInfo after the fact. Sometimes it requires a full OAuth flow first, at which point either an OIDC token can be fetched or similar user info can be gleaned.

It's also not clear from the examples I've seen that even given an OIDC token you can always rely on the same claims to specify group membership.

There are also potentially different ways to authenticate tokens, including ingressing associated public keys.

If you look at this from a higher level perspective, what it really looks like is that you need the following:

  1. User/Group policy mappings
  2. A way to provide login information, which may be an OIDC token itself, and may be some other set of credentials
  3. A way to validate the result, e.g. signature verification
  4. A way to transfer the result -- e.g. UserInfo data -- to the user/group mappings to generate the final token

With the database backends early on people would just copy the code from one backend to the next and make the changes necessary to talk to e.g. mysql instead of postgresql. Bugs were copied too, and feature disparity grew over time. The way we solved it was a common backend that handled all non-specific functions and plugins conforming to an interface to handle the specific bits. The amount of copypasta has drastically decreased; it's easier to maintain, easier to keep feature parity, and easier not to have bugs proliferate.

In the above list, really only items 2 and 3 are likely to differ significantly between source of the user information. If we adopt a plugin system similar to the database backend, we can have all of the code for items 1 and 4 be the same. For a normal OIDC case, the plugin would be configured with a public key (or the URL to fetch it from); it would accept the JWT, validate the JWT, parse it, and return user info -- things it needs to do anyways, just behind a plugin interface. For Ping, the plugin could take in the user credentials, perform whatever calls it needs, and return the user info. We already merged an Okta backend, but Okta also has a way to fetch user info, and it'd be great if Okta could be supported here too.

There is a little extra work required to use a plugin interface, but common plugin functionality is already done (for the database backend).

I think the confusion with OAuth is that I was saying that such a mechanism could support OAuth eventually if other bits were worked on in Vault's core. But that's neither planned nor requested and I don't in any way see it as being necessary for this PR, or for #2796 or #2571.

However, I would like to see a plugin abstraction, even if the only plugin initially is a pure OIDC plugin. I think it has real value going forward and will be far easier to maintain by the Vault team.

bkrodgers commented 7 years ago

If we adopt a plugin system similar to the database backend, we can have all of the code for items 1 and 4 be the same.

I think 4 would differ -- some plugins will need to call the userinfo endpoint, others can get it from an ID token, and still others might call some other arbitrary service to get that info. Or is that part of step 2 or 3? In other words, are you only referring to the step of a plugin saying "here's the user's info, wherever I got it from" and looking up the policies in the map from step 1?

One other question on the plugin implementation. Will plugins have a good way to be able to use Vault to store configuration info? In our original implementation as we submitted it for direct inclusion, we stored things in the mounted backend's config map, like the Ping URLs for both tokens and userinfo, a client ID, client secret, and the name of the attribute in userinfo to use for the groups. Will plugins have a way to store config data for each mounted instance of an auth plugin? Or if they have to deal with it themselves, will they at least be passed the mount info so they can key off that to support being mounted more than once?

I think the confusion with OAuth is that I was saying that such a mechanism could support OAuth eventually if other bits were worked on in Vault's core. But that's neither planned nor requested and I don't in any way see it as being necessary for this PR, or for #2796 or #2571.

Now I'm slightly confused...OAuth (resource owner flow) is definitely requested, but we're fine implementing it ourselves as a plugin once that's supported. We might even open source it. Other parts of your post make it sound like the plugin system will support us writing such a plugin though, so I'm not quite sure if this comment is anything I should be concerned with.

jefferai commented 7 years ago

I think 4 would differ -- some plugins will need to call the userinfo endpoint, others can get it from an ID token, and still others might call some other arbitrary service to get that info. Or is that part of step 2 or 3? In other words, are you only referring to the step of a plugin saying "here's the user's info, wherever I got it from" and looking up the policies in the map from step 1?

Yes. My assertion is that the input to a plugin (into step 2) should be the client input (credentials, which may just simply be an OIDC token) and the output (after step 3) should be a defined data type that indicates what configured users/groups the authenticating user should inherit policies for. For OIDC or anything that can return an OIDC UserInfo-compatible struct this would likely be a fairly direct mapping.

One other question on the plugin implementation. Will plugins have a good way to be able to use Vault to store configuration info?

Yes. Normal storage semantics, just piped over a TLS-encrypted connection. (However, to be perfectly transparent, for built-in plugins they would actually be launched directly as objects in-memory. This is what the database backend does. But that's an implementation detail.)

Now I'm slightly confused...OAuth (resource owner flow) is definitely requested, but we're fine implementing it ourselves as a plugin once that's supported.

Sorry, what I should have said (rather than sowing more confusion) was other OAuth mechanisms could be supported eventually, such as the three-legged server side flow, where the client is given a URL to paste in their browser, after which they are redirected to Vault. That would require other plumbing through Vault's core and http layers. But pure OIDC and what you need for Ping would not require any of that and I don't see any reason they would have to be deferred. That's just implementation against a slightly different interface than exists now.

Hobbit71 commented 7 years ago

OK. That is a really long thread and my understanding OIDC has been increased as a result. I am looking at this for the same reason as a few others (I want to use KeyCloak as my auth provider - which defaults to OIDC). While I doubt I can help with the coding on this, I am more than happy to test and/or provide documentation once we get round to a solution. BTW - I like the ide of taking the same approach as you have with databases. Happy to contribute in any way possible.

jefferai commented 7 years ago

@Hobbit71 with backend plugins coming out in 0.8, that might be a better way to go than the plugin system specific to OIDC/OAuth I mentioned above.

Hobbit71 commented 7 years ago

That is good to hear. Out of interest, how far away is 0.8. Not looking for anything firm here, is it weeks, months or quarters away?

Like I say, when we have that, happy to be involved. While I am not a Go coder, I now know some guys here (where I work) who are and we would love to contribute back to a great tool.

peon-pasado-zeitnot commented 6 years ago

Are there any updates on this? I would love to be able to use OIDC as auth backend in vault.

jefferai commented 6 years ago

There are several plugins (GCP, Kubernetes) using OIDC for auth.

kamalmarhubi commented 6 years ago

@jefferai

There are several plugins (GCP, Kubernetes) using OIDC for auth.

Does this mean it's possible to use something like Dex? Or are you suggesting there are plugins to adapt from if someone wanted to implement generic OIDC.

jefferai commented 6 years ago

@kamalmarhubi I would love to see "generic OIDC" but it may be relatively impossible given that most of the implementations we've seen rely heavily on custom metadata. But, those plugins are good starting points for someone wanting to make a plugin for their own needs.

ptone commented 6 years ago

The specs discussed in this thread are at times a bit obtuse, but It is sad to see this still unresolved nearly a year in. The plugin system seems like it could support this entirely outside the vault "batteries included" set.

@jefferai I do not understand the statement of how a generic OIDC plugin is "relatively impossible" if it is constrained to be a well formed JWT token. When you say "custom metadata" is this metadata that is put onto the vault token for some later use, or are you saying something about custom claims in the JWT.

A point of confusion throughout this thread is that OAuth2 and OIDC are different in their intent. OAuth2 is about authorization grants, while OIDC is only about authentication. OIDC is a much more natural fit for Vault auth. If Vault secrets are considered resources, than the most natural fit of OAuth2 for vault would be as a resource server, and that gets all entangled in client-ids and flows and really puts Vault in the middle of an OAuth2 system, not as a peripheral participant (which starts to feel very awkward to have things like login flows in the CLI or backend).

But the original goal of supporting a simple flow - where you have, at the Vault client, an OIDC token (Vault shouldn't care how you got it), and want to login to vault with that. And the Vault plugin supports roles that bind certain signers/issuer and jwt claims to vault policy. This seems very straightforward and worth supporting and seems the direction of #2796 (thank you @mwitkow for the persistence).

Most of the imperative to optimally combine OIDC with any/all OAuth2 support from early in this thread seems to be dissolved by the flexibility of the plug-in system replacing builtin backends.

Is a generic OIDC plugin still of interest or should this just be hosted outside of Vault project now as just a vault plugin?

jefferai commented 6 years ago

Hi Preston,

I don't think it's so much a point of confusion around OAuth2/OIDC as the fact that there are some systems (I forget which) that won't grant OIDC tokens initially but will allow fetching such a token once you already have an OAuth2 token and people would like those to be supported too.

Anyways, OIDC support is totally straightforward in theory, but there are of course complicating factors. One is that many of the tokens people might want to use are JWTs but don't include many of the optional claims you'd really want for security, such as issue time or expiration time (I know this was an issue with the GCP and/or Kubernetes plugins). Could we just ignore it and let the user beware? Sure, but then that's added flags to control such bypass.

Another is that in most of the OIDCs we've seen so far in the plugins that have been written put important information in custom claims. So you'd need a way to define which claims are important for matching against roles, and what types those are -- a string, a list, etc.

Sometimes you can issue a call to a server to perform further token validation or get important parameters needed for matching (like the Kubernetes plugin does), but then you have to define how the call is made, what the inputs and outputs are, and what to do with them.

So it's not really a lack of desire to have a generic OIDC auth mechanism, it's that when it comes down to it, we haven't seen, in the specific examples that have been brought to our attention from real-world systems, a complete workflow that is sufficient from purely the OIDC token itself. Instead for GCP and Kubernetes it's been more like OIDC++ in ways that end up being specific to any given system.

If there is sufficient demand for a pure, token-only OIDC mechanism that requires no external validation and has standard claims, I'm totally happy to have it. But I'd want to actually have an understanding of the real-world systems for which it's sufficient, because if it ends up being the case that we put an OIDC backend in and it's unusable for the majority of real-world use-cases then we end up either in a position where we need to keep frankencoding the backend to try to handle more and more special cases, or we're better off creating simple plugins specific to these systems.

peon-pasado-zeitnot commented 6 years ago

@jefferai you mentioned lack of possible use cases.

My is simple I would like to be able to use Vault with either Dex or KeyCloak (preferable KeyCloak).

Workflow would be like this:

  1. From cli users executes 'vault login -method=AWESOME-OIDC-METHODE'
  2. Some url is displayed
  3. Users opens the url and login
  4. a) if browser is the same host as vault app will redirect him 127.0.0.1 with appropriate tokens to confirm he was logged in successfully b) if not user will copy pin and enter it to cli

Beside KeyCloak/Dex support GitLab.com support would be nice.

jefferai commented 6 years ago

What are the claims used in each of those cases, what types are they, and how would they map to Vault identities and policies?

ror6ax commented 6 years ago

I think it would map to http://www.keycloak.org/docs/3.3/authorization_services/topics/policy/role-policy.html

Keycloak's client_id would be mapped to identity("role" I guess) in Vault. Adding policies to this client would enable policies in Vault.

jefferai commented 6 years ago

Just had a talk with @chrishoffman about this today, as he's writing an auth backend that uses OIDC -- and of course, nothing useful is in non-proprietary extensions, and even worse, there's very little useful at all that doesn't then require taking a single identifying value and making further API calls.

However, we were talking about it in the context of Identity, which is increasingly how we're moving towards people assigning policy, via Identity groups, and realized that a dead simple approach could work well as a "generic" OIDC plugin. (If you don't know much about Vault's Identity system, take a look at https://www.vaultproject.io/docs/secrets/identity/index.html).

The configuration would consist only of:

Identity's API designed to be scripted, so you can easily sync some set of servers or users into Identity and have them auth and be identified as that user every time they auth. So long as you have a known unique identifier that can be validated, you can just do all policy management via Identity, without having to deal with complex mappings of groups/roles/policies/claims (many of which would require further API calls) in the plugin. If you can't get group info out of the JWT, just sync group info into Identity from your source of truth and assign uniquely-identified users to them. Anything much more complicated is probably better served by a specific plugin anyways.

Interested in feedback, and if someone wanted to write such a plugin, I'd be interested in having it in an official repo and pulling it upstream.

ror6ax commented 6 years ago

Hi @jefferai , myself and members of my team would be interested in this very much. I got questions, tho.

  1. It sound like Chris Hoffmann is working on OIDC auth backend that you intend to live along the generic OIDC plugin that is the subject of this issue. Is that correct? If so, I don't get what is the difference between the two.

  2. Do you want public key endpoint to be specific to this functionality? I was surprised to find there's no "generic" endpoint for giving Vault my public key for verification of whatever payload later on.

  3. Would you like to use all 3 - registered claim names, public claim names, and private claim names? Would you like any verification on the contains of JWT at all, or it's basically as long as the hash is unique and we can map it to recorded Identity, we're good?

  4. What OIDC flow are you thinking about? Implicit would be the easiest to implement, in my opinion.

ror6ax commented 6 years ago

As I'm re-reading your comment, it occurs to me you just want to map unique claim hash as an identifier of the user accessing Vault's endpoint.

Interactions then would be as follows.

  1. Upload public key to Vault.
  2. Register a indentity, point to key it has to use.
  3. Set identity on some endpoint( say /v1/aws/creds/ by means of policy. Set identity entity-alias for that identity to match claim, given https://tools.ietf.org/html/rfc7519#section-4.1 claims as parameters.
  4. Set /v1/aws/creds/ auth to oidc.
  5. Now, accessing /v1/aws/creds/ will require Authorization: Bearer header packets, encoded with appropriate public key.
  6. After such request is recieved the validation of JWT has to take place.

Please let me know what you think.

jefferai commented 6 years ago

@ror6ax I'm a bit confused by all the AWS stuff in your example, especially step 4. I think it looks more like this though:

  1. Upload validating key to mount config
  2. Ensure identities are created that match some unique claim in each JWT and whose identifier is configured in the mount
  3. Users log in by passing in their JWT to Vault via POST/PUT
  4. The mount validates the signature on the JWT, pulls out the configured claim value, and sends it back to Vault, which matches it to an entity and generates a Vault token
codyaray commented 6 years ago

What's the current state of the world here? I read through the whole thread but its still not clear. Is this being actively worked on?

2796 looks like exactly what we need as well. I saw there's a year-old fork, but was wondering if this (or something like it) was ever implemented as a plugin?

jefferai commented 6 years ago

I don't know of anyone actively working on the proposed solution.

mvdkleijn commented 6 years ago

This thread / issue saddens me a little. All I and I believe several others here want is to use Keycloak as a Vault auth method. Apparently that's not achievable for the foreseeable future.

jefferai commented 6 years ago

@mvdkleijn I believe it's totally achievable, just requires someone to do it.

avoidik commented 6 years ago

it's very strange for me, it's highly demanded feature to have for wide society, but in the same time I see that every small initiative got "killed", in the same time we've got Azure auth

https://github.com/hashicorp/vault/pull/2796 https://github.com/hashicorp/vault/pull/2571 https://github.com/hashicorp/vault/issues/644 https://github.com/hashicorp/vault/issues/1986 https://github.com/hashicorp/vault/pull/3005

jefferai commented 6 years ago

Nothing has been "killed", whatever that means. But nobody has written a generic enough provider as outlined at https://github.com/hashicorp/vault/issues/2525#issuecomment-369718031

The fact that Azure uses JWTs is besides the point. The majority of the logic in that plugin has nothing to do with JWT verification but rather with checks and binds against Azure specific APIs.

In fact it's an excellent example of why a generic OIDC auth plugin has been hard to figure out. Most things you'd actually use to perform identification are behind custom claims or custom APIs that must be called and parsed. My guess is that that's why nobody has implemented a generic plugin as outlined in that comment.

jefferai commented 6 years ago

Someone pointed me towards https://github.com/immutability-io/jwt-auth. Looks like it fits the mold quite closely to how I said a plugin would need to be designed for the generic case, so we may reach out to the author about pulling it in.

cypherhat commented 6 years ago

Hi all,

The above plugin https://github.com/immutability-io/jwt-auth is my handiwork. I am more than amenable to upstreaming it; but, I recently added a feature that may complicate matters.

The feature is to facilitate (among other things) delegated authentication using a rather opinionated mechanism. Please take a look at this mechanism (authentication with a JWT via a trustee).

I can refactor the plugin to remove this feature, but, I have a few real world use cases that will use it so I will have to figure out how to accommodate the functionality in a less tightly coupled? fashion.

onorua commented 6 years ago

meanwhile you can use: https://github.com/AnchorFree/vault-plugin-oidc (we use it in production for some time already, and it works).

cypherhat commented 6 years ago

We use jwt-auth in production too - for what it's worth. We modeled it after the GitHub plugin for mapping a claims (configurable) to policies.

jwt-auth also supports OAuth password grants and refreshes - so that the token can be used via renewal more effectively.

The trustee stuff can be used when a CI/CD pipeline wants to act on behalf of a user. IP constraints can be used alternatively.

jefferai commented 6 years ago

@onorua Any interest in upstreaming?

jefferai commented 6 years ago

@mwitkow I believe https://github.com/AnchorFree/vault-plugin-oidc is actually basically your code, just modernized. Are you still making use of it?

onorua commented 6 years ago

@jefferai sure, we can work on upstreaming it. I saw that we have quite a dead PR, with a dead ticket for more than a year, and decided to make it work for us. I would love to cooperate with @mwitkow or do it on our own, I just want this problem to be solved :)

jefferai commented 6 years ago

Initial implementation of an official plugin is at https://github.com/hashicorp/vault-plugin-auth-jwt/pull/1

It handles both OIDC discovery and offline JWT validation workflows.

This is completely untested (by which I mean, literally, I made it compile successfully and pushed it up), but anyone that would like to comment on features and/or provide testing before I write some tests for it, feel free!

I hope to get this officially in for 0.11.

jefferai commented 6 years ago

The plugin now has full tests for both OIDC discovery endpoint based and offline verification based workflows. It has been merged into the repo and is now included in vault master. As such, closing this.

rocktavious commented 6 years ago

@jefferai I just wanted to report on this that the 0.10.4 OIDC flow works for me (i'm using Auth0)

It was very hard to follow the documentation here https://www.vaultproject.io/docs/auth/jwt.html The examples given didn't map well to any of the Auth0 things and i ended up having to read throughly https://www.vaultproject.io/api/auth/jwt/index.html to know what was being mapped to what.

I think it could benefit from better examples for specific services like Auth0.

That being said i was disappointed to see that there was no Vault UI login flow for this - do you know when the improvements to the Vault UI will land for allowing you login with a given JWT token?

Also it would be nice if there was a mapping that could be setup - so that instead of saying this JWT "role" gets X policy - it could parse the "groups_claim" and assign a set of policies - very similar to how the LDAP login flow works.

Thanks and would love to hear your thoughts.

jefferai commented 6 years ago

All my OIDC testing was done with Auth0 so it should have just worked with something similar to the OIDC discovery URL that is in those docs. Feel free to PR examples into the docs.

I thought JWT made it into the UI. @meirish can you comment?

You can do group policy mapping with Identity (https://www.vaultproject.io/docs/secrets/identity/index.html). It's a bit confusing right now, we need to shore up the documentation, but basically, set up an external identity group with an alias mapping to the group from auth0. You'll have to do this (once) for each of the auth0 groups but after that membership will automatically happen as people log in, so you can configure policies there. Overall having a central place to do this rather than an implementation of user/group mapping in each plugin makes every plugin significantly less complicated to write and maintain, and makes the centrally managed ones more useful for other features since we can check user/group membership within Vault's core.

jefferai commented 6 years ago

Oh, there's a PR of a guide: https://github.com/hashicorp/vault/pull/4968 -- still being reviewed, but might be useful.

avoidik commented 6 years ago

I was able to authenticate against Keycloak OIDC provider, but having issues with configuring groups_claim parameter for role

https://github.com/hashicorp/vault-plugin-auth-jwt/issues/9

if I omit it (despite its required mark in documentation), then I could get Vault token

jefferai commented 6 years ago

@avoidik Please don't ping on two tickets. One is enough.

Leletir commented 5 years ago

@avoidik How did you manage to use Keycloak OIDC provider ?

I'm trying to use Keycloak as an OIDC provider, but I'm getting lost between documentation, could you explain how did you manage to do it ?

Thank you very much.