mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 10:34:10 +00:00

While doing https://github.com/jupyterhub/jupyterhub/pull/2726, I realized we don't have a consistent way to format references inside the docs. I now have them be formatted to match the name of the file, but using `:` to separate them instead of `/` or `-`. `/` makes it ambiguous when using with markdown link syntax, as it could be a reference or a file. And using `-` is ambiguous, as that can be the name of the file itself. This PR does about half, I can do the other half later (unless someone else does).
237 lines
8.3 KiB
Markdown
237 lines
8.3 KiB
Markdown
(security-basics)=
|
||
|
||
# Security settings
|
||
|
||
:::{important}
|
||
You should not run JupyterHub without SSL encryption on a public network.
|
||
:::
|
||
|
||
Security is the most important aspect of configuring Jupyter.
|
||
Three (3) configuration settings are the main aspects of security configuration:
|
||
|
||
1. {ref}`SSL encryption <ssl-encryption>` (to enable HTTPS)
|
||
2. {ref}`Cookie secret <cookie-secret>` (a key for encrypting browser cookies)
|
||
3. Proxy {ref}`authentication token <authentication-token>` (used for the Hub and
|
||
other services to authenticate to the Proxy)
|
||
|
||
The Hub hashes all secrets (e.g. auth tokens) before storing them in its
|
||
database. A loss of control over read-access to the database should have
|
||
minimal impact on your deployment. If your database has been compromised, it
|
||
is still a good idea to revoke existing tokens.
|
||
|
||
(ssl-encryption)=
|
||
|
||
## Enabling SSL encryption
|
||
|
||
Since JupyterHub includes authentication and allows arbitrary code execution,
|
||
you should not run it without SSL (HTTPS).
|
||
|
||
### Using an SSL certificate
|
||
|
||
This will require you to obtain an official, trusted SSL certificate or create a
|
||
self-signed certificate. Once you have obtained and installed a key and
|
||
certificate, you need to specify their locations in the `jupyterhub_config.py`
|
||
configuration file as follows:
|
||
|
||
```python
|
||
c.JupyterHub.ssl_key = '/path/to/my.key'
|
||
c.JupyterHub.ssl_cert = '/path/to/my.cert'
|
||
```
|
||
|
||
Some cert files also contain the key, in which case only the cert is needed. It
|
||
is important that these files be put in a secure location on your server, where
|
||
they are not readable by regular users.
|
||
|
||
If you are using a **chain certificate**, see also chained certificate for SSL
|
||
in the JupyterHub [Troubleshooting FAQ](faq:troubleshooting).
|
||
|
||
### Using letsencrypt
|
||
|
||
It is also possible to use [letsencrypt](https://letsencrypt.org/) to obtain
|
||
a free, trusted SSL certificate. If you run letsencrypt using the default
|
||
options, the needed configuration is (replace `mydomain.tld` by your fully
|
||
qualified domain name):
|
||
|
||
```python
|
||
c.JupyterHub.ssl_key = '/etc/letsencrypt/live/{mydomain.tld}/privkey.pem'
|
||
c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/{mydomain.tld}/fullchain.pem'
|
||
```
|
||
|
||
If the fully qualified domain name (FQDN) is `example.com`, the following
|
||
would be the needed configuration:
|
||
|
||
```python
|
||
c.JupyterHub.ssl_key = '/etc/letsencrypt/live/example.com/privkey.pem'
|
||
c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/example.com/fullchain.pem'
|
||
```
|
||
|
||
### If SSL termination happens outside of the Hub
|
||
|
||
In certain cases, for example, if the hub is running behind a reverse proxy, and
|
||
[SSL termination is being provided by NGINX](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/),
|
||
it is reasonable to run the hub without SSL.
|
||
|
||
To achieve this, remove `c.JupyterHub.ssl_key` and `c.JupyterHub.ssl_cert`
|
||
from your configuration (setting them to `None` or an empty string does not
|
||
have the same effect, and will result in an error).
|
||
|
||
(authentication-token)=
|
||
|
||
## Proxy authentication token
|
||
|
||
The Hub authenticates its requests to the Proxy using a secret token that
|
||
the Hub and Proxy agree upon. Note that this applies to the default
|
||
`ConfigurableHTTPProxy` implementation. Not all proxy implementations
|
||
use an auth token.
|
||
|
||
The value of this token should be a random string (for example, generated by
|
||
`openssl rand -hex 32`). You can store it in the configuration file or an
|
||
environment variable.
|
||
|
||
### Generating and storing token in the configuration file
|
||
|
||
You can set the value in the configuration file, `jupyterhub_config.py`:
|
||
|
||
```python
|
||
c.ConfigurableHTTPProxy.api_token = 'abc123...' # any random string
|
||
```
|
||
|
||
### Generating and storing as an environment variable
|
||
|
||
You can pass this value of the proxy authentication token to the Hub and Proxy
|
||
using the `CONFIGPROXY_AUTH_TOKEN` environment variable:
|
||
|
||
```bash
|
||
export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
|
||
```
|
||
|
||
This environment variable needs to be visible to the Hub and Proxy.
|
||
|
||
### Default if token is not set
|
||
|
||
If you do not set the Proxy authentication token, the Hub will generate a random
|
||
key itself. This means that any time you restart the Hub, you **must also
|
||
restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
|
||
automatically (this is the default configuration).
|
||
|
||
(cookie-secret)=
|
||
|
||
## Cookie secret
|
||
|
||
The cookie secret is an encryption key, used to encrypt the browser cookies,
|
||
which are used for authentication. Three common methods are described for
|
||
generating and configuring the cookie secret.
|
||
|
||
### Generating and storing as a cookie secret file
|
||
|
||
The cookie secret should be 32 random bytes, encoded as hex, and is typically
|
||
stored in a `jupyterhub_cookie_secret` file. Below, is an example command to generate the
|
||
`jupyterhub_cookie_secret` file:
|
||
|
||
```bash
|
||
openssl rand -hex 32 > /srv/jupyterhub/jupyterhub_cookie_secret
|
||
```
|
||
|
||
In most deployments of JupyterHub, you should point this to a secure location on
|
||
the file system, such as `/srv/jupyterhub/jupyterhub_cookie_secret`.
|
||
|
||
The location of the `jupyterhub_cookie_secret` file can be specified in the
|
||
`jupyterhub_config.py` file as follows:
|
||
|
||
```python
|
||
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
|
||
```
|
||
|
||
If the cookie secret file doesn't exist when the Hub starts, a new cookie
|
||
secret is generated and stored in the file. The file must not be readable by
|
||
`group` or `other`, otherwise the server won't start. The recommended permissions
|
||
for the cookie secret file are `600` (owner-only rw).
|
||
|
||
### Generating and storing as an environment variable
|
||
|
||
If you would like to avoid the need for files, the value can be loaded in the
|
||
Hub process from the `JPY_COOKIE_SECRET` environment variable, which is a
|
||
hex-encoded string. You can set it this way:
|
||
|
||
```bash
|
||
export JPY_COOKIE_SECRET=$(openssl rand -hex 32)
|
||
```
|
||
|
||
For security reasons, this environment variable should only be visible to the
|
||
Hub. If you set it dynamically as above, all users will be logged out each time
|
||
the Hub starts.
|
||
|
||
### Generating and storing as a binary string
|
||
|
||
You can also set the cookie secret, as a binary string,
|
||
in the configuration file (`jupyterhub_config.py`) itself:
|
||
|
||
```python
|
||
c.JupyterHub.cookie_secret = bytes.fromhex('64 CHAR HEX STRING')
|
||
```
|
||
|
||
(cookies)=
|
||
|
||
## Cookies used by JupyterHub authentication
|
||
|
||
The following cookies are used by the Hub for handling user authentication.
|
||
|
||
This section was created based on this [post] from Discourse.
|
||
|
||
### jupyterhub-hub-login
|
||
|
||
This is the login token used when visiting Hub-served pages that are
|
||
protected by authentication, such as the main home, the spawn form, etc.
|
||
If this cookie is set, then the user is logged in.
|
||
|
||
Resetting the Hub cookie secret effectively revokes this cookie.
|
||
|
||
This cookie is restricted to the path `/hub/`.
|
||
|
||
### jupyterhub-user-\<username>
|
||
|
||
This is the cookie used for authenticating with a single-user server.
|
||
It is set by the single-user server, after OAuth with the Hub.
|
||
|
||
Effectively the same as `jupyterhub-hub-login`, but for the
|
||
single-user server instead of the Hub. It contains an OAuth access token,
|
||
which is checked with the Hub to authenticate the browser.
|
||
|
||
Each OAuth access token is associated with a session id (see `jupyterhub-session-id` section
|
||
below).
|
||
|
||
To avoid hitting the Hub on every request, the authentication response is cached.
|
||
The cache key is comprised of both the token and session id, to avoid a stale cache.
|
||
|
||
Resetting the Hub cookie secret effectively revokes this cookie.
|
||
|
||
This cookie is restricted to the path `/user/<username>`,
|
||
to ensure that only the user’s server receives it.
|
||
|
||
### jupyterhub-session-id
|
||
|
||
This is a random string, meaningless in itself, and the only cookie
|
||
shared by the Hub and single-user servers.
|
||
|
||
Its sole purpose is to coordinate the logout of the multiple OAuth cookies.
|
||
|
||
This cookie is set to `/` so all endpoints can receive it, clear it, etc.
|
||
|
||
### jupyterhub-user-\<username>-oauth-state
|
||
|
||
A short-lived cookie, used solely to store and validate OAuth state.
|
||
It is only set while OAuth between the single-user server and the Hub
|
||
is processing.
|
||
|
||
If you use your browser development tools, you should see this cookie
|
||
for a very brief moment before you are logged in,
|
||
with an expiration date shorter than `jupyterhub-hub-login` or
|
||
`jupyterhub-user-<username>`.
|
||
|
||
This cookie should not exist after you have successfully logged in.
|
||
|
||
This cookie is restricted to the path `/user/<username>`, so that only
|
||
the user’s server receives it.
|
||
|
||
[post]: https://discourse.jupyter.org/t/how-to-force-re-login-for-users/1998/6
|