Skip to content
Snippets Groups Projects
Commit ba45e97c authored by Klaas Kliffen's avatar Klaas Kliffen :satellite:
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
*.env
.venv
\ No newline at end of file
This diff is collapsed.
# Interactive Data Analysis Hub
A JupyterHub example deployment for connecting to an OIDC Provider and providing access tokens to sofware running in the notebook.
## How does it work
Jupyter hub provides an `/user` endpoint in it's api which allows a logged in
user to view it's own information. The default jupyter hub only shows `auth_state` information when the user has the `admin` role. Using the [AuthStateJupyterHub](https://gitlab.cern.ch/escape-wp2/docker-images/-/tree/master/jupyterhub/jupyterhub.d/AuthStateJupyterHub) from CERN's Swanhub is a wrapper which allows the tokens to be visible to all logged in users.
Using the `OIDCAuthenticator` from the same [repo](https://gitlab.cern.ch/escape-wp2/docker-images/-/tree/master/jupyterhub/jupyterhub.d/OIDCAuthenticator) it allows to connect to OIDC providers for authentication.
## Configuring
* Setup an IAM client: `https://iam-escape.cloud.cnaf.infn.it/manage/dev/dynreg/new`
* Make sure that the redirect URI's are set-up correctly including http(s)://
* The following scopes are required:
* `openid`
* `profile`
* `email`
* `offline_acces` (for refresh tokens)
* `wlcg.groups`
* `wlcg`
* The following grant types should be enabled:
* `code`
* `refresh`
* `token exchange` (requires manual intervention by the IAM administrator, ask on rocketchat)
* Copy `hub.env.example` to `hub.env`
```
# client and secret id generated by registering the application in IAM
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
# ODIC address and list (multiple entries separated by `,`) of audiences
OIDC_ISSUER=https://iam-escape.cloud.cnaf.infn.it/
OAUTH_ALLOWED_AUDIENCES=https://wlcg.cern.ch/jwt/v1/any
# List of audiences to exchange tokens for (e.g. rucio, esap etc.)
EXCHANGE_TOKENS=rucio
# Key used to encrypt the tokens stored in the Jupyter Database
# can be generated by openssl:
JUPYTERHUB_CRYPT_KEY=$(openssl rand -hex 32)
```
## Running the Hub
```bash
docker-compose build
docker pull <notebook image> # optional: to speed up the launching of new containers
# jupyterhub/singleuser:<jupyterhub version> by default
docker-compose up -d
```
## Accessing exchanged tokens in a notebook
```python
import os
import requests
URL = os.getenv("JUPYTERHUB_API_URL")
TOKEN = os.getenv("JUPYTERHUB_API_TOKEN")
res = requests.get(f"{URL}/user", headers={"Authorization": f"token {TOKEN}"})
res.json()["auth_state"]["exchanged_tokens"]["rucio"]
```
To prevent requesting the API for the token, the JWT should be cached by the requesting software. The cache can be invalidated by decoding the JWT and looking for the `exp` claim, which is a Unix timestamp for invalidation. The jupyter hub takes care of refreshing the tokens which are provided via the Hub API.
One example of softare caching the tokens is [SwanOauthRenew](https://github.com/swan-cern/jupyter-extensions/tree/master/SwanOauthRenew). **TODO**: add configuration information
## Caveats
* No SSL configured, but can easily added by prodivind a ssl key and cert or using NGINX as Reverse proxy
* There is no persistent filesystem yet in this configuration. The containers are kept around by the jupyter hub, but their data is lost when stopped containers are cleaned up.
\ No newline at end of file
version: "3.5"
services:
hub:
image: ida_hub
build: hub
ports:
- "8000:8000"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock" # allow this container to spawn docker containers
networks:
- hub_network
env_file: hub.env
networks:
hub_network:
name: hub_network
\ No newline at end of file
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
OIDC_ISSUER=
OAUTH_ALLOWED_AUDIENCES=
EXCHANGE_TOKENS=
JUPYTERHUB_CRYPT_KEY=
\ No newline at end of file
FROM jupyterhub/jupyterhub
# Install Docker
RUN apt-get update && apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
git \
lsb-release
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
RUN echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
RUN apt-get update && apt-get -y install docker-ce-cli
# Install docker spawner and OIDCAuthenticator and AuthStateJupyterHub
RUN pip install dockerspawner jupyter_client
RUN pip install "git+https://gitlab.cern.ch/escape-wp2/docker-images.git#egg=oidcauthenticator&subdirectory=jupyterhub/jupyterhub.d/OIDCAuthenticator"
RUN pip install "git+https://gitlab.cern.ch/escape-wp2/docker-images.git#egg=authstatejupyterhub&subdirectory=jupyterhub/jupyterhub.d/AuthStateJupyterHub"
COPY jupyterhub_minimal_config.py /usr/local/etc/jupyterhub/jupyterhub_config.py
CMD ["authstatejupyterhub", "--config", "/usr/local/etc/jupyterhub/jupyterhub_config.py"]
import os
from jupyter_client.localinterfaces import public_ips
c.JupyterHub.authenticator_class = 'oidcauthenticator.OIDCAuthenticator'
c.OIDCAuthenticator.username_key = 'preferred_username'
c.OIDCAuthenticator.scope = ['openid', 'profile', 'offline_access']
c.OIDCAuthenticator.allowed_roles = []
c.OIDCAuthenticator.check_signature=True
c.OIDCAuthenticator.verify_aud=True
c.OIDCAuthenticator.allowed_audience=os.environ["OAUTH_ALLOWED_AUDIENCES"].split(",")
c.OIDCAuthenticator.exchange_tokens = os.environ["EXCHANGE_TOKENS"].split(",")
c.OIDCAuthenticator.jwt_signing_algorithms = ["RS256"]
c.OIDCAuthenticator.oidc_issuer = os.environ["OIDC_ISSUER"]
c.OIDCAuthenticator.enable_auth_state = True
# The docker instances need access to the Hub, so the default loopback port doesn't work:
c.JupyterHub.hub_ip = public_ips()[0]
# spawn with Docker
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
# This should be the same as in the docker-compose.yml file
network_name = "hub_network"
c.DockerSpawmer.use_internal_ip = True
c.DockerSpawner.network_name = network_name
c.DockerSpawner.extra_host_config = { 'network_mode': network_name }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment