dex
dex copied to clipboard
Client Credentials Grant
so client can get access to Dex resources e.g. forwarding auth request and performing token exchange while handling callback from Dex.
@xtremerui I've been evaluating Dex as SP front ending Okta IdP. One of the use cases, I was looking at is support for client_credentials grant type. In a way, I want client application to use static client id and secret, configured at the time of Dex connector configuration, to retrieve id_token, refresh_token, access_token, code from Dex.
This PR is going to support that? The connector type 'am exploring is OIDC.
@karunchennuri yes we have similar use case thus we implemented this in our fork https://github.com/concourse/dex. Not sure how it is gonna be in upstream.
Thanks a lot I will use your fork to test today. Just checking it works on oidc connector type right? Also to test this do you have gRPC client application?
With Regards Karun Chennuri
From: Rui Yang [email protected] Sent: Wednesday, June 10, 2020 8:36:46 AM To: dexidp/dex [email protected] Cc: Chennuri, Karun [email protected]; Mention [email protected] Subject: Re: [dexidp/dex] Client Credentials Grant (#1629)
[External]
@karunchennurihttps://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fkarunchennuri&data=02%7C01%7CKarun.Chennuri1%40T-Mobile.com%7C6f239a7bc83b41a37a7308d80d54162d%7Cbe0f980bdd994b19bd7bbc71a09b026c%7C0%7C0%7C637274002080775697&sdata=HpT4ik9WkAB8Q6q6OKT0tyZ0Ccuu2i8zRn1sXmhUOfU%3D&reserved=0 yes we have similar use case thus we implemented this in our fork https://github.com/concourse/dexhttps://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fconcourse%2Fdex&data=02%7C01%7CKarun.Chennuri1%40T-Mobile.com%7C6f239a7bc83b41a37a7308d80d54162d%7Cbe0f980bdd994b19bd7bbc71a09b026c%7C0%7C0%7C637274002080785690&sdata=Rus8zrNYFO%2F5IJxTiMvBUvPENA0F671et8RgQldYIOk%3D&reserved=0. Not sure how it is gonna be in upstream.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdexidp%2Fdex%2Fpull%2F1629%23issuecomment-642089715&data=02%7C01%7CKarun.Chennuri1%40T-Mobile.com%7C6f239a7bc83b41a37a7308d80d54162d%7Cbe0f980bdd994b19bd7bbc71a09b026c%7C0%7C0%7C637274002080785690&sdata=FslR9xe0EKNt6UDEZCZxt47DafA8gSyeseqIE8vYSec%3D&reserved=0, or unsubscribehttps://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAEKPQROP4L4KGLBTMRRXKSDRV6SA5ANCNFSM4KHZYJOA&data=02%7C01%7CKarun.Chennuri1%40T-Mobile.com%7C6f239a7bc83b41a37a7308d80d54162d%7Cbe0f980bdd994b19bd7bbc71a09b026c%7C0%7C0%7C637274002080795681&sdata=w2qyKV2xd1aIAUbN744MuVDpCABSsaSAdMqpEsqiJmo%3D&reserved=0.
@xtremerui I tested this PR on the master branch of dex. Worked perfectly fine. Somehow I had difficulty running local tests on the https://github.com/concourse/dex. That's the reason just cherry picked your PR. Thanks a lot for putting this together, I was reading some older posts on the reluctance to support client_credentials grant type, there must be a reason, I would be interested in knowing more on that from others on this thread.
@karunchennuri glad it works. The test could be ran using the Makefile
https://github.com/dexidp/dex/blob/2ca992e9b359eff69f9886d10620f255c4cff2f2/Makefile#L44-L45
make test
@xtremerui Would you be open to update this PR since it has conflicts?
@mvdkleijn absolutely. Most of my PRs got conflicts I will update all of them. Thank you!
@sagikazarmark could I get some feedback on this PR please?
@xtremerui I've added some changes to your original PR that fix a few problems. See: https://github.com/kellyma2/dex/commit/a4414c271d5510d76ebff696da93f889ca1e6d7a
Can you add them to this PR?
@xtremerui I've added some changes to your original PR that fix a few problems. See: kellyma2@a4414c2
Can you add them to this PR?
@kellyma2 Sure thing. Thx for the fix!
Though, you will have to sign off your commit first so after I cherry-pick into this PR it won't fail github DCO checks.
Done. https://github.com/kellyma2/dex/commit/19f212fb2846d900d77ce20fa49ea2bc14579afb
Sorry for the delay reviewing this PR.
@xtremerui can you please clarify why this is useful?
From an OIDC perspective the client_credentials
grant makes no sense because you don't receive an id_token
.
From an OAuth2 perspective it might, but who do you want to talk to with the access token issued by Dex?
We use it for non human, ie: robot, interactions. Not supporting it would force folks to use another provider such as Auth0.
We use it for non human, ie: robot, interactions.
Well, that much I've guessed.
Not supporting it would force folks to use another provider such as Auth0.
I don't think that's necessarily true.
As far as I can tell, this is basically a convenience feature for issuing JWT tokens as access tokens (but that itself is a problem IMO, more on that later) and the fact that Dex is issuing these tokens is irrelevant.
I see a couple problems with this approach:
First of all, OpenID doesn't care about client credentials, because there is no identity involved. Dex is primarily an OpenID Connect provider. Although technically that also means it's an OAuth2 authorization server, it simply lacks the characteristics and integration with resource servers.
Moving onto the more pressing issues:
The current implementation accepts a list of scopes and issues an access token with that same list of scopes. Normally, during an authorization grant the user would have to authorize those scopes....or not. Without a user present, the server is supposed to make sure that a client cannot authorize itself for any scope (eg. by assigning a default list of approved scopes to the client or using any other solution the authorization server sees fit). None of that is happening in this case, which means the client can just authorize itself to do anything. Since there is no identity involved, the resource server can't even properly authorize these requests either other than trusting that the issued token can do all those things.
Another serious problem with using these access tokens is the targeted audience. If you take a closer look at the implementation access tokens are basically....ID tokens. The audience for ID tokens is the client whereas the audience for access tokens is the resource server. One could say that this is a Dex implementation flaw, but the thing is OIDC (and Dex) is not traditional OAuth2, there is no target audience, other than the clients. If you decide to accept these tokens somewhere, it basically means you have to turn off audience validation (because the audience will always be the client requesting the token) which means all you have to do is get a valid token from somewhere (eg. an ID token from a public client auth flow) and you will be able to talk to the resource server, no problem.
Finally, there is the problem of the access token verification: the fact that access tokens issued by Dex today are JWTs is coincidental (basically they are JWTs so it would be easier for Dex to validate them when the client calls the /userinfo
endpoint). They could just be opaque tokens in which case the resource server would have to verify the token by talking to Dex....but Dex has no token verification endpoint. This is an internal detail of how Dex issues access tokens.
To sum it up:
- Using
client_credentials
grant in the context of OIDC is abusing the fact that OIDC builds on OAuth - There is no scope authorization whatsoever which opens a window for privilege escalation
- You need to disable audience verification which means a user could easily gain access to an API by simply getting an ID token
- JWT as access token is not mandated by OIDC or OAuth, it's an implementation detail for Dex
For these reasons, I find the current implementation and the idea to use client_credentials
grant with OIDC quite problematic.
The reason why I asked for a use case, because my guess you only need something that issues JWT tokens that you can verify in a backend API for example. I don't think you necessarily need Dex for that, you can simply integrate a basic OAuth2 server into your application.
Am I wrong in any of my assumptions above? If so, please prove me wrong and I'm happy to reconsider my position.
Also pinging @ericchiang because I know he knows this stuff inside out. Can you verify if I'm right/wrong here?
I stand corrected on scopes: unknown scopes are in fact ignored, but that's also coincidental.
Since client_credentials can't be used with 2FA (U2F, WebAuthN, even OTP), I think its value is limited in real world scenarios. Modern orgs are at the point where they're literally hunting down and disabling APIs that only use username/password auth without a second factor.
Dex's Access Tokens were never intended to have meaning outside of Dex itself. They're intended to be opaque to other services.
The concerns about scopes and audience could be resolved by configuration. E.g. you could limit what scopes or audiences a specific client_id could be issued.
If you need a flexible CA to issue credentials automation, you may want to look at HashiCorp Vault. Service-to-service credentials have different lifecycle and issuance requirements than human ones (e.g. you don't want a CI job to break when a user leaves the company). And you generally want to issue automation credentials based on the identity of the machine the automation is running on (e.g. a cloud VM identity[0][1] or kubernetes container identity[2]).
That being said, I don't work on Dex anymore.
The OAuth2 and OpenID Connect specs are routinely stretched in all sorts of interesting ways :) If you understand the draw backs but still feel that this is within Dex's scope, then please feel free to add support.
[0] https://cloud.google.com/compute/docs/instances/verifying-instance-identity [1] https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html [2] https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection
As far as I can tell, this is basically a convenience feature for issuing JWT tokens as access tokens (but that itself is a problem IMO, more on that later) and the fact that Dex is issuing these tokens is irrelevant.
Why does dex even issue access tokens at all, if not to use them?
The current implementation accepts a list of scopes and issues an access token with that same list of scopes. Normally, during an authorization grant the user would have to authorize those scopes....or not. Without a user present, the server is supposed to make sure that a client cannot authorize itself for any scope (eg. by assigning a default list of approved scopes to the client or using any other solution the authorization server sees fit). None of that is happening in this case, which means the client can just authorize itself to do anything. Since there is no identity involved, the resource server can't even properly authorize these requests either other than trusting that the issued token can do all those things.
In practice, scopes are not super important in the client credentials sense. Effectively, the robot in question is demonstrating that it is a valid client with respect to dex by providing a valid client id and client secret.
Another serious problem with using these access tokens is the targeted audience. If you take a closer look at the implementation access tokens are basically....ID tokens. The audience for ID tokens is the client whereas the audience for access tokens is the resource server. One could say that this is a Dex implementation flaw, but the thing is OIDC (and Dex) is not traditional OAuth2, there is no target audience, other than the clients. If you decide to accept these tokens somewhere, it basically means you have to turn off audience validation (because the audience will always be the client requesting the token) which means all you have to do is get a valid token from somewhere (eg. an ID token from a public client auth flow) and you will be able to talk to the resource server, no problem.
I'm aware of the implementation - part of this PR is my work. :) Regarding being able to use any access token, this assumes that the token is still valid (not expired and not revoked). That's an interesting point though - probably want to restrict client credentials to require a client secret....
Regarding audience validation:
https://benohead.com/blog/2018/07/05/oauth-2-0-openid-connect-explained/#:~:text=of%20access%20permissions.-,Audience,only%20valid%20for%20certain%20purposes.
"It is the responsibility is the resource server to determine whether the token should be used or not. A resource server may choose to ignore the audience claim and accept any valid token."
For my purposes, that's a-ok.
Finally, there is the problem of the access token verification: the fact that access tokens issued by Dex today are JWTs is coincidental (basically they are JWTs so it would be easier for Dex to validate them when the client calls the
/userinfo
endpoint). They could just be opaque tokens in which case the resource server would have to verify the token by talking to Dex....but Dex has no token verification endpoint. This is an internal detail of how Dex issues access tokens.
I'm happy to work up a token verification endpoint for dex. :) I can even look at it next week.
To sum it up:
- Using
client_credentials
grant in the context of OIDC is abusing the fact that OIDC builds on OAuth- There is no scope authorization whatsoever which opens a window for privilege escalation
- You need to disable audience verification which means a user could easily gain access to an API by simply getting an ID token
- JWT as access token is not mandated by OIDC or OAuth, it's an implementation detail for Dex
For these reasons, I find the current implementation and the idea to use
client_credentials
grant with OIDC quite problematic.The reason why I asked for a use case, because my guess you only need something that issues JWT tokens that you can verify in a backend API for example. I don't think you necessarily need Dex for that, you can simply integrate a basic OAuth2 server into your application.
Bigger picture is that we're integrating one or more external IdPs using dex on a per-cluster basis and wiring it all up to have Envoy talk to it. It'd be a bit of a pain to do what you're desribing.
If you need a flexible CA to issue credentials automation, you may want to look at HashiCorp Vault. Service-to-service credentials have different lifecycle and issuance requirements than human ones (e.g. you don't want a CI job to break when a user leaves the company). And you generally want to issue automation credentials based on the identity of the machine the automation is running on (e.g. a cloud VM identity[0][1] or kubernetes container identity[2]).
Yes, that would be a sane approach generally speaking. Unfortunately, in our case that'd be a bit messy.. we'd effectively be creating identies based on entire classes of containers. This seems like the appropriate use case for client credentials, no? As in, how is issuing a client id + client secret pair significantly different from issuing a username+password pair?
fyi,I should point out that I'm not the original PR author. I'm just using the PR in my setup
Why does dex even issue access tokens at all, if not to use them?
It should be used for accessing the /userinfo
endpoint as per the OIDC standard.
In practice, scopes are not super important in the client credentials sense. Effectively, the robot in question is demonstrating that it is a valid client with respect to dex by providing a valid client id and client secret.
It might not be important in your use case, but APIs that use scopes for authorization would potentially be in a big trouble. Obviously, documentation can help, but it would be a funny thing to say "Hey, if you use scopes, client_credentials might make your API vulnerable if you issue tokens through Dex". Admittedly, configuration could be used to limit scopes.
"It is the responsibility is the resource server to determine whether the token should be used or not. A resource server may choose to ignore the audience claim and accept any valid token."
Here is the deal though: if you accept any valid token, you accept ID tokens as well (because the way it's implemented in Dex). It's not that difficult to get an ID token for a user. Again, it might not be a problem for your use case, but it could mean a potential vulnerability in someone else's system.
The concerns about scopes and audience could be resolved by configuration. E.g. you could limit what scopes or audiences a specific client_id could be issued.
I'm not 100% sure that's true for audiences. I mean, the audience is just hard-coded to the client ID.
I'm happy to work up a token verification endpoint for dex.
Well, that's not my point at all. :)
Bigger picture is that we're integrating one or more external IdPs using dex on a per-cluster basis and wiring it all up to have Envoy talk to it. It'd be a bit of a pain to do what you're desribing.
I'm not entirely sure why: can Envoy only talk to one OAuth2 server at a time. Anyway, it's still all too coincidental to me: The fact that Dex uses OAuth2 under the hood for OIDC doesn't mean it's a fully fledged OAuth2 server.
I'm afraid this is an incredibly deep rabbit hole: next thing we realize we are reviewing PRs for token introspection and revocation which are absolutely not in scope for Dex. And this PR basically makes an implementation detail (JWT access token) part of the "spec" without token introspection.
I still haven't heard an argument why introducing a separate OAuth2 server for this purpose is impossible or a bad thing.
Here is the deal though: if you accept any valid token, you accept ID tokens as well (because the way it's implemented in Dex). It's not that difficult to get an ID token for a user. Again, it might not be a problem for your use case, but it could mean a potential vulnerability in someone else's system.
If you've gotten the id token or access token, you're vulnerable. This is a given regardless, scopes or not. That's why access tokens are supposed to have a limited lifetime, no?
The concerns about scopes and audience could be resolved by configuration. E.g. you could limit what scopes or audiences a specific client_id could be issued.
I'm not 100% sure that's true for audiences. I mean, the audience is just hard-coded to the client ID.
Would this concern not just be resolved by dealing with the audience correctly? Why is this not a problem for existing user credentials?
I'm not entirely sure why: can Envoy only talk to one OAuth2 server at a time.
More specifically, in that this is Kubernetes we're talking about here, we're using Emissary-ingress, and no, it only allows a single AuthService to be configured. :)
I'm afraid this is an incredibly deep rabbit hole: next thing we realize we are reviewing PRs for token introspection and revocation which are absolutely not in scope for Dex. And this PR basically makes an implementation detail (JWT access token) part of the "spec" without token introspection.
I'm not clear why you wouldn't want to support token revocation? :) As to the implementation detail being part of the spec, I hate to break it to you, but that horse has likely already left the barn long ago. Implicit or not, it is effectively part of the spec.
If you've gotten the id token or access token, you're vulnerable. This is a given regardless, scopes or not. That's why access tokens are supposed to have a limited lifetime, no?
I'm not talking about stolen tokens. You can legitimately get your own ID token (eg. when using any kind of public client auth). And without audience check, a user could access any of the services that accepts a Dex issued JWT token (id or access).
Would this concern not just be resolved by dealing with the audience correctly? Why is this not a problem for existing user credentials?
What existing user credentials? :) Currently, the access token issued by Dex should only be consumed by Dex. So I guess technically you are right: Dex is vulnerable, although when it's the only audience apart from the client, this is probably less interesting.
More specifically, in that this is Kubernetes we're talking about here, we're using Emissary-ingress, and no, it only allows a single AuthService to be configured. :)
Can you point me to some sort of documentation? I'd like to understand how they are consuming both OIDC and OAuth2 client credentials at the same time.
As to the implementation detail being part of the spec, I hate to break it to you, but that horse has likely already left the barn long ago. Implicit or not, it is effectively part of the spec.
Technically, it's not. There is no token introspection implemented, so everything you rely on is implementation detail. That being said, I guess people would be mad if we changed it...
Let's pretend for a moment that we want to expand Dex and make it a fully fledged OAuth2 server. I'm afraid there is still a whole lot of work we would have to do to make it secure enough and we have a whole lot of other things on our plates right now. I guess we could hide it behind feature flags, exempt it from the backwards compatibility promise....
I need to think about it a bit more. I wouldn't mind to here from @xtremerui as well: how they use it, in what environment, etc.
Appreciate the input from you all! Here is how Concourse uses client credentials grant for its worker node.
First, we use Dex as a third party library in Concousre. Typically, Concourse has two type of nodes: web and worker; and a CLI called fly. Dex server runs as the auth server in each web node.
Web UI and fly CLI are user interfaces that accessing Concourse API. They use auth code flow mainly. Additionlly, as being a CLI, fly needs to support auth when no browser is available i.e. bot in CI. So we submitted https://github.com/dexidp/dex/pull/1621 and it works well for us.
On the other hand, worker becomes our problem with Dex. Concourse worker is a containerized machine that running tasks that assigned from ATC
in web node. When starting up, worker needs to register itself to ATC. Then it keeps heartbeating so ATC knows it is in good state.
In summary, worker need to auth with Dex for access token for below ATC endpoints:
/api/v1/register. // runs only one time
/api/v1/heartbeat // every 30s
... // there are some more worker managing endpoints similar to `register`
Since there is no user involved and it is a web node to worker node authentication, we think it falls perfectly into the scenario where client credentials grant should be used.
fwiw, the use case that @xtremerui described is very congruent with my own internal use case as well.
Hey folks, is there anything I can do to help this PR move forward?
Hi there, any intention still to merge this feature?
I've tested the fork this change is based on and found that in the case of a public client, Dex will issue client credentials for the public client if I provide the client ID and leave out the secret value (or set it to null). This is an issue for our use; simply knowing the client ID (which is sent in plaintext by the UI when getting a token) is enough to get an access token. Not sure if that has been resolved in this PR though.
We've ended up using the Device Code Flow with a staticClient and staticPassword as an alternative, but having full support for the Client Credentials Grant would be better.
With the allowedGrants, I think now we can add the support for client credentials grant and make it not enabled by default.
@sagikazarmark what do you think?