Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client Credentials Grant #1629

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

xtremerui
Copy link

so client can get access to Dex resources e.g. forwarding auth request and performing token exchange while handling callback from Dex.

@karunchennuri
Copy link

@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.

@xtremerui
Copy link
Author

@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.

@karunchennuri
Copy link

karunchennuri commented Jun 10, 2020 via email

@karunchennuri
Copy link

@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.

@xtremerui
Copy link
Author

@karunchennuri glad it works. The test could be ran using the Makefile

dex/Makefile

Lines 44 to 45 in 2ca992e

test: bin/test/kube-apiserver bin/test/etcd
@go test -v ./...

make test

@mvdkleijn
Copy link
Contributor

@xtremerui Would you be open to update this PR since it has conflicts?

@xtremerui
Copy link
Author

@mvdkleijn absolutely. Most of my PRs got conflicts I will update all of them. Thank you!

@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 2 times, most recently from d06e60b to 37225ee Compare August 4, 2020 19:13
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 3 times, most recently from 579a1de to 133d9b6 Compare September 28, 2020 19:39
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 3 times, most recently from 47d4519 to b648030 Compare October 5, 2020 17:39
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from b648030 to 00c37b6 Compare October 16, 2020 16:43
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 2 times, most recently from 5b265dd to 12421b2 Compare November 5, 2020 04:19
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 12421b2 to 378e4c2 Compare December 15, 2020 15:54
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 2 times, most recently from 4530e4b to 1eeff38 Compare March 16, 2021 16:15
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 1eeff38 to 93b8d35 Compare March 20, 2021 20:06
@xtremerui xtremerui closed this Nov 10, 2021
@xtremerui xtremerui reopened this Nov 10, 2021
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from e84cf0e to dbb4f3e Compare November 10, 2021 19:44
@xtremerui
Copy link
Author

@sagikazarmark could I get some feedback on this PR please?

@ericchiang
Copy link
Contributor

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

@kellyma2
Copy link
Contributor

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.

@kellyma2
Copy link
Contributor

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?

@kellyma2
Copy link
Contributor

fyi,I should point out that I'm not the original PR author. I'm just using the PR in my setup

@sagikazarmark
Copy link
Member

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.

@kellyma2
Copy link
Contributor

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.

@sagikazarmark
Copy link
Member

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.

@xtremerui
Copy link
Author

xtremerui commented Jan 18, 2022

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 #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.

@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 6536373 to a759248 Compare February 9, 2022 16:03
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from a759248 to f7a0f79 Compare March 22, 2022 14:15
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from f7a0f79 to 7f90e61 Compare May 26, 2022 16:08
@kellyma2
Copy link
Contributor

kellyma2 commented Jun 9, 2022

fwiw, the use case that @xtremerui described is very congruent with my own internal use case as well.

@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 7f90e61 to 59bfbd3 Compare August 2, 2022 17:09
server/handlers.go Fixed Show fixed Hide fixed
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 2 times, most recently from a031678 to 73f1657 Compare September 16, 2022 05:28
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch 2 times, most recently from 1905f8f to 4e3fe5b Compare October 4, 2022 10:34
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 4e3fe5b to 895f3b2 Compare October 31, 2022 15:02
@RealHarshThakur
Copy link

Hey folks, is there anything I can do to help this PR move forward?

@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 895f3b2 to 7a07880 Compare March 20, 2023 13:31
Rui Yang and others added 3 commits June 30, 2023 14:13
This fixes two issues in the existing client credentials change:

- client_credentials was not listed as a supported grant type
- access tokens are not the storage ID

Signed-off-by: Michael Kelly <[email protected]>
@xtremerui xtremerui force-pushed the pr/client-credentials-grant-sync branch from 7a07880 to 3f0f531 Compare June 30, 2023 14:13
@cloudmarius
Copy link

Hi there, any intention still to merge this feature?

@jarrettprosser
Copy link

jarrettprosser commented Sep 13, 2023

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.

@nabokihms
Copy link
Member

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?

@smehboub
Copy link

smehboub commented Jun 9, 2024

Hello,

Thank you very much for your work.
Any news on progress ?

Thanks in advance :-)
Rgs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.