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

fix(server): Switch to using access token for SSO. Fixes #4027 #4035

Closed
wants to merge 7 commits into from

Conversation

alexec
Copy link
Contributor

@alexec alexec commented Sep 15, 2020

Checklist:

  • Either (a) I've created an enhancement proposal and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
  • The title of the PR is (a) conventional, (b) states what changed, and (c) suffixes the related issues number. E.g. "fix(controller): Updates such and such. Fixes #1234".
  • I've signed the CLA.
  • I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
  • My builds are green. Try syncing with master if they are not.
  • My organization is added to USERS.md.

Fixes #4027

@jessesuen and @edlee2121 this changes how argo-server authenticates users. Previously, we stored their id_token as a cookie. This token was adequate because we need only to verify their identity. This is similar to Argo CD. However, ID tokens are not intended as long lived and expire after 5m, forcing users to re-login.

This PR switches to use the access token instead of the ID token. We should not store an access token in a cookie the same way you could an ID token, because if the access token is stolen, the token could be used to make API requests with other system. So now, access token is AES-GCM-256 encrypted when saved as a cookie. It is useless apart from speaking to Argo Workflows.

The encryption key is cryptographically secure random generated string. It is saved into a Kubernetes secret on first start up, so it can then be used any argo-server replica.

The access token is opaque, unlike the ID token so each argo-server must make an API request to validate it. Each time a request is made, we decrypt the cookie, and make a OIDC UserInfo request.

The cookie has a 10h expiry, secure and same-site-strict, so not accessible to HTTP or other apps. This is unchanded.

If the cookie is compromised, we are in the same situation as today (which is acceptable).

If the encryption key is compromised, the operator should delete it and restart the service. All users will be logged out, and must log back in.

@@ -19,7 +19,7 @@ import (
"github.com/argoproj/argo/server/auth/jws"
)

const Prefix = "Bearer id_token:"
const Prefix = "Bearer v2:"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this magic string could be anything, but lets expose a little less info

@alexec alexec added this to the v2.11 milestone Sep 16, 2020
@RichiCoder1
Copy link

RichiCoder1 commented Sep 16, 2020

Hoping I'm understanding this PR correctly, and do have some feedback:

However, ID tokens are not intended as long lived and expire after 5m, forcing users to re-login.
This PR switches to use the access token instead of the ID token.

For some context, the intent is also that the access_token in OAuth2 systems itself should also be short lived. Instead, you usually have some sort of refresh_token or credentials which can be given to a provider for a newly minted access & id token. Or at least that's how it works in OIDC land usually. So the app or session manager would either reactively (in response to a 401) or proactively (by monitoring exp) regularly request a new access_token/id_token/refresh_token. However here, you're not really doing OIDC or even OAuth, you'll using a JWT as a session token so this is a bit different.

We should not store an access token in a cookie the same way you could an ID token, because if the access token is stolen, the token could be used to make API requests with other system. So now, access token is AES-GCM-256 encrypted when saved as a cookie. It is useless apart from speaking to Argo Workflows.

Correct me if I'm wrong, but encrypting it does nothing, no? It just means an interceptor can't read the jwt, but it doesn't prevent them from actually using it. It sounds like what you're trying to accomplish might be better accomplished via setting HttpOnly and Secure on the cookie. Doesn't prevent HTTPS-effective MITM attacks, but prevents most other attacks. For more info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#:~:text=Restrict%20access%20to%20cookies

@alexec
Copy link
Contributor Author

alexec commented Sep 16, 2020

Correct me if I'm wrong, but encrypting it does nothing, no?

This prevents someone from taking the cookie and using to speak directly to another system that accepts the access token - because it is encrypted and they cannot decrypt it. In additional, this token would only have openid scope. Finally, I believe the cookie security is covered is covered by HttpOnly and Secure, SameSiteStrict

@RichiCoder1
Copy link

RichiCoder1 commented Sep 16, 2020

This prevents someone from taking the cookie and using to speak directly to another system that accepts the access token - because it is encrypted and they cannot decrypt it. In additional, this token would only have openid scope. Finally, I believe the cookie security is covered is covered by HttpOnly and Secure, SameSiteStrict

This is one way to solve that, but usually this is also solved by requesting the access_token with something like a specific audience and then the server verifies the audience. Or via client_id or some other information stored in the jwt itself. Unless your concern is two identically configured servers, except deployed in different clusters, recieving the token?

@zachaller
Copy link
Contributor

zachaller commented Sep 17, 2020

Moved from slack to github:

So I did take a look at this and it looks like an Authorization Code flow from the server that you are then storing the access token in an encrypted cookie, I have seen this done in a few projects such as https://github.com/oauth2-proxy/oauth2-proxy the one downside to this is that cookie can get very large when encrypted with some access tokens such is the case generally with jwt’s and azure comes to mind here (also jwt’s with group scopes) oauth2-proxy actually implements a cookie splitting but actually recommend using their redis backend instead to store the token and then they just store a short session cookie https://oauth2-proxy.github.io/oauth2-proxy/auth-configuration#azure-auth-provider notice the note at the bottom

Note: When using the Azure Auth provider with nginx and the cookie session store you may find the cookie is too large and doesn't get passed through correctly. Increasing the proxy_buffer_size in nginx or implementing the redis session storage should resolve this.

But other than that from a security perspective nothing jumps out at me, but I didn’t spend a lot of time looking at how the cookie encryption was done.
The other option I am not sure if you thought of is the Authorization Code Flow with PKCE then it’s all done client side the downside to it is that the access_token is not stored encrypted on the client but then the server would have to validate access token agains the oauth provider because Argo Workflows has a server component to it I don’t think Auth Flow with PKCE makes the most sense but it is another flow you might be interested in knowing about

@zachaller
Copy link
Contributor

zachaller commented Sep 17, 2020

I will say the one area I think PKCE might make sense and I kind of alluded a bit to this is with people that have a k8s cluster setup for oauth logins already then you don't really need any server component you just need to get a token from the same oauth provider that k8s is using and use that token then communicate to kubernetes directly from the client say a local argo server command.

@KevinThompsonYCRX
Copy link

Is there any consideration of implementing token_refresh in this PR as well? Azure access tokens will still be limited to 3600 seconds without it.

I believe it would require using a second cookie to store the refresh_token, adding a handler for an 'oauth2/refresh' endpoint, and then updating the UI to try and hit that endpoint on a 401 error before redirecting to the login page.

@alexec alexec linked an issue Sep 18, 2020 that may be closed by this pull request
@alexec
Copy link
Contributor Author

alexec commented Sep 22, 2020

After discussion with corp experts - this is wrong approach.

@alexec alexec closed this Sep 22, 2020
@alexec alexec deleted the access_token branch September 22, 2020 00:35
@alexec
Copy link
Contributor Author

alexec commented Sep 22, 2020

Superseded by #4095

@agilgur5 agilgur5 added area/sso-rbac solution/superseded This PR or issue has been superseded by another one (slightly different from a duplicate) labels May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/sso-rbac solution/superseded This PR or issue has been superseded by another one (slightly different from a duplicate)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SSO logs out quickly SSO Timeout Configuration
5 participants