mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 15:03:02 +00:00
Merge branch 'main' into Improve-on-documentation-websecurity.md
This commit is contained in:
@@ -14,7 +14,7 @@ satisfy the following:
|
||||
- After testing, the server in question should be able to score at least an A on the
|
||||
Qualys SSL Labs [SSL Server Test](https://www.ssllabs.com/ssltest/)
|
||||
|
||||
Let's start out with needed JupyterHub configuration in `jupyterhub_config.py`:
|
||||
Let's start out with the needed JupyterHub configuration in `jupyterhub_config.py`:
|
||||
|
||||
```python
|
||||
# Force the proxy to only listen to connections to 127.0.0.1 (on port 8000)
|
||||
@@ -30,15 +30,15 @@ This can take a few minutes:
|
||||
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
|
||||
```
|
||||
|
||||
## nginx
|
||||
## Nginx
|
||||
|
||||
This **`nginx` config file** is fairly standard fare except for the two
|
||||
`location` blocks within the main section for HUB.DOMAIN.tld.
|
||||
To create a new site for jupyterhub in your nginx config, make a new file
|
||||
To create a new site for jupyterhub in your Nginx config, make a new file
|
||||
in `sites.enabled`, e.g. `/etc/nginx/sites.enabled/jupyterhub.conf`:
|
||||
|
||||
```bash
|
||||
# top-level http config for websocket headers
|
||||
# Top-level HTTP config for WebSocket headers
|
||||
# If Upgrade is defined, Connection = upgrade
|
||||
# If Upgrade is empty, Connection = close
|
||||
map $http_upgrade $connection_upgrade {
|
||||
@@ -51,7 +51,7 @@ server {
|
||||
listen 80;
|
||||
server_name HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
# Redirect the request to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ server {
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
|
||||
# Managing literal requests to the JupyterHub front end
|
||||
# Managing literal requests to the JupyterHub frontend
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -101,10 +101,10 @@ server {
|
||||
If `nginx` is not running on port 443, substitute `$http_host` for `$host` on
|
||||
the lines setting the `Host` header.
|
||||
|
||||
`nginx` will now be the front facing element of JupyterHub on `443` which means
|
||||
`nginx` will now be the front-facing element of JupyterHub on `443` which means
|
||||
it is also free to bind other servers, like `NO_HUB.DOMAIN.TLD` to the same port
|
||||
on the same machine and network interface. In fact, one can simply use the same
|
||||
server blocks as above for `NO_HUB` and simply add line for the root directory
|
||||
server blocks as above for `NO_HUB` and simply add a line for the root directory
|
||||
of the site as well as the applicable location call:
|
||||
|
||||
```bash
|
||||
@@ -112,7 +112,7 @@ server {
|
||||
listen 80;
|
||||
server_name NO_HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
# Redirect the request to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
@@ -143,12 +143,12 @@ Now restart `nginx`, restart the JupyterHub, and enjoy accessing
|
||||
`https://HUB.DOMAIN.TLD` while serving other content securely on
|
||||
`https://NO_HUB.DOMAIN.TLD`.
|
||||
|
||||
### SELinux permissions for nginx
|
||||
### SELinux permissions for Nginx
|
||||
|
||||
On distributions with SELinux enabled (e.g. Fedora), one may encounter permission errors
|
||||
when the nginx service is started.
|
||||
when the Nginx service is started.
|
||||
|
||||
We need to allow nginx to perform network relay and connect to the jupyterhub port. The
|
||||
We need to allow Nginx to perform network relay and connect to the JupyterHub port. The
|
||||
following commands do that:
|
||||
|
||||
```bash
|
||||
@@ -157,26 +157,26 @@ setsebool -P httpd_can_network_relay 1
|
||||
setsebool -P httpd_can_network_connect 1
|
||||
```
|
||||
|
||||
Replace 8000 with the port the jupyterhub server is running from.
|
||||
Replace 8000 with the port the JupyterHub server is running from.
|
||||
|
||||
## Apache
|
||||
|
||||
As with nginx above, you can use [Apache](https://httpd.apache.org) as the reverse proxy.
|
||||
First, we will need to enable the apache modules that we are going to need:
|
||||
As with Nginx above, you can use [Apache](https://httpd.apache.org) as the reverse proxy.
|
||||
First, we will need to enable the Apache modules that we are going to need:
|
||||
|
||||
```bash
|
||||
a2enmod ssl rewrite proxy headers proxy_http proxy_wstunnel
|
||||
```
|
||||
|
||||
Our Apache configuration is equivalent to the nginx configuration above:
|
||||
Our Apache configuration is equivalent to the Nginx configuration above:
|
||||
|
||||
- Redirect HTTP to HTTPS
|
||||
- Good SSL Configuration
|
||||
- Support for websockets on any proxied URL
|
||||
- Support for WebSocket on any proxied URL
|
||||
- JupyterHub is running locally at http://127.0.0.1:8000
|
||||
|
||||
```bash
|
||||
# redirect HTTP to HTTPS
|
||||
# Redirect HTTP to HTTPS
|
||||
Listen 80
|
||||
<VirtualHost HUB.DOMAIN.TLD:80>
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
@@ -188,26 +188,26 @@ Listen 443
|
||||
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
|
||||
# enable HTTP/2, if available
|
||||
# Enable HTTP/2, if available
|
||||
Protocols h2 http/1.1
|
||||
|
||||
# HTTP Strict Transport Security (mod_headers is required) (63072000 seconds)
|
||||
Header always set Strict-Transport-Security "max-age=63072000"
|
||||
|
||||
# configure SSL
|
||||
# Configure SSL
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem
|
||||
SSLOpenSSLConfCmd DHParameters /etc/ssl/certs/dhparam.pem
|
||||
|
||||
# intermediate configuration from ssl-config.mozilla.org (2022-03-03)
|
||||
# Please note, that this configuration might be out-dated - please update it accordingly using https://ssl-config.mozilla.org/
|
||||
# Intermediate configuration from SSL-config.mozilla.org (2022-03-03)
|
||||
# Please note, that this configuration might be outdated - please update it accordingly using https://ssl-config.mozilla.org/
|
||||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||
SSLHonorCipherOrder off
|
||||
SSLSessionTickets off
|
||||
|
||||
# Use RewriteEngine to handle websocket connection upgrades
|
||||
# Use RewriteEngine to handle WebSocket connection upgrades
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Connection} Upgrade [NC]
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
@@ -224,7 +224,7 @@ Listen 443
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
In case of the need to run the jupyterhub under /jhub/ or other location please use the below configurations:
|
||||
In case of the need to run JupyterHub under /jhub/ or another location please use the below configurations:
|
||||
|
||||
- JupyterHub running locally at http://127.0.0.1:8000/jhub/ or other location
|
||||
|
||||
@@ -241,7 +241,7 @@ httpd.conf amendments:
|
||||
jupyterhub_config.py amendments:
|
||||
|
||||
```bash
|
||||
--The public facing URL of the whole JupyterHub application.
|
||||
--This is the address on which the proxy will bind. Sets protocol, ip, base_url
|
||||
--The public-facing URL of the whole JupyterHub application.
|
||||
--This is the address on which the proxy will bind. Sets protocol, IP, base_url
|
||||
c.JupyterHub.bind_url = 'http://127.0.0.1:8000/jhub/'
|
||||
```
|
||||
|
@@ -1,8 +1,8 @@
|
||||
# Configuring user environments
|
||||
|
||||
Deploying JupyterHub means you are providing Jupyter notebook environments for
|
||||
To deploy JupyterHub means you are providing Jupyter notebook environments for
|
||||
multiple users. Often, this includes a desire to configure the user
|
||||
environment in some way.
|
||||
environment in a custom way.
|
||||
|
||||
Since the `jupyterhub-singleuser` server extends the standard Jupyter notebook
|
||||
server, most configuration and documentation that applies to Jupyter Notebook
|
||||
@@ -10,12 +10,9 @@ applies to the single-user environments. Configuration of user environments
|
||||
typically does not occur through JupyterHub itself, but rather through the system-wide
|
||||
configuration of Jupyter, which is inherited by `jupyterhub-singleuser`.
|
||||
|
||||
**Tip:** When searching for configuration tips for JupyterHub user
|
||||
environments, try removing JupyterHub from your search because there are a lot
|
||||
more people out there configuring Jupyter than JupyterHub and the
|
||||
configuration is the same.
|
||||
**Tip:** When searching for configuration tips for JupyterHub user environments, you might want to remove JupyterHub from your search because there are a lot more people out there configuring Jupyter than JupyterHub and the configuration is the same.
|
||||
|
||||
This section will focus on user environments, including:
|
||||
This section will focus on user environments, which includes the following:
|
||||
|
||||
- [Installing packages](#installing-packages)
|
||||
- [Configuring Jupyter and IPython](#configuring-jupyter-and-ipython)
|
||||
@@ -24,26 +21,23 @@ This section will focus on user environments, including:
|
||||
|
||||
## Installing packages
|
||||
|
||||
To make packages available to users, you generally will install packages
|
||||
system-wide or in a shared environment.
|
||||
To make packages available to users, you will typically install packages system-wide or in a shared environment.
|
||||
|
||||
This installation location should always be in the same environment that
|
||||
This installation location should always be in the same environment where
|
||||
`jupyterhub-singleuser` itself is installed in, and must be _readable and
|
||||
executable_ by your users. If you want users to be able to install additional
|
||||
executable_ by your users. If you want your users to be able to install additional
|
||||
packages, the installation location must also be _writable_ by your users.
|
||||
|
||||
If you are using a standard system Python install, you would use:
|
||||
If you are using a standard Python installation on your system, use the following command:
|
||||
|
||||
```bash
|
||||
sudo python3 -m pip install numpy
|
||||
```
|
||||
|
||||
to install the numpy package in the default system Python 3 environment
|
||||
to install the numpy package in the default Python 3 environment on your system
|
||||
(typically `/usr/local`).
|
||||
|
||||
You may also use conda to install packages. If you do, you should make sure
|
||||
that the conda environment has appropriate permissions for users to be able to
|
||||
run Python code in the env.
|
||||
Alternatively, You may also use conda to install packages. To do this, ensure that the conda environment has appropriate users permissions needed to run Python code in the environment.
|
||||
|
||||
## Configuring Jupyter and IPython
|
||||
|
||||
@@ -51,15 +45,9 @@ run Python code in the env.
|
||||
and [IPython](https://ipython.readthedocs.io/en/stable/development/config.html)
|
||||
have their own configuration systems.
|
||||
|
||||
As a JupyterHub administrator, you will typically want to install and configure
|
||||
environments for all JupyterHub users. For example, you wish for each student in
|
||||
a class to have the same user environment configuration.
|
||||
|
||||
Jupyter and IPython support **"system-wide"** locations for configuration, which
|
||||
is the logical place to put global configuration that you want to affect all
|
||||
users. It's generally more efficient to configure user environments "system-wide",
|
||||
and it's a good idea to avoid creating files in users' home directories.
|
||||
As a JupyterHub administrator, you will typically want to install and configure environments for all JupyterHub users. For example, let's say you wish for each student in a class to have the same user environment configuration.
|
||||
|
||||
Jupyter and IPython support **"system-wide"** locations for configuration, which is the logical place to put global configuration that you want to affect all users. It's generally more efficient to configure user environments "system-wide", and it's a good practice to avoid creating files in the users' home directories.
|
||||
The typical locations for these config files are:
|
||||
|
||||
- **system-wide** in `/etc/{jupyter|ipython}`
|
||||
@@ -67,8 +55,7 @@ The typical locations for these config files are:
|
||||
|
||||
### Example: Enable an extension system-wide
|
||||
|
||||
For example, to enable the `cython` IPython extension for all of your users,
|
||||
create the file `/etc/ipython/ipython_config.py`:
|
||||
For example, to enable the `cython` IPython extension for all of your users, create the file `/etc/ipython/ipython_config.py`:
|
||||
|
||||
```python
|
||||
c.InteractiveShellApp.extensions.append("cython")
|
||||
@@ -77,21 +64,18 @@ c.InteractiveShellApp.extensions.append("cython")
|
||||
### Example: Enable a Jupyter notebook configuration setting for all users
|
||||
|
||||
:::{note}
|
||||
These examples configure the Jupyter ServerApp,
|
||||
which is used by JupyterLab, the default in JupyterHub 2.0.
|
||||
These examples configure the Jupyter ServerApp, which is used by JupyterLab, the default in JupyterHub 2.0.
|
||||
|
||||
If you are using the classing Jupyter Notebook server,
|
||||
the same things should work,
|
||||
with the following substitutions:
|
||||
|
||||
- Where you see `jupyter_server_config`, use `jupyter_notebook_config`
|
||||
- Where you see `NotebookApp`, use `ServerApp`
|
||||
- Search for `jupyter_server_config`, and replace with `jupyter_notebook_config`
|
||||
- Search for `NotebookApp`, and replace with `ServerApp`
|
||||
|
||||
:::
|
||||
|
||||
To enable Jupyter notebook's internal idle-shutdown behavior (requires
|
||||
notebook ≥ 5.4), set the following in the `/etc/jupyter/jupyter_server_config.py`
|
||||
file:
|
||||
To enable Jupyter notebook's internal idle-shutdown behavior (requires notebook ≥ 5.4), set the following in the `/etc/jupyter/jupyter_server_config.py` file:
|
||||
|
||||
```python
|
||||
# shutdown the server after no activity for an hour
|
||||
@@ -104,16 +88,14 @@ c.MappingKernelManager.cull_interval = 2 * 60
|
||||
|
||||
## Installing kernelspecs
|
||||
|
||||
You may have multiple Jupyter kernels installed and want to make sure that
|
||||
they are available to all of your users. This means installing kernelspecs
|
||||
either system-wide (e.g. in /usr/local/) or in the `sys.prefix` of JupyterHub
|
||||
You may have multiple Jupyter kernels installed and want to make sure that they are available to all of your users. This means installing kernelspecs either system-wide (e.g. in /usr/local/) or in the `sys.prefix` of JupyterHub
|
||||
itself.
|
||||
|
||||
Jupyter kernelspec installation is system-wide by default, but some kernels
|
||||
may default to installing kernelspecs in your home directory. These will need
|
||||
to be moved system-wide to ensure that they are accessible.
|
||||
|
||||
You can see where your kernelspecs are with:
|
||||
To see where your kernelspecs are, you can use the following command:
|
||||
|
||||
```bash
|
||||
jupyter kernelspec list
|
||||
@@ -121,8 +103,7 @@ jupyter kernelspec list
|
||||
|
||||
### Example: Installing kernels system-wide
|
||||
|
||||
Assuming I have a Python 2 and Python 3 environment that I want to make
|
||||
sure are available, I can install their specs system-wide (in /usr/local) with:
|
||||
Let's assume that I have a Python 2 and Python 3 environment that I want to make sure are available, I can install their specs **system-wide** (in /usr/local) using the following command:
|
||||
|
||||
```bash
|
||||
/path/to/python3 -m ipykernel install --prefix=/usr/local
|
||||
@@ -141,31 +122,25 @@ How you configure user environments for each category can differ a bit
|
||||
depending on what Spawner you are using.
|
||||
|
||||
The first category is a **shared system (multi-user host)** where
|
||||
each user has a JupyterHub account and a home directory as well as being
|
||||
each user has a JupyterHub account, a home directory as well as being
|
||||
a real system user. In this example, shared configuration and installation
|
||||
must be in a 'system-wide' location, such as `/etc/`, or `/usr/local`
|
||||
or a custom prefix such as `/opt/conda`.
|
||||
|
||||
When JupyterHub uses **container-based** Spawners (e.g. KubeSpawner or
|
||||
DockerSpawner), the 'system-wide' environment is really the container image
|
||||
that you are using for users.
|
||||
DockerSpawner), the 'system-wide' environment is really the container image used for users.
|
||||
|
||||
In both cases, you want to _avoid putting configuration in user home
|
||||
directories_ because users can change those configuration settings. Also,
|
||||
home directories typically persist once they are created, so they are
|
||||
difficult for admins to update later.
|
||||
directories_ because users can change those configuration settings. Also, home directories typically persist once they are created, thereby making it difficult for admins to update later.
|
||||
|
||||
## Named servers
|
||||
|
||||
By default, in a JupyterHub deployment, each user has exactly one server.
|
||||
By default, in a JupyterHub deployment, each user has one server only.
|
||||
|
||||
JupyterHub can, however, have multiple servers per user.
|
||||
This is most useful in deployments where users can configure the environment
|
||||
in which their server will start (e.g. resource requests on an HPC cluster),
|
||||
so that a given user can have multiple configurations running at the same time,
|
||||
without having to stop and restart their one server.
|
||||
This is mostly useful in deployments where users can configure the environment in which their server will start (e.g. resource requests on an HPC cluster), so that a given user can have multiple configurations running at the same time, without having to stop and restart their own server.
|
||||
|
||||
To allow named servers:
|
||||
To allow named servers, include this code snippet in your config file:
|
||||
|
||||
```python
|
||||
c.JupyterHub.allow_named_servers = True
|
||||
@@ -183,13 +158,13 @@ as well as the admin page:
|
||||
Named servers can be accessed, created, started, stopped, and deleted
|
||||
from these pages. Activity tracking is now per server as well.
|
||||
|
||||
The number of named servers per user can be limited by setting a constant value:
|
||||
To limit the number of **named server** per user by setting a constant value, include this code snippet in your config file:
|
||||
|
||||
```python
|
||||
c.JupyterHub.named_server_limit_per_user = 5
|
||||
```
|
||||
|
||||
or a callable/awaitable based on the handler object:
|
||||
Alternatively, to use a callable/awaitable based on the handler object, include this code snippet in your config file:
|
||||
|
||||
```python
|
||||
def named_server_limit_per_user_fn(handler):
|
||||
@@ -211,8 +186,9 @@ If `named_server_limit_per_user` is set to `0`, no limit is enforced.
|
||||
|
||||
By default, the single-user server launches JupyterLab,
|
||||
which is based on [Jupyter Server][].
|
||||
|
||||
This is the default server when running JupyterHub ≥ 2.0.
|
||||
You can switch to using the legacy Jupyter Notebook server by setting the `JUPYTERHUB_SINGLEUSER_APP` environment variable
|
||||
To switch to using the legacy Jupyter Notebook server, you can set the `JUPYTERHUB_SINGLEUSER_APP` environment variable
|
||||
(in the single-user environment) to:
|
||||
|
||||
```bash
|
||||
@@ -223,19 +199,20 @@ export JUPYTERHUB_SINGLEUSER_APP='notebook.notebookapp.NotebookApp'
|
||||
[jupyter notebook]: https://jupyter-notebook.readthedocs.io
|
||||
|
||||
:::{versionchanged} 2.0
|
||||
|
||||
JupyterLab is now the default single-user UI, if available,
|
||||
which is based on the [Jupyter Server][],
|
||||
no longer the legacy [Jupyter Notebook][] server.
|
||||
JupyterHub prior to 2.0 launched the legacy notebook server (`jupyter notebook`),
|
||||
and the Jupyter server could be selected by specifying
|
||||
and the Jupyter server could be selected by specifying the following:
|
||||
|
||||
```python
|
||||
# jupyterhub_config.py
|
||||
c.Spawner.cmd = ["jupyter-labhub"]
|
||||
```
|
||||
|
||||
or for an otherwise customized Jupyter Server app,
|
||||
set the environment variable:
|
||||
Alternatively, for an otherwise customized Jupyter Server app,
|
||||
set the environment variable using the following command:
|
||||
|
||||
```bash
|
||||
export JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp'
|
||||
|
@@ -30,19 +30,19 @@ Some relevant points:
|
||||
Here are some key definitions to keep in mind when we are talking about OAuth.
|
||||
You can also read more detail [here](https://www.oauth.com/oauth2-servers/definitions/).
|
||||
|
||||
- **provider** the entity responsible for managing identity and authorization,
|
||||
- **provider**: The entity responsible for managing identity and authorization,
|
||||
always a web server.
|
||||
JupyterHub is _always_ an oauth provider for JupyterHub's components.
|
||||
When OAuthenticator is used, an external service, such as GitHub or KeyCloak, is also an oauth provider.
|
||||
- **client** An entity that requests OAuth **tokens** on a user's behalf,
|
||||
- **client**: An entity that requests OAuth **tokens** on a user's behalf,
|
||||
generally a web server of some kind.
|
||||
OAuth **clients** are services that _delegate_ authentication and/or authorization
|
||||
to an OAuth **provider**.
|
||||
JupyterHub _services_ or single-user _servers_ are OAuth **clients** of the JupyterHub **provider**.
|
||||
When OAuthenticator is used, JupyterHub is itself _also_ an OAuth **client** for the external oauth **provider**, e.g. GitHub.
|
||||
- **browser** A user's web browser, which makes requests and stores things like cookies
|
||||
- **token** The secret value used to represent a user's authorization. This is the final product of the OAuth process.
|
||||
- **code** A short-lived temporary secret that the **client** exchanges
|
||||
- **browser**: A user's web browser, which makes requests and stores things like cookies.
|
||||
- **token**: The secret value used to represent a user's authorization. This is the final product of the OAuth process.
|
||||
- **code**: A short-lived temporary secret that the **client** exchanges
|
||||
for a **token** at the conclusion of oauth,
|
||||
in what's generally called the "oauth callback handler."
|
||||
|
||||
@@ -56,8 +56,8 @@ A single oauth flow generally goes like this:
|
||||
|
||||
1. A **browser** makes an HTTP request to an oauth **client**.
|
||||
2. There are no credentials, so the client _redirects_ the browser to an "authorize" page on the oauth **provider** with some extra information:
|
||||
- the oauth **client id** of the client itself
|
||||
- the **redirect uri** to be redirected back to after completion
|
||||
- the oauth **client id** of the client itself.
|
||||
- the **redirect uri** to be redirected back to after completion.
|
||||
- the **scopes** requested, which the user should be presented with to confirm.
|
||||
This is the "X would like to be able to Y on your behalf. Allow this?" page you see on all the "Login with ..." pages around the Internet.
|
||||
3. During this authorize step,
|
||||
@@ -77,8 +77,8 @@ That's the end of the requests made between the **browser** and the **provider**
|
||||
|
||||
At this point:
|
||||
|
||||
- The browser is authenticated with the _provider_
|
||||
- The user's authorized permissions are recorded in an _oauth code_
|
||||
- The browser is authenticated with the _provider_.
|
||||
- The user's authorized permissions are recorded in an _oauth code_.
|
||||
- The _provider_ knows that the given oauth client's requested permissions have been granted, but the client doesn't know this yet.
|
||||
- All requests so far have been made directly by the browser.
|
||||
No requests have originated at the client or provider.
|
||||
@@ -86,8 +86,8 @@ At this point:
|
||||
### OAuth Client Handles Callback Request
|
||||
|
||||
Now we get to finish the OAuth process.
|
||||
Let's dig into what the oauth client does when it handles
|
||||
the oauth callback request with the
|
||||
Let's dig into what the OAuth client does when it handles
|
||||
the OAuth callback request.
|
||||
|
||||
- The OAuth client receives the _code_ and makes an API request to the _provider_ to exchange the code for a real _token_.
|
||||
This is the first direct request between the OAuth _client_ and the _provider_.
|
||||
@@ -113,24 +113,24 @@ So that's _one_ OAuth process.
|
||||
|
||||
## Full sequence of OAuth in JupyterHub
|
||||
|
||||
Let's go through the above oauth process in JupyterHub,
|
||||
Let's go through the above OAuth process in JupyterHub,
|
||||
with specific examples of each HTTP request and what information is contained.
|
||||
For bonus points, we are using the double-oauth example of JupyterHub configured with GitHubOAuthenticator.
|
||||
For bonus points, we are using the double-OAuth example of JupyterHub configured with GitHubOAuthenticator.
|
||||
|
||||
To disambiguate, we will call the OAuth process where JupyterHub is the **provider** "internal oauth,"
|
||||
and the one with JupyterHub as a **client** "external oauth."
|
||||
To disambiguate, we will call the OAuth process where JupyterHub is the **provider** "internal OAuth,"
|
||||
and the one with JupyterHub as a **client** "external OAuth."
|
||||
|
||||
Our starting point:
|
||||
|
||||
- a user's single-user server is running. Let's call them `danez`
|
||||
- jupyterhub is running with GitHub as an oauth provider (this means two full instances of oauth),
|
||||
- Danez has a fresh browser session with no cookies yet
|
||||
- Jupyterhub is running with GitHub as an OAuth provider (this means two full instances of OAuth),
|
||||
- Danez has a fresh browser session with no cookies yet.
|
||||
|
||||
First request:
|
||||
|
||||
- browser->single-user server running JupyterLab or Jupyter Classic
|
||||
- `GET /user/danez/notebooks/mynotebook.ipynb`
|
||||
- no credentials, so single-user server (as an oauth **client**) starts internal oauth process with JupyterHub (the **provider**)
|
||||
- no credentials, so single-user server (as an OAuth **client**) starts internal OAuth process with JupyterHub (the **provider**)
|
||||
- response: 302 redirect -> `/hub/api/oauth2/authorize`
|
||||
with:
|
||||
- client-id=`jupyterhub-user-danez`
|
||||
@@ -138,9 +138,9 @@ First request:
|
||||
|
||||
Second request, following redirect:
|
||||
|
||||
- browser->jupyterhub
|
||||
- browser->JupyterHub
|
||||
- `GET /hub/api/oauth2/authorize`
|
||||
- no credentials, so jupyterhub starts external oauth process _with GitHub_
|
||||
- no credentials, so JupyterHub starts external OAuth process _with GitHub_
|
||||
- response: 302 redirect -> `https://github.com/login/oauth/authorize`
|
||||
with:
|
||||
- client-id=`jupyterhub-client-uuid`
|
||||
@@ -154,8 +154,8 @@ c.JupyterHub.authenticator_class = 'github'
|
||||
```
|
||||
|
||||
That means authenticating a request to the Hub itself starts
|
||||
a _second_, external oauth process with GitHub as a provider.
|
||||
This external oauth process is optional, though.
|
||||
a _second_, external OAuth process with GitHub as a provider.
|
||||
This external OAuth process is optional, though.
|
||||
If you were using the default username+password PAMAuthenticator,
|
||||
this redirect would have been to `/hub/login` instead, to present the user
|
||||
with a login form.
|
||||
@@ -171,7 +171,7 @@ Here, GitHub prompts for login and asks for confirmation of authorization
|
||||
After successful authorization
|
||||
(either by looking up a pre-existing authorization,
|
||||
or recording it via form submission)
|
||||
GitHub issues an **oauth code** and redirects to `/hub/oauth_callback?code=github-code`
|
||||
GitHub issues an **OAuth code** and redirects to `/hub/oauth_callback?code=github-code`
|
||||
|
||||
Next request:
|
||||
|
||||
@@ -194,9 +194,9 @@ The second:
|
||||
- request made with access **token** in the `Authorization` header
|
||||
- response is the user model, including username, email, etc.
|
||||
|
||||
Now the external oauth callback request completes with:
|
||||
Now the external OAuth callback request completes with:
|
||||
|
||||
- set cookie on `/hub/` path, recording jupyterhub authentication so we don't need to do external oauth with GitHub again for a while
|
||||
- set cookie on `/hub/` path, recording jupyterhub authentication so we don't need to do external OAuth with GitHub again for a while
|
||||
- redirect -> `/hub/api/oauth2/authorize`
|
||||
|
||||
🎉 At this point, we have completed our first OAuth flow! 🎉
|
||||
@@ -211,14 +211,14 @@ Now, we get our first repeated request:
|
||||
2. automatically accepts authorization (shortcut taken when a user is visiting their own server)
|
||||
- redirect -> `/user/danez/oauth_callback?code=jupyterhub-code`
|
||||
|
||||
Here, we start the same oauth callback process as before, but at Danez's single-user server for the _internal_ oauth
|
||||
Here, we start the same OAuth callback process as before, but at Danez's single-user server for the _internal_ OAuth.
|
||||
|
||||
- browser->single-user server
|
||||
- `GET /user/danez/oauth_callback`
|
||||
|
||||
(in handler)
|
||||
|
||||
Inside the internal oauth callback handler,
|
||||
Inside the internal OAuth callback handler,
|
||||
Danez's server makes two API requests to JupyterHub:
|
||||
|
||||
The first:
|
||||
|
@@ -84,5 +84,5 @@ template (for example, `login.html`) with:
|
||||
```
|
||||
|
||||
Extending `page.html` puts the message on all pages, but note that
|
||||
extending `page.html` take precedence over an extension of a specific
|
||||
extending `page.html` takes precedence over an extension of a specific
|
||||
page (unlike the variable-based approach above).
|
||||
|
@@ -2,13 +2,13 @@
|
||||
|
||||
This document describes how JupyterHub routes requests.
|
||||
|
||||
This does not include the [REST API](./rest.md) urls.
|
||||
This does not include the [REST API](./rest.md) URLs.
|
||||
|
||||
In general, all URLs can be prefixed with `c.JupyterHub.base_url` to
|
||||
run the whole JupyterHub application on a prefix.
|
||||
|
||||
All authenticated handlers redirect to `/hub/login` to login users
|
||||
prior to being redirected back to the originating page.
|
||||
All authenticated handlers redirect to `/hub/login` to log-in users
|
||||
before being redirected back to the originating page.
|
||||
The returned request should preserve all query parameters.
|
||||
|
||||
## `/`
|
||||
@@ -25,12 +25,12 @@ This is an authenticated URL.
|
||||
|
||||
This handler redirects users to the default URL of the application,
|
||||
which defaults to the user's default server.
|
||||
That is, it redirects to `/hub/spawn` if the user's server is not running,
|
||||
or the server itself (`/user/:name`) if the server is running.
|
||||
That is, the handler redirects to `/hub/spawn` if the user's server is not running,
|
||||
or to the server itself (`/user/:name`) if the server is running.
|
||||
|
||||
This default url behavior can be customized in two ways:
|
||||
This default URL behavior can be customized in two ways:
|
||||
|
||||
To redirect users to the JupyterHub home page (`/hub/home`)
|
||||
First, to redirect users to the JupyterHub home page (`/hub/home`)
|
||||
instead of spawning their server,
|
||||
set `redirect_to_server` to False:
|
||||
|
||||
@@ -40,7 +40,7 @@ c.JupyterHub.redirect_to_server = False
|
||||
|
||||
This might be useful if you have a Hub where you expect
|
||||
users to be managing multiple server configurations
|
||||
and automatic spawning is not desirable.
|
||||
but automatic spawning is not desirable.
|
||||
|
||||
Second, you can customise the landing page to any page you like,
|
||||
such as a custom service you have deployed e.g. with course information:
|
||||
@@ -57,7 +57,7 @@ By default, the Hub home page has just one or two buttons
|
||||
for starting and stopping the user's server.
|
||||
|
||||
If named servers are enabled, there will be some additional
|
||||
tools for management of named servers.
|
||||
tools for management of the named servers.
|
||||
|
||||
_Version added: 1.0_ named server UI is new in 1.0.
|
||||
|
||||
@@ -65,34 +65,34 @@ _Version added: 1.0_ named server UI is new in 1.0.
|
||||
|
||||
This is the JupyterHub login page.
|
||||
If you have a form-based username+password login,
|
||||
such as the default PAMAuthenticator,
|
||||
such as the default [PAMAuthenticator](https://en.wikipedia.org/wiki/Pluggable_authentication_module),
|
||||
this page will render the login form.
|
||||
|
||||

|
||||
|
||||
If login is handled by an external service,
|
||||
e.g. with OAuth, this page will have a button,
|
||||
declaring "Login with ..." which users can click
|
||||
to login with the chosen service.
|
||||
declaring "Log in with ..." which users can click
|
||||
to log in with the chosen service.
|
||||
|
||||

|
||||
|
||||
If you want to skip the user-interaction to initiate logging in
|
||||
via the button, you can set
|
||||
If you want to skip the user interaction and initiate login
|
||||
via the button, you can set:
|
||||
|
||||
```python
|
||||
c.Authenticator.auto_login = True
|
||||
```
|
||||
|
||||
This can be useful when the user is "already logged in" via some mechanism,
|
||||
but a handshake via redirects is necessary to complete the authentication with JupyterHub.
|
||||
This can be useful when the user is "already logged in" via some mechanism.
|
||||
However, a handshake via `redirects` is necessary to complete the authentication with JupyterHub.
|
||||
|
||||
## `/hub/logout`
|
||||
|
||||
Visiting `/hub/logout` clears cookies from the current browser.
|
||||
Visiting `/hub/logout` clears [cookies](https://en.wikipedia.org/wiki/HTTP_cookie) from the current browser.
|
||||
Note that **logging out does not stop a user's server(s)** by default.
|
||||
|
||||
If you would like to shutdown user servers on logout,
|
||||
If you would like to shut down user servers on logout,
|
||||
you can enable this behavior with:
|
||||
|
||||
```python
|
||||
@@ -105,8 +105,8 @@ does not mean the user is no longer actively using their server from another mac
|
||||
## `/user/:username[/:servername]`
|
||||
|
||||
If a user's server is running, this URL is handled by the user's given server,
|
||||
not the Hub.
|
||||
The username is the first part and, if using named servers,
|
||||
not by the Hub.
|
||||
The username is the first part, and if using named servers,
|
||||
the server name is the second part.
|
||||
|
||||
If the user's server is _not_ running, this will be redirected to `/hub/user/:username/...`
|
||||
@@ -120,11 +120,11 @@ if the specified server were running).
|
||||
Handling this URL is the most complicated condition in JupyterHub,
|
||||
because there can be many states:
|
||||
|
||||
1. server is not active
|
||||
1. the server is not active
|
||||
a. user matches
|
||||
b. user doesn't match
|
||||
2. server is ready
|
||||
3. server is pending, but not ready
|
||||
2. the server is ready
|
||||
3. the server is pending, but not ready
|
||||
|
||||
If the server is pending spawn,
|
||||
the browser will be redirected to `/hub/spawn-pending/:username/:servername`
|
||||
@@ -140,39 +140,37 @@ Some checks are performed and a delay is added before redirecting back to `/user
|
||||
If something is really wrong, this can result in a redirect loop.
|
||||
|
||||
Visiting this page will never result in triggering the spawn of servers
|
||||
without additional user action (i.e. clicking the link on the page)
|
||||
without additional user action (i.e. clicking the link on the page).
|
||||
|
||||

|
||||
|
||||
_Version changed: 1.0_
|
||||
|
||||
Prior to 1.0, this URL itself was responsible for spawning servers,
|
||||
and served the progress page if it was pending,
|
||||
redirected to running servers, and
|
||||
This was useful because it made sure that requested servers were restarted after they stopped,
|
||||
but could also be harmful because unused servers would continuously be restarted if e.g.
|
||||
an idle JupyterLab frontend were open pointed at it,
|
||||
which constantly makes polling requests.
|
||||
Prior to 1.0, this URL itself was responsible for spawning servers.
|
||||
If the progress page was pending, the URL redirected it to running servers.
|
||||
This was useful because it made sure that the requested servers were restarted after they stopped.
|
||||
However, it could also be harmful because unused servers would continuously be restarted if e.g.
|
||||
an idle JupyterLab frontend that constantly makes polling requests was openly pointed at it.
|
||||
|
||||
### Special handling of API requests
|
||||
|
||||
Requests to `/user/:username[/:servername]/api/...` are assumed to be
|
||||
from applications connected to stopped servers.
|
||||
These are failed with 503 and an informative JSON error message
|
||||
indicating how to spawn the server.
|
||||
This is meant to help applications such as JupyterLab
|
||||
These requests fail with a `503` status code and an informative JSON error message
|
||||
that indicates how to spawn the server.
|
||||
This is meant to help applications such as JupyterLab,
|
||||
that are connected to a server that has stopped.
|
||||
|
||||
_Version changed: 1.0_
|
||||
|
||||
JupyterHub 0.9 failed these API requests with status 404,
|
||||
but 1.0 uses 503.
|
||||
JupyterHub version 0.9 failed these API requests with status `404`,
|
||||
but version 1.0 uses 503.
|
||||
|
||||
## `/user-redirect/...`
|
||||
|
||||
This URL is for sharing a URL that will redirect a user
|
||||
The `/user-redirect/...` URL is for sharing a URL that will redirect a user
|
||||
to a path on their own default server.
|
||||
This is useful when users have the same file at the same URL on their servers,
|
||||
This is useful when different users have the same file at the same URL on their servers,
|
||||
and you want a single link to give to any user that will open that file on their server.
|
||||
|
||||
e.g. a link to `/user-redirect/notebooks/Index.ipynb`
|
||||
@@ -194,7 +192,7 @@ that is intended to make it possible.
|
||||
### `/hub/spawn[/:username[/:servername]]`
|
||||
|
||||
Requesting `/hub/spawn` will spawn the default server for the current user.
|
||||
If `username` and optionally `servername` are specified,
|
||||
If the `username` and optionally `servername` are specified,
|
||||
then the specified server for the specified user will be spawned.
|
||||
Once spawn has been requested,
|
||||
the browser is redirected to `/hub/spawn-pending/...`.
|
||||
@@ -207,7 +205,7 @@ and a POST request will trigger the actual spawn and redirect.
|
||||
|
||||
_Version added: 1.0_
|
||||
|
||||
1.0 adds the ability to specify username and servername.
|
||||
1.0 adds the ability to specify `username` and `servername`.
|
||||
Prior to 1.0, only `/hub/spawn` was recognized for the default server.
|
||||
|
||||
_Version changed: 1.0_
|
||||
@@ -247,7 +245,7 @@ against the [JupyterHub REST API](./rest.md).
|
||||
|
||||
Administrators can take various administrative actions from this page:
|
||||
|
||||
1. add/remove users
|
||||
2. grant admin privileges
|
||||
3. start/stop user servers
|
||||
4. shutdown JupyterHub itself
|
||||
- add/remove users
|
||||
- grant admin privileges
|
||||
- start/stop user servers
|
||||
- shutdown JupyterHub itself
|
||||
|
@@ -56,7 +56,7 @@ If any additional services are run on the same domain as the Hub, the services
|
||||
|
||||
## Mitigate security issues
|
||||
|
||||
Several approaches to mitigating these issues with configuration
|
||||
The several approaches to mitigating security issues with configuration
|
||||
options provided by JupyterHub include:
|
||||
|
||||
### Enable subdomains
|
||||
@@ -74,10 +74,10 @@ resolves the cross-site issues.
|
||||
|
||||
### Disable user config
|
||||
|
||||
If subdomains are not available or not desirable, JupyterHub provides the
|
||||
If subdomains are unavailable or undesirable, JupyterHub provides a
|
||||
configuration option `Spawner.disable_user_config`, which can be set to prevent
|
||||
the user-owned configuration files from being loaded. After implementing this
|
||||
option, PATHs and package installation and PATHs are the other things that the
|
||||
option, `PATH`s and package installation are the other things that the
|
||||
admin must enforce.
|
||||
|
||||
### Prevent spawners from evaluating shell configuration files
|
||||
@@ -115,9 +115,9 @@ extend to securing the `tcp` sockets as well.
|
||||
|
||||
## Security audits
|
||||
|
||||
We recommend that you do periodic reviews of your deployment's security. It is
|
||||
good practice to keep JupyterHub, configurable-http-proxy, and nodejs
|
||||
versions up to date.
|
||||
We recommend that you do periodic reviews of your deployment's security. It's
|
||||
good practice to keep [JupyterHub](https://readthedocs.org/projects/jupyterhub/), [configurable-http-proxy][], and [nodejs
|
||||
versions](https://github.com/nodejs/Release) up to date.
|
||||
|
||||
A handy website for testing your deployment is
|
||||
[Qualsys' SSL analyzer tool](https://www.ssllabs.com/ssltest/analyze.html).
|
||||
|
Reference in New Issue
Block a user