diff --git a/config/clusters/earthscope/common.values.yaml b/config/clusters/earthscope/common.values.yaml index f1c42f498c..16b420da25 100644 --- a/config/clusters/earthscope/common.values.yaml +++ b/config/clusters/earthscope/common.values.yaml @@ -33,9 +33,88 @@ basehub: name: "EarthScope Consortium" url: https://www.earthscope.org/ hub: + extraConfig: + 001-username-claim: | + from oauthenticator.auth0 import Auth0OAuthenticator + from traitlets import List, Unicode + + class CustomAuth0OAuthenticator(Auth0OAuthenticator): + # 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( + Unicode(), + config=True, + help=""" + List of scopes that must be granted to allow login. + + All the scopes listed in this config must be present in the OAuth2 grant + from the authorizing server to allow the user to login. We request all + the scopes listed in the 'scope' config, but only a subset of these may + be granted by the authorization server. This may happen if the user does not + have permissions to access a requested scope, or has chosen to not give consent + for a particular scope. If the scopes listed in this config are not granted, + the user will not be allowed to log in. + + See the OAuth documentation of your OAuth provider for various options. + """, + ) + + async def authenticate(self, *args, **kwargs): + auth_model = await super().authenticate(*args, **kwargs) + username = auth_model["name"] + # This is required until https://github.com/jupyterhub/oauthenticator/pull/717 + # gets merged, can be removed after that. + if username.startswith("oauth2|cilogon"): + cilogon_sub = username.rsplit("|", 1)[-1] + cilogon_sub_parts = cilogon_sub.split("/") + username = f"oauth2|cilogon|{cilogon_sub_parts[3]}|{cilogon_sub_parts[5]}" + auth_model["name"] = username + return auth_model + + async def check_allowed(self, username, auth_model): + if await super().check_allowed(username, auth_model): + return True + + if self.required_scopes: + granted_scopes = auth_model.get('auth_state', {}).get('scope', []) + missing_scopes = set(self.required_scopes) - set(granted_scopes) + if missing_scopes: + self.log.info(f"Denying access to user {username} - scopes {missing_scopes} were not granted, only {granted_scopes} were granted") + return False + else: + return True + + return False + + def populate_token(spawner, auth_state): + token_env = { + 'AUTH0_ACCESS_TOKEN': auth_state.get("access_token", ""), + 'AUTH0_ID_TOKEN': auth_state.get("id_token", ""), + 'AUTH0_REFRESH_TOKEN': auth_state.get('refresh_token', '') + } + spawner.environment.update(token_env) + + c.Spawner.auth_state_hook = populate_token + + c.JupyterHub.authenticator_class = CustomAuth0OAuthenticator config: JupyterHub: - authenticator_class: cilogon + authenticator_class: auth0 + CustomAuth0OAuthenticator: + required_scopes: + # This allows EarthScope to control who can login to the hub + - geolab + Auth0OAuthenticator: + scope: + - openid + # This gives us refresh token + - offline_access + # This allows EarthScope to control who can login to the hub + - geolab + extra_authorize_params: + # This isn't an actual URL, just a string. Must not have a trailing slash + audience: https://api.dev.earthscope.org + username_claim: sub CILogonOAuthenticator: allowed_idps: http://github.com/login/oauth/authorize: @@ -46,9 +125,12 @@ basehub: username_derivation: username_claim: email Authenticator: + enable_auth_state: true admin_users: - timdittmann - chad-earthscope + - google-oauth2|101500906458831444600 + - oauth2|cilogon|servera|32158821 singleuser: profileList: - display_name: "Shared Small: 1-4 CPU, 8-32 GB" diff --git a/config/clusters/earthscope/enc-prod.secret.values.yaml b/config/clusters/earthscope/enc-prod.secret.values.yaml index 323551895b..2894018c8d 100644 --- a/config/clusters/earthscope/enc-prod.secret.values.yaml +++ b/config/clusters/earthscope/enc-prod.secret.values.yaml @@ -2,6 +2,9 @@ basehub: jupyterhub: hub: config: + Auth0OAuthenticator: + client_id: ENC[AES256_GCM,data:DwOUn4AFZyJrPv2gw3SvArLXNrEOQgoWJPYLpJSQetE=,iv:HFevqec5FROZQkAfCnkoVZacFhVsRB2Fym82XHDzFBw=,tag:O9it7h27UX7a89sExeXs9A==,type:str] + client_secret: ENC[AES256_GCM,data:fQdBLKrl9OG1zB9wX0+j10K+1+rgSTz1/v/tVOcV8ZZcXM8FCs9EKR2nhvfrMHL3nX59NMUeI64Jb9EG16mE5g==,iv:JfSBDbzia4xNSWPmW3Cde8RqUg78l6t34yviXx54VXU=,tag:9ID3wzmWvZa1hbfsT2rTyw==,type:str] CILogonOAuthenticator: client_id: ENC[AES256_GCM,data:1C0ercYZjjc63vTPPcVa7B0Y1bnuawg854Yf3Kl4UnJ0gYuqem+zuv1lQfOzU8zKXy5L,iv:2IZjb7WzomJg8I9uDDXINjULJPXUBfJCldMOxH+B8tA=,tag:Dv1xaVkDCpI7/GLuGv6GzA==,type:str] client_secret: ENC[AES256_GCM,data:2mGbTTnKcVZp57ZX2Tj2o+j2y0NfABPtTiV6sw3oWlR/t7w4fiFkSK9cyArnJwQfRjWc6M6NNB50A3zWZrKaoPLRj8Afiq8pFTjtRZnZGe5g4h2mXYg=,iv:xmJEHc2V0aG1KEh2eAPj80tZoNzFnBz42QdCSmzO2mc=,tag:+48SuYVdlJCizaYVMn9hrA==,type:str] @@ -14,8 +17,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-12-14T18:24:34Z" - mac: ENC[AES256_GCM,data:0Kde6XE/A7k9CwhxQFsa3I61ohr9WN7AO2haWkFETpDG+jXtU5MYkrScbwnlayLa0vM6vk2OfUxR6LrB9jPcxTx8+n2Pqx6kPTzgr8a8ORhG4xc6Lqj0a1KyDMdnGi5beqoXSxolPyd1mnSTAFAVIGwle37Gg0fIr0VFii9lsfQ=,iv:gPVYPvyTEriA9sxbmtMRo611b5dB5idYa0J+DtEYcaY=,tag:RNOItHNKtUGZ/UgfT1Ea2Q==,type:str] + lastmodified: "2024-01-24T22:53:57Z" + mac: ENC[AES256_GCM,data:MgnyRZmQryZqw+0gy3yUp3syuIYsWi3vvkOQrjW4jkk3/ZfIjWyo81cnO3Jgxr2pAADAbqX4qKITpBuWpX05lEDhv3kg3L6DAhnY0iExuDSWHGYJ04856pADGtuHIFIYmQxG46u+RfpTljVZK4cHAY4OVUraHVbKVxg/iP5pkpU=,iv:XF43toaqgiGjUh0W3HG0Iq2Y10paP24BGnPk9HomLKk=,tag:bsp93INUlVt1WJA4h7uVLw==,type:str] pgp: [] unencrypted_suffix: _unencrypted 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 bab6d99775..7dc792e327 100644 --- a/config/clusters/earthscope/enc-staging.secret.values.yaml +++ b/config/clusters/earthscope/enc-staging.secret.values.yaml @@ -2,6 +2,9 @@ basehub: jupyterhub: hub: config: + Auth0OAuthenticator: + client_id: ENC[AES256_GCM,data:zAZAcTnDoYXd6+HEHyCTAZcWDfFb4MVGaHguf+l80jc=,iv:aQidh2IJMcMcEPBCyB7I94of0ywyvNNc4R/9jrTh/Xo=,tag:EN3jpNVKALN4L5mBw21Ptg==,type:str] + client_secret: ENC[AES256_GCM,data:glfuw+S6w1n8hNOvYlEPvTVU6yfAePNt1/zzz8ttrW8eTro5o05dKLeUgULp75/tk5BbVoYkjt3VsruVWq5nWg==,iv:GtB9642/chhguJaLsvI/It1kGWH/VZ5J/ubdbu5GzvY=,tag:Ym62f23AnqPDEFTDC9RwAA==,type:str] CILogonOAuthenticator: client_id: ENC[AES256_GCM,data:Lv/25K0A8CZs6dK20mujkn536hpreimP/MUqGOJ4cpXLTFnJNRmGkN7mYPC2klalEKcn,iv:nj4b7Y75A9wgg+w2XBas17Cs8Az3AzDkeO9u1ZwI1Jo=,tag:gCMMoa3iQWVRQvTQkCIkAg==,type:str] client_secret: ENC[AES256_GCM,data:EAD3iQGXs7soD4VxRXol2YuuJBmOpDBbX5Cg+VyTk7xA7Jn715vZMNBeOKtal1a6kzyds3tuw+h+DWsF3Dod2MxHS7H4FARHLopP9xuAvS6Tw3mZZ28=,iv:F8CqwLYz7WR5qge0Yj91aU/w5pj6fiEaBvndVe4zvG4=,tag:60BekNlkRhf2a3Nkvo1kWg==,type:str] @@ -14,8 +17,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-12-14T18:42:52Z" - mac: ENC[AES256_GCM,data:DWO/hv47PbcFx8NATfOJrLUMkOV3dTUzr53nUtpDge+NseEOSoMKeEWz1L7jWYhM+Iga05csm78BT9c3gI921dKlOXRJ6fn1e5guxqKPOAuZugbWUeEqGa8Z26sAwuSRXIZyWiWDJZJThsNk4+s0s7vZmXcrGHGjWA3eCEvTwxE=,iv:9QDeyrmE0euFgqcvZMCuubNA44YB8x2Sa1CqEGJjKjM=,tag:qj5ZnX1TS2Lt4QbXuJFB0Q==,type:str] + lastmodified: "2024-01-24T23:03:04Z" + mac: ENC[AES256_GCM,data:ZPZmbQLCeuK1C7FR8USNXtJiE8xV6esOt4tcqSRuwe73HxAyogAstYBqDz5rlsi5qf68ew6dLkhX17oiJxABTCi4PpNMMktuVGe10OrlAEgZm4cRc3H4MfdMEfS/2I7V0PcItJINqte0EGQbYqRYgkz5XCA4+0k8075uIqypoug=,iv:uzeiyu9hP6mo7YphNJU/AZOquKU055IxznWiDXrETrA=,tag:qwDi7EleXQaYHagsXS7jzA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.8.1 diff --git a/config/clusters/earthscope/prod.values.yaml b/config/clusters/earthscope/prod.values.yaml index a850dd41e8..e8e7aeee13 100644 --- a/config/clusters/earthscope/prod.values.yaml +++ b/config/clusters/earthscope/prod.values.yaml @@ -12,5 +12,10 @@ basehub: name: "EarthScope" hub: config: + Auth0OAuthenticator: + auth0_domain: login.earthscope.org + extra_authorize_params: + # This isn't an actual URL, just a string. Must not have a trailing slash + audience: https://api.earthscope.org CILogonOAuthenticator: oauth_callback_url: https://earthscope.2i2c.cloud/hub/oauth_callback diff --git a/config/clusters/earthscope/staging.values.yaml b/config/clusters/earthscope/staging.values.yaml index bb621d8433..4f2a7d6049 100644 --- a/config/clusters/earthscope/staging.values.yaml +++ b/config/clusters/earthscope/staging.values.yaml @@ -12,5 +12,10 @@ basehub: name: "EarthScope staging" hub: config: + Auth0OAuthenticator: + auth0_domain: login-dev.earthscope.org + extra_authorize_params: + # This isn't an actual URL, just a string. Must not have a trailing slash + audience: https://api.dev.earthscope.org CILogonOAuthenticator: oauth_callback_url: https://staging.earthscope.2i2c.cloud/hub/oauth_callback diff --git a/docs/hub-deployment-guide/configure-auth/auth0.md b/docs/hub-deployment-guide/configure-auth/auth0.md new file mode 100644 index 0000000000..ff5f34d6a1 --- /dev/null +++ b/docs/hub-deployment-guide/configure-auth/auth0.md @@ -0,0 +1,86 @@ +(auth:auth0)= +# Auth0 + +[Auth0](https://auth0.com/) is a commercial authentication provider that some communities +would like to use, for the various extra features it offers. Since it's outside the primary +two authentication mechanisms we offer, this costs extra - please confirm with partnerships +team that the community is being billed for it. + +## Set up the hub with CILogon + +First, we set up the hub and use [CILogon](auth:cilogon) for authentication, so the community +can get started and poke around. This decouples getting started from the auth0 process, +to make everything smoother (for both 2i2c engineers and the community). + +## Requesting credentials from the community + +We have to ask the community to create and provision Auth0 credentials for us. They will need +to create a [Regular Auth0 Web App](https://auth0.com/docs/get-started/auth0-overview/create-applications/regular-web-apps) +for each hub - so at the least, for the staging hub and the production hub. + +Under [Application URIs](https://auth0.com/docs/get-started/applications/application-settings#application-uris), +they should use the following URL under "Allowed Callback URLs": + +`https:///hub/oauth_callback` + +Once created, they should collect the following information: + +1. `client_secret` and `client_id` for the created application. +2. The "Auth0 domain" for the created application. + +These are *secure credentials*, and must be sent to us using [the encrypted support mechanism](https://docs.2i2c.org/support/#send-us-encrypted-content) + +They can configure this with whatever [connections](https://auth0.com/docs/connections) they +prefer - 2i2c is not responsible for and hence can not really help with configuring this. + +```{note} + +It may be advantageous to 2i2c engineers to have shared access to this auth0 web application, +so we can debug issues that may arise. But we don't want to create too much friction here, +by having to manually create accounts for each 2i2c engineer for each auth0 application we +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. + +In the `common.yaml` file for the cluster hosting the hubs, we set the authenticator to be `auth0`. + +```yaml +jupyterhub: + hub: + config: + JupyterHub: + authenticator_class: auth0 +``` + +In the encrypted, per-hub config (of form `enc-.secret.values.yaml`), we specify the secret values +we received from the community. + +```yaml +jupyterhub: + hub: + config: + Auth0OAuthenticator: + client_id: + client_secret: +``` + +And in the *unencrypted*, per-hub config (of form `.values.yaml`), we specify the non-secret +config values. + +```yaml +jupyterhub: + hub: + config: + Auth0OAuthenticator: + auth0_domain: + scope: openid + username_claim: sub +``` + +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 diff --git a/docs/hub-deployment-guide/configure-auth/index.md b/docs/hub-deployment-guide/configure-auth/index.md index 2da95120b0..b7cb0939ae 100644 --- a/docs/hub-deployment-guide/configure-auth/index.md +++ b/docs/hub-deployment-guide/configure-auth/index.md @@ -12,4 +12,5 @@ Switching authentication providers (e.g. from GitHub to Google) for a pre-existi :caption: Authentication Providers github-orgs cilogon +auth0 ```