Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Update JWT login type to support JWKS, custom sub claim, and encode special chars in user ID #9493

Closed
wants to merge 14 commits into from

Conversation

boti7
Copy link

@boti7 boti7 commented Feb 25, 2021

This PR adds the following optional fields to the JWT login type configuration:

  • jwks_uri allows specifying a JSON Web Key Set endpoint where RSA signing keys can be retrieved from, instead of using a secret or public key from the configuration.
  • subject_claim allows specifying a claim other than the default "sub" to use as localpart of the Matrix ID.
  • normalize_user_id enables mapping the localpart from other character sets using the map_username_to_mxid_localpart function.

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
  • Pull request includes a sign off
  • Code style is correct (run the linters)

Signed-off-by: Botond Horvath hello@boti7.com

Signed-off-by: Botond Horvath <hello@boti7.com>
… ID for JWT logins

Signed-off-by: Botond Horvath <hello@boti7.com>
…logins

Signed-off-by: Botond Horvath <hello@boti7.com>
Signed-off-by: Botond Horvath <hello@boti7.com>
@clokep
Copy link
Contributor

clokep commented Feb 25, 2021

Looks like reasonable improvements! 👍 This seems broken on Python 3.5 (which pyjwt technically doesn't support: jpadilla/pyjwt#585, Synapse will be dropping it shortly). Also could you see if any of the documentation under docs/jwt.md needs to be updated? (I don't know if we need to detail all the options there + the sample config, but at least mentioning some of the additional features likely makes sense.)

@clokep
Copy link
Contributor

clokep commented Mar 8, 2021

@boti7 It seems that some unrelated changes were pushed onto this branch for CI. If those are improvements that would be useful we should do them as a separate PR.

Did you see my comment above? Note that we'll be dropping support for Python 3.5 soon, so might make sense to just wait on that if possible.

@worldofgeese
Copy link

@clokep this was my fault. We'll roll it all back.

@clokep
Copy link
Contributor

clokep commented Mar 8, 2021

(Also merging in develop should fix the mypy issues.)

@worldofgeese
Copy link

@clokep I've hard reset to @boti7's last change. Let me know if there's anything else I can do.

@clokep clokep requested a review from a team March 8, 2021 13:54
Copy link
Contributor

@clokep clokep left a comment

Choose a reason for hiding this comment

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

Looks pretty reasonable overall. I suspect it would be a nice follow-up to move the JWT code into a separate handler instead of sprinkling it into LoginRestServlet.

synapse/config/jwt_config.py Outdated Show resolved Hide resolved
synapse/config/jwt_config.py Outdated Show resolved Hide resolved
synapse/config/jwt_config.py Outdated Show resolved Hide resolved
synapse/rest/client/v1/login.py Show resolved Hide resolved

if not key and self.jwt_jwks_uri:
jwks_client = PyJWKClient(self.jwt_jwks_uri)
signing_key = jwks_client.get_signing_key_from_jwt(token)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this make a synchronous HTTP call? If so we ideally would do this via a SimpleHttpClient or push this into the background.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, seems like it makes a synchronous HTTP call. It is also possible to implement the loading and parsing of JWKS without PyJWT, like in OidcHandler, then we have more control over the request and caching.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fetching this for every login seems quite inefficient. I wonder if we should do something on start-up (like OIDC). It looks like PyJWKClient is quite simple!

@boti7
Copy link
Author

boti7 commented Mar 9, 2021

I just made a small change to docs/jwt.md and I think the other options are clear from the sample config, but let me know if you want me to add anything else.

From my side it's totally fine to wait until you drop support for Python 3.5. Also merged in develop, but there are still some mypy issues.

@anoadragon453
Copy link
Member

We've now dropped Python 3.5 support 🎉

@erikjohnston erikjohnston added the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label May 20, 2021
@erikjohnston
Copy link
Member

I think we're still waiting for some changes here? If nothing else there is a merge conflict. @boti7 please nudge if/when its ready for review 🙂

@e-001
Copy link

e-001 commented Jun 17, 2021

@boti7 I'll clean up this conflict and PR. Desperately need escaped sub claim. Auth0 lovingly uses a pipe to separate auth method and ID.

@e-001
Copy link

e-001 commented Jun 17, 2021

a77ebc9 Caught up to develop and refactored for 683d6f7. Waiting on MQSdk#1.

Rebase JWT-auth changes onto upstream/develop
@erikjohnston
Copy link
Member

Looks like the JWT tests are failing when using old-deps, possibly need to bump the minimum version of the library?

@boti7
Copy link
Author

boti7 commented Jun 19, 2021

@erikjohnston It's ready for review from my side.

@richvdh richvdh requested a review from a team June 21, 2021 10:25
@erikjohnston erikjohnston removed the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label Jun 22, 2021
Copy link
Contributor

@clokep clokep left a comment

Choose a reason for hiding this comment

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

Looks pretty good overall! I wonder if there's enough JWT logic here now that we should move this code to a separate JwtHandler. What do you think?

synapse/config/jwt.py Outdated Show resolved Hide resolved
@@ -107,7 +107,7 @@
"url_preview": ["lxml>=3.5.0"],
"sentry": ["sentry-sdk>=0.7.2"],
"opentracing": ["jaeger-client>=4.0.0", "opentracing>=2.2.0"],
"jwt": ["pyjwt>=1.6.4"],
"jwt": ["pyjwt>=2.1.0"],
Copy link
Contributor

Choose a reason for hiding this comment

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

I asked the packages whether this change would be OK. Can you remind me why we're bumping this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like supported versions of Debian and Fedora are on 1.7.1 still. Would it be possible to use that?

Copy link
Author

Choose a reason for hiding this comment

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

PyJWKClient was added in v2.0.0 and caching of signing keys is supported since v2.1.0: https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst#v210

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not really sure what to do about this, although I do wonder if we should be using PyJWKClient since it bypasses the reactor / current methods for communicating with external resources.


if not key and self.jwt_jwks_uri:
jwks_client = PyJWKClient(self.jwt_jwks_uri)
signing_key = jwks_client.get_signing_key_from_jwt(token)
Copy link
Contributor

Choose a reason for hiding this comment

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

Fetching this for every login seems quite inefficient. I wonder if we should do something on start-up (like OIDC). It looks like PyJWKClient is quite simple!

Comment on lines +350 to +351
subject_claim = self.jwt_subject_claim or "sub"
user = payload.get(subject_claim, None)
Copy link
Contributor

Choose a reason for hiding this comment

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

We already did the fallback to sub in the config code, no need to do it again.

Suggested change
subject_claim = self.jwt_subject_claim or "sub"
user = payload.get(subject_claim, None)
user = payload.get(self.jwt_subject_claim, None)

Copy link
Author

Choose a reason for hiding this comment

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

The issue is that tests don't use the config code but set hs.config directly, so without specifying a fallback here, many tests in JWTTestCase would fail. What do you suggest to solve this?

Copy link
Contributor

Choose a reason for hiding this comment

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

The config code should get run during tests, see:

synapse/tests/unittest.py

Lines 469 to 472 in fe604a0

# Parse the config from a config dict into a HomeServerConfig
config_obj = HomeServerConfig()
config_obj.parse_config_dict(config, "", "")
kwargs["config"] = config_obj

Copy link
Contributor

Choose a reason for hiding this comment

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

Were you able to try this again? It should run fine during tests.

boti7 and others added 2 commits June 29, 2021 21:39
Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>
@erikjohnston erikjohnston requested a review from clokep July 23, 2021 16:16
@clokep clokep removed their request for review July 26, 2021 14:11
@clokep
Copy link
Contributor

clokep commented Jul 26, 2021

Removing my review -- this has a couple of open questions still that are waiting for responses!

@clokep clokep added the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label Jul 26, 2021
@anoadragon453
Copy link
Member

Hey @boti7, are you able to continue working on this? 🙂

Also note that there is now a conflict in synapse/rest/client/v1/login.py. You'll need to simply merge the develop branch into yours to resolve it.

@anoadragon453
Copy link
Member

anoadragon453 commented Sep 14, 2021

Given the lack of activity, I'm going to close this. @boti7 feel free to reopen if you'd like to continue working on this!

Anyone else is free to pick it back up in a new PR as well 🙂

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants