From c463c4041a289bd13e70539c30bcd9870613e579 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Fri, 29 Mar 2024 17:16:57 -0700 Subject: [PATCH] Use GenericOAuthenticator to support Auth0 The Auth0 authenticator made the EarthScope authentication mechanism look more custom than it really was - it's really just standard auth0, and if you look at the [auth0 authenticator](https://github.com/jupyterhub/oauthenticator/blob/main/oauthenticator/auth0.py) code it's fairly minimal - just a couple of convenience functions. This PR switches that (+ our auth0 documentation) to simply use GenericOAuthenticator. This has the following advantages: 1. The Auth0 documentation we have can be easily ported to just support any Generic OAuth provider if needed in the future. 2. The GenericOAuthenticator has features the Auth0 one does not - particularly around groups management that we do want to use. While eventually I think this should be made available to all authenticators (and will work with upstream in doing so), moving to GenericOAuthenticator unblocks planning & scheduling engineering work here as soon as https://github.com/2i2c-org/infrastructure/pull/3818 is merged. 3. It signals that the EarthScope hub is not *that* special, just the first as a way for us to develop and offer new features. We should work on structuring how we do this, and signal when features are available in what hubs. But in the meantime, this reduces the overall apparent complexity to match actual complexity 4. Removes the custom logout_url work, and instead just mentions you need to set the `client_id` in the logout_url, and adds that to our auth0 documentation. This removes more custom code we have for EarthScope. Once https://github.com/jupyterhub/oauthenticator/pull/719 is merged, this helps us remove all custom code here from earthscope. This part fixes https://github.com/2i2c-org/infrastructure/issues/3715 This was triggered as cleanup by https://2i2c.freshdesk.com/a/tickets/1453. I'll create appropriate issues for next steps, and will prioritize any future work accordingly with our engineering processes. --- config/clusters/earthscope/common.values.yaml | 36 ++++--------------- .../earthscope/enc-prod.secret.values.yaml | 12 +++---- .../earthscope/enc-staging.secret.values.yaml | 12 +++---- config/clusters/earthscope/prod.values.yaml | 9 ++--- .../clusters/earthscope/staging.values.yaml | 9 ++--- .../configure-auth/auth0.md | 24 +++++++++---- 6 files changed, 46 insertions(+), 56 deletions(-) diff --git a/config/clusters/earthscope/common.values.yaml b/config/clusters/earthscope/common.values.yaml index 59c4aa637c..ee98b14eab 100644 --- a/config/clusters/earthscope/common.values.yaml +++ b/config/clusters/earthscope/common.values.yaml @@ -37,11 +37,11 @@ basehub: hub: extraConfig: 001-username-claim: | - from oauthenticator.auth0 import Auth0OAuthenticator + from oauthenticator.generic import GenericOAuthenticator from traitlets import List, Unicode, default from urllib.parse import urlencode - class CustomAuth0OAuthenticator(Auth0OAuthenticator): + class CustomGenericOAuthenticator(GenericOAuthenticator): # required_scopes functionality comes in from https://github.com/jupyterhub/oauthenticator/pull/719 # Can be removed from here once that PR is merged required_scopes = List( @@ -62,28 +62,6 @@ basehub: """, ) - # Upstreamed at https://github.com/jupyterhub/oauthenticator/pull/722 - logout_redirect_to_url = Unicode( - config=True, - help=""" - Redirect to this URL after the user is logged out. - - Must be explicitly added to the "Allowed Logout URLs" in the configuration - for this Auth0 application. See https://auth0.com/docs/authenticate/login/logout/redirect-users-after-logout - for more information. - """ - ) - - @default("logout_redirect_url") - def _logout_redirect_url_default(self): - url = f"https://{self.auth0_domain}/v2/logout" - if self.logout_redirect_to_url: - # If a redirectTo is set, we must also include the `client_id` - # Auth0 expects `client_id` to be snake cased while `redirectTo` is camel cased - params = urlencode({"client_id": self.client_id, "redirectTo": self.logout_redirect_to_url}) - url = f"{url}?{params}" - return url - async def check_allowed(self, username, auth_model): if await super().check_allowed(username, auth_model): return True @@ -112,15 +90,15 @@ basehub: c.Spawner.auth_state_hook = populate_token - c.JupyterHub.authenticator_class = CustomAuth0OAuthenticator + c.JupyterHub.authenticator_class = CustomGenericOAuthenticator config: - JupyterHub: - authenticator_class: auth0 - CustomAuth0OAuthenticator: + # JupyterHub: + # authenticator_class: auth0 + CustomGenericOAuthenticator: required_scopes: # This allows EarthScope to control who can login to the hub - geolab - Auth0OAuthenticator: + GenericOAuthenticator: scope: - openid # This gives us refresh token diff --git a/config/clusters/earthscope/enc-prod.secret.values.yaml b/config/clusters/earthscope/enc-prod.secret.values.yaml index 0300bd385c..7ec0eabf06 100644 --- a/config/clusters/earthscope/enc-prod.secret.values.yaml +++ b/config/clusters/earthscope/enc-prod.secret.values.yaml @@ -2,9 +2,9 @@ basehub: jupyterhub: hub: config: - Auth0OAuthenticator: - client_id: ENC[AES256_GCM,data:qn8Xel6vzFKHuL7gP8aGKQr3C7AGORQ7sCyNvKulbDE=,iv:bWYt/w31HcaEDjUBW3DZv/Lb4Ny/BPEjoBTsjp0XP6g=,tag:/02E1lYfhfOMcd+P2+DV8Q==,type:str] - client_secret: ENC[AES256_GCM,data:qry2vIkYLTRd7rlg6RTO6pB+e4SP5mvzClqagyJbbzXYkdeiGQccVFsERQ15RT/BRDX/PX4Bj5ZxcuCY9wGsxw==,iv:k763ow53AuqWG7dSyqkaosa9O4NwufRnmmORRxssGQA=,tag:MX4zqeOS3vdxhhYUljipJA==,type:str] + GenericOAuthenticator: + client_id: ENC[AES256_GCM,data:+ctWM1MpyksEjMLTnVZAw+N0Wv6ZNXL+fHdeamt64Ow=,iv:1KBoaNQTaUmyAt1wAO9pmvOkoLCl+B2eCBIu3SsRKYA=,tag:sK3EmbdSJP3UldX51274xA==,type:str] + client_secret: ENC[AES256_GCM,data:UtnmnF84dQ50h741JNvLmfBkzoI6ui16YVV8tRh1GXyCIrcu5bgy5StRIIjw3uRVo7g7bFFdGN6lIeQIUVd+Pw==,iv:arvQ5RbKiHFFNdyksmIA9UVoHWRdXgeKAhDGPPE7qrU=,tag:2NdZqXXBpEIUhrA3oTnKPw==,type:str] sops: kms: [] gcp_kms: @@ -14,8 +14,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2024-02-01T19:48:36Z" - mac: ENC[AES256_GCM,data:Cw3rTUGqQlymWXXu/Z7qLSAIlULn5B3SAPxbzkeBDCFSO8u4fhuZXEjoEBvFFdujdEtU9Q7bASKRyl4aveZDJ+aZHboKNDV77d7atONojcEFj/DIy2ELQriMwyq1hx5hZS/onGgt8XLmcjXDJdMH6zEZOYrZl93uTuoS+Qt+4GI=,iv:oCWMr+17mgo+P1btrLglokBO3yYZ9JpZBTx36Vhtb3s=,tag:GEN4T0/9KQzz4+oTxvhbBQ==,type:str] + lastmodified: "2024-03-29T23:50:57Z" + mac: ENC[AES256_GCM,data:h9pUWffgf8vBqG4timmCMharFGj1jdP8iSaaczx1GfzouUG+hhlG82OQTFVSmLwhHkzlmxJxw+t7gi6Zwx9nNgVVfnwa4Qhw6V/XWrBRr8gre2I9+MuXXeYOcjiDqIyasF0TYxGW/kvLZ6+khGvi4iIhnk9rJOk/LpFhpj7IthQ=,iv:1yIFhnW1Mv+d5bBKFGgpMDCCt5zPGfP9YekGey4KF/g=,tag:YgXBpr03lP5q9y1sJ2CsUA==,type:str] pgp: [] unencrypted_suffix: _unencrypted - version: 3.7.3 + version: 3.8.1 diff --git a/config/clusters/earthscope/enc-staging.secret.values.yaml b/config/clusters/earthscope/enc-staging.secret.values.yaml index 9ae205942a..f48a3830a8 100644 --- a/config/clusters/earthscope/enc-staging.secret.values.yaml +++ b/config/clusters/earthscope/enc-staging.secret.values.yaml @@ -2,9 +2,9 @@ basehub: jupyterhub: hub: config: - Auth0OAuthenticator: - client_id: ENC[AES256_GCM,data:urLrYypX6IUSVpqFAumEAi9aGJKyQv8oQuNqw5HNhKo=,iv:sQcq2R5wbS2P00nygxPQ3p2LdAsxkRQrk4jvnMWAjQg=,tag:eosLpXx6vWQMNjIxDcsC7Q==,type:str] - client_secret: ENC[AES256_GCM,data:PrphM7gVSfUOInO008VgfhNU4r1+I4oLRT+ypJv5848Bvy1nN+ARzTrPgu7Q3KIiCaXyfNd3Xv6ieb0lsKCLZw==,iv:Vnbo4jG0sARtOL28GxgGAKKITQb5Tx6/TNscWUNgkJU=,tag:RgxhqVzdfsmwMTTEMCn2Zw==,type:str] + GenericOAuthenticator: + client_id: ENC[AES256_GCM,data:Rpa6XhJLmHBkccOZM58T0IwcviJvc2+jbLbL3LDQxgI=,iv:57//hbKbkT8PDa1kanOoS4wlWLvc1hp8fyGgMMaUKzk=,tag:zyv29aa/M7cqar2izZDRTg==,type:str] + client_secret: ENC[AES256_GCM,data:w7feSVDwFN0mbxvLH1DEpw/eanx5+vJXZ7JPSTkVxIAm0aZod4H7lhlEy/gmMgPUJfBF32tXPrrYh6Z5E83oIQ==,iv:RQt6NCiDwAwn15XGxF7T+DVdYck0kw/hKEV9ULgxY1k=,tag:BRnwMh76yaMF7RyKPhBd/g==,type:str] sops: kms: [] gcp_kms: @@ -14,8 +14,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2024-02-01T19:48:31Z" - mac: ENC[AES256_GCM,data:ZYVgv+u0FD+jxYtgyITNLXr5bHNEEkkXtTM0SJGv8txbAVM4yt1k9CF95iVevPsRGG2yztY5vTDQaFGeg0tLGmG55fuuliZhMrB9RsDkmM3qEibVgQQTQZI5ZUciWHSBGm/NCMKnj6ujIx0h3E3cjtZBESIpONH+66kbuGhAlMo=,iv:ORwgid7PCff05bxWN9FuWNCN+wLY+bVZi0GfGDZwQj4=,tag:O+Bkh0UbeDzmAvYWJ5PpKQ==,type:str] + lastmodified: "2024-03-29T23:43:10Z" + mac: ENC[AES256_GCM,data:OnvUNbNHox7iF98w1aJSnrFJ1C3FSD+dz/l7ZK1z5uBnJAyhX3FhVoDGmA3TWAtS5U+ebiz8RbbVjJ/ge687ke2dL/Lnd9Ueay2tsF4ac1BYF6i5LqqsHqzaPwkrRVazB1aRgKx/O37Plm8KuAg2o9dN8jGtjnnSlbIxgJuJIUQ=,iv:Eo0iQ6qrbbcUkPFHzBwuMBGi1fCYnVWLPkkn9GQfrig=,tag:iUbwcWtat7qkfNE5i5MU6A==,type:str] pgp: [] unencrypted_suffix: _unencrypted - version: 3.7.3 + version: 3.8.1 diff --git a/config/clusters/earthscope/prod.values.yaml b/config/clusters/earthscope/prod.values.yaml index 55c932cbe2..8aec56439d 100644 --- a/config/clusters/earthscope/prod.values.yaml +++ b/config/clusters/earthscope/prod.values.yaml @@ -12,10 +12,11 @@ basehub: name: "EarthScope" hub: config: - CustomAuth0OAuthenticator: - logout_redirect_to_url: https://geolab.earthscope.cloud - Auth0OAuthenticator: - auth0_domain: login.earthscope.org + GenericOAuthenticator: + token_url: https://login.earthscope.org/oauth/token + authorize_url: https://login.earthscope.org/authorize + userdata_url: https://login.earthscope.org/userinfo + logout_redirect_url: https://login.earthscope.org/v2/logout?client_id=2PbhUTbRU6e7uIaaEZIShotx15MbvsJJ extra_authorize_params: # This isn't an actual URL, just a string. Must not have a trailing slash audience: https://api.earthscope.org diff --git a/config/clusters/earthscope/staging.values.yaml b/config/clusters/earthscope/staging.values.yaml index 11541d14a8..91c420e16a 100644 --- a/config/clusters/earthscope/staging.values.yaml +++ b/config/clusters/earthscope/staging.values.yaml @@ -13,10 +13,11 @@ basehub: name: "EarthScope staging" hub: config: - CustomAuth0OAuthenticator: - logout_redirect_to_url: https://staging.geolab.earthscope.cloud - Auth0OAuthenticator: - auth0_domain: login-dev.earthscope.org + GenericOAuthenticator: + token_url: https://login-dev.earthscope.org/oauth/token + authorize_url: https://login-dev.earthscope.org/authorize + userdata_url: https://login-dev.earthscope.org/userinfo + logout_redirect_url: https://login-dev.earthscope.org/v2/logout?client_id=Kn6kSKtw9TqgrSrEmDS0rlBM7Sc69BkL extra_authorize_params: # This isn't an actual URL, just a string. Must not have a trailing slash audience: https://api.dev.earthscope.org diff --git a/docs/hub-deployment-guide/configure-auth/auth0.md b/docs/hub-deployment-guide/configure-auth/auth0.md index ff5f34d6a1..80a2d222d6 100644 --- a/docs/hub-deployment-guide/configure-auth/auth0.md +++ b/docs/hub-deployment-guide/configure-auth/auth0.md @@ -43,17 +43,20 @@ administer. Solutions (potentially a shared account) are being explored. ## Configuring the JupyterHub to use Auth0 -We will use the upstream [Auth0OAuthenticator](https://github.com/jupyterhub/oauthenticator/blob/main/oauthenticator/auth0.py) -to allow folks to login to JupyterHub. +While there is an upstream [Auth0OAuthenticator](https://github.com/jupyterhub/oauthenticator/blob/main/oauthenticator/auth0.py), +it doesn't have any specific features that aren't in the upstream [GenericOAuthenticator](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.generic.html), +and is missing some features that are present in the GenericOAuthenticator. Using the GenericOAuthenticator +here also allows us to support other Generic OAuth providers in the future, and not tie ourselves down +to Auth0. -In the `common.yaml` file for the cluster hosting the hubs, we set the authenticator to be `auth0`. +In the `common.yaml` file for the cluster hosting the hubs, we set the authenticator to be `generic`. ```yaml jupyterhub: hub: config: JupyterHub: - authenticator_class: auth0 + authenticator_class: generic ``` In the encrypted, per-hub config (of form `enc-.secret.values.yaml`), we specify the secret values @@ -63,9 +66,10 @@ we received from the community. jupyterhub: hub: config: - Auth0OAuthenticator: + GenericOAuthenticator: client_id: client_secret: + logout_redirect_url: https:///v2/logout?client_id= ``` And in the *unencrypted*, per-hub config (of form `.values.yaml`), we specify the non-secret @@ -75,12 +79,18 @@ config values. jupyterhub: hub: config: - Auth0OAuthenticator: - auth0_domain: + GenericOAuthenticator: + token_url: https:///oauth/token + authorize_url: https:///authorize + userdata_url: https:///userinfo scope: openid username_claim: sub ``` +Auth0 has documentation for the [userinfo](https://auth0.com/docs/api/authentication#get-user-info), +[token](https://auth0.com/docs/api/authentication#authenticate-user) and [authorize](https://auth0.com/docs/api/authentication#social) +endpoints. + Once deployed, this should allow users authorized by Auth0 to login to the hub! Their usernames will look like `:`, which looks a little strange but allows differentiation between people who use multiple accounts but the same email. \ No newline at end of file