diff --git a/.github/workflows/singleuser-release.yaml b/.github/workflows/singleuser-release.yaml index 2f91629..e85a205 100644 --- a/.github/workflows/singleuser-release.yaml +++ b/.github/workflows/singleuser-release.yaml @@ -53,8 +53,13 @@ jobs: release-image-ide: if: github.event.inputs.dockerfile_name == 'IDE.Dockerfile' runs-on: ubuntu-latest + strategy: + matrix: + include: + - py_ver: '3.11' + - py_ver: '3.12' env: - PYTHON_VERSION: '3.11' + PYTHON_VERSION: ${{matrix.py_ver}} JUPYTERHUB_VERSION: ${{github.event.inputs.jupyterhub_version}} DOCKERFILE_NAME: ${{github.event.inputs.dockerfile_name}} IMAGE_SUFFIX: '-ide' diff --git a/README.md b/README.md index dda3463..b790376 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ## Table of Contents - [Introduction](#introduction) - [Quick Start](#quick-start) + - [Hub configuration env vars](#hub-configuration-env-vars) - [Build Jupyterhub Singleuser Image](#build-jupyterhub-singleuser-image) - [Configuration](#configuration) - [Build](#build) @@ -24,6 +25,20 @@ Directory explain: - `~/hub` is a Docker based jupyterhub host server using DockerSpawner and Docker-in-Docker to launch jupyterhub singleuser server as a Docker container, in order to locally validate the singleuser images for development purpose.(**Don't recommend to use this in production, due to the security concern of Docker-in-Docker**) +## Hub configuration env vars + +The hub image (`hub/config/jupyterhub_config.py`) requires the following environment variables at startup: + +| Env var | Description | Example | +|---------|-------------|---------| +| `OAUTH2_CLIENT_ID` | Keycloak client ID | `codebook` | +| `OAUTH2_CLIENT_SECRET` | Keycloak client secret | (secret) | +| `KEYCLOAK_HOST` | Keycloak hostname, no scheme | `keycloak.datajoint.com` | +| `OAUTH_CALLBACK_URL` | Fully-qualified OAuth callback URL | `https://codebook.datajoint.com/hub/oauth_callback` | + +The container will fail to start if any of these are unset. + + ## Build Jupyterhub Singleuser Image ### Configuration diff --git a/hub/config/jupyterhub_config.py b/hub/config/jupyterhub_config.py index ce5e596..a3f3493 100644 --- a/hub/config/jupyterhub_config.py +++ b/hub/config/jupyterhub_config.py @@ -101,12 +101,24 @@ async def refresh_user(self, user, handler=None): c.JupyterHub.ssl_key = '/etc/letsencrypt/live/fakeservices.datajoint.io/privkey.pem' c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/fakeservices.datajoint.io/fullchain.pem' c.JupyterHub.authenticator_class = RefreshingAuthenticator -c.GenericOAuthenticator.client_id = os.getenv("OAUTH2_CLIENT_ID") -c.GenericOAuthenticator.client_secret = os.getenv("OAUTH2_CLIENT_SECRET") -c.GenericOAuthenticator.oauth_callback_url = "https://127.0.0.1:8000/hub/oauth_callback" -c.GenericOAuthenticator.authorize_url = "https://keycloak-qa.datajoint.io/realms/datajoint/protocol/openid-connect/auth" -c.GenericOAuthenticator.token_url = "https://keycloak-qa.datajoint.io/realms/datajoint/protocol/openid-connect/token" -c.GenericOAuthenticator.userdata_url = "https://keycloak-qa.datajoint.io/realms/datajoint/protocol/openid-connect/userinfo" +c.GenericOAuthenticator.client_id = os.environ["OAUTH2_CLIENT_ID"] +c.GenericOAuthenticator.client_secret = os.environ["OAUTH2_CLIENT_SECRET"] + +# Keycloak host and OAuth callback URL come from environment so the hub image +# is reusable across environments. Set KEYCLOAK_HOST to the keycloak hostname +# (e.g. "keycloak.datajoint.com"), no scheme. OAUTH_CALLBACK_URL must be a +# fully-qualified HTTPS URL. +# +# Known residual risks (not yet addressed): verify=False is still in effect +# on the OAuth callback (see TODO above), and jupyter_codeserver_proxy is on +# its 1.0b3 beta pin. +_keycloak_host = os.environ["KEYCLOAK_HOST"] +_realm_path = "realms/datajoint/protocol/openid-connect" +c.GenericOAuthenticator.oauth_callback_url = os.environ["OAUTH_CALLBACK_URL"] +c.GenericOAuthenticator.authorize_url = f"https://{_keycloak_host}/{_realm_path}/auth" +c.GenericOAuthenticator.token_url = f"https://{_keycloak_host}/{_realm_path}/token" +c.GenericOAuthenticator.userdata_url = f"https://{_keycloak_host}/{_realm_path}/userinfo" +c.GenericOAuthenticator.logout_redirect_url = f"https://{_keycloak_host}/{_realm_path}/logout" c.GenericOAuthenticator.login_service = "Datajoint" c.GenericOAuthenticator.username_claim = "preferred_username" c.GenericOAuthenticator.enable_auth_state = True