mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 07:23:00 +00:00
forward-port 4.1.0
This commit is contained in:
@@ -16,7 +16,8 @@ works.
|
||||
|
||||
JupyterHub is designed to be a _simple multi-user server for modestly sized
|
||||
groups_ of **semi-trusted** users. While the design reflects serving
|
||||
semi-trusted users, JupyterHub can also be suitable for serving **untrusted** users.
|
||||
semi-trusted users, JupyterHub can also be suitable for serving **untrusted** users,
|
||||
but **is not suitable for untrusted users** in its default configuration.
|
||||
|
||||
As a result, using JupyterHub with **untrusted** users means more work by the
|
||||
administrator, since much care is required to secure a Hub, with extra caution on
|
||||
@@ -56,30 +57,63 @@ ensure that:
|
||||
|
||||
If any additional services are run on the same domain as the Hub, the services
|
||||
**must never** display user-authored HTML that is neither _sanitized_ nor _sandboxed_
|
||||
(e.g. IFramed) to any user that lacks authentication as the author of a file.
|
||||
to any user that lacks authentication as the author of a file.
|
||||
|
||||
### Sharing access to servers
|
||||
|
||||
Because sharing access to servers (via `access:servers` scopes or the sharing feature in JupyterHub 5) by definition means users can serve each other files, enabling sharing is not suitable for untrusted users without also enabling per-user domains.
|
||||
|
||||
JupyterHub does not enable any sharing by default.
|
||||
|
||||
## Mitigate security issues
|
||||
|
||||
The several approaches to mitigating security issues with configuration
|
||||
options provided by JupyterHub include:
|
||||
|
||||
### Enable subdomains
|
||||
### Enable user subdomains
|
||||
|
||||
JupyterHub provides the ability to run single-user servers on their own
|
||||
subdomains. This means the cross-origin protections between servers has the
|
||||
desired effect, and user servers and the Hub are protected from each other. A
|
||||
user's single-user server will be at `username.jupyter.mydomain.com`. This also
|
||||
requires all user subdomains to point to the same address, which is most easily
|
||||
accomplished with wildcard DNS. Since this spreads the service across multiple
|
||||
domains, you will need wildcard SSL as well. Unfortunately, for many
|
||||
institutional domains, wildcard DNS and SSL are not available. **If you do plan
|
||||
to serve untrusted users, enabling subdomains is highly encouraged**, as it
|
||||
resolves the cross-site issues.
|
||||
domains. This means the cross-origin protections between servers has the
|
||||
desired effect, and user servers and the Hub are protected from each other.
|
||||
|
||||
**Subdomains are the only way to reliably isolate user servers from each other.**
|
||||
|
||||
To enable subdomains, set:
|
||||
|
||||
```python
|
||||
c.JupyterHub.subdomain_host = "https://jupyter.example.org"
|
||||
```
|
||||
|
||||
When subdomains are enabled, each user's single-user server will be at e.g. `https://username.jupyter.example.org`.
|
||||
This also requires all user subdomains to point to the same address,
|
||||
which is most easily accomplished with wildcard DNS, where a single A record points to your server and a wildcard CNAME record points to your A record:
|
||||
|
||||
```
|
||||
A jupyter.example.org 192.168.1.123
|
||||
CNAME *.jupyter.example.org jupyter.example.org
|
||||
```
|
||||
|
||||
Since this spreads the service across multiple domains, you will likely need wildcard SSL as well,
|
||||
matching `*.jupyter.example.org`.
|
||||
|
||||
Unfortunately, for many institutional domains, wildcard DNS and SSL may not be available.
|
||||
|
||||
We also **strongly encourage** serving JupyterHub and user content on a domain that is _not_ a subdomain of any sensitive content.
|
||||
For reasoning, see [GitHub's discussion of moving user content to github.io from \*.github.com](https://github.blog/2013-04-09-yummy-cookies-across-domains/).
|
||||
|
||||
**If you do plan to serve untrusted users, enabling subdomains is highly encouraged**,
|
||||
as it resolves many security issues, which are difficult to unavoidable when JupyterHub is on a single-domain.
|
||||
|
||||
:::{important}
|
||||
JupyterHub makes no guarantees about protecting users from each other unless subdomains are enabled.
|
||||
|
||||
If you want to protect users from each other, you **_must_** enable per-user domains.
|
||||
:::
|
||||
|
||||
### Disable user config
|
||||
|
||||
If subdomains are unavailable or undesirable, JupyterHub provides a
|
||||
configuration option `Spawner.disable_user_config`, which can be set to prevent
|
||||
configuration option `Spawner.disable_user_config = True`, which can be set to prevent
|
||||
the user-owned configuration files from being loaded. After implementing this
|
||||
option, `PATH`s and package installation are the other things that the
|
||||
admin must enforce.
|
||||
@@ -89,23 +123,24 @@ admin must enforce.
|
||||
For most Spawners, `PATH` is not something users can influence, but it's important that
|
||||
the Spawner should _not_ evaluate shell configuration files prior to launching the server.
|
||||
|
||||
### Isolate packages using virtualenv
|
||||
### Isolate packages in a read-only environment
|
||||
|
||||
Package isolation is most easily handled by running the single-user server in
|
||||
a virtualenv with disabled system-site-packages. The user should not have
|
||||
permission to install packages into this environment.
|
||||
The user must not have permission to install packages into the environment where the singleuser-server runs.
|
||||
On a shared system, package isolation is most easily handled by running the single-user server in
|
||||
a root-owned virtualenv with disabled system-site-packages.
|
||||
The user must not have permission to install packages into this environment.
|
||||
The same principle extends to the images used by container-based deployments.
|
||||
If users can select the images in which their servers run, they can disable all security.
|
||||
If users can select the images in which their servers run, they can disable all security for their own servers.
|
||||
|
||||
It is important to note that the control over the environment only affects the
|
||||
single-user server, and not the environment(s) in which the user's kernel(s)
|
||||
It is important to note that the control over the environment is only required for the
|
||||
single-user server, and not the environment(s) in which the users' kernel(s)
|
||||
may run. Installing additional packages in the kernel environment does not
|
||||
pose additional risk to the web application's security.
|
||||
|
||||
### Encrypt internal connections with SSL/TLS
|
||||
|
||||
By default, all communications on the server, between the proxy, hub, and single
|
||||
-user notebooks are performed unencrypted. Setting the `internal_ssl` flag in
|
||||
By default, all communications within JupyterHub—between the proxy, hub, and single
|
||||
-user notebooks—are performed unencrypted. Setting the `internal_ssl` flag in
|
||||
`jupyterhub_config.py` secures the aforementioned routes. Turning this
|
||||
feature on does require that the enabled `Spawner` can use the certificates
|
||||
generated by the `Hub` (the default `LocalProcessSpawner` can, for instance).
|
||||
@@ -119,6 +154,104 @@ Unix permissions to the communication sockets thereby restricting
|
||||
communication to the socket owner. The `internal_ssl` option will eventually
|
||||
extend to securing the `tcp` sockets as well.
|
||||
|
||||
### Mitigating same-origin deployments
|
||||
|
||||
While per-user domains are **required** for robust protection of users from each other,
|
||||
you can mitigate many (but not all) cross-user issues.
|
||||
First, it is critical that users cannot modify their server environments, as described above.
|
||||
Second, it is important that users do not have `access:servers` permission to any server other than their own.
|
||||
|
||||
If users can access each others' servers, additional security measures must be enabled, some of which come with distinct user-experience costs.
|
||||
|
||||
Without the [Same-Origin Policy] (SOP) protecting user servers from each other,
|
||||
each user server is considered a trusted origin for requests to each other user server (and the Hub itself).
|
||||
Servers _cannot_ meaningfully distinguish requests originating from other user servers,
|
||||
because SOP implies a great deal of trust, losing many restrictions applied to cross-origin requests.
|
||||
|
||||
That means pages served from each user server can:
|
||||
|
||||
1. arbitrarily modify the path in the Referer
|
||||
2. make fully authorized requests with cookies
|
||||
3. access full page contents served from the hub or other servers via popups
|
||||
|
||||
JupyterHub uses distinct xsrf tokens stored in cookies on each server path to attempt to limit requests across.
|
||||
This has limitations because not all requests are protected by these XSRF tokens,
|
||||
and unless additional measures are taken, the XSRF tokens from other user prefixes may be retrieved.
|
||||
|
||||
[Same-Origin Policy]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
|
||||
|
||||
For example:
|
||||
|
||||
- `Content-Security-Policy` header must prohibit popups and iframes from the same origin.
|
||||
The following Content-Security-Policy rules are _insecure_ and readily enable users to access each others' servers:
|
||||
|
||||
- `frame-ancestors: 'self'`
|
||||
- `frame-ancestors: '*'`
|
||||
- `sandbox allow-popups`
|
||||
|
||||
- Ideally, pages should use the strictest `Content-Security-Policy: sandbox` available,
|
||||
but this is not feasible in general for JupyterLab pages, which need at least `sandbox allow-same-origin allow-scripts` to work.
|
||||
|
||||
The default Content-Security-Policy for single-user servers is
|
||||
|
||||
```
|
||||
frame-ancestors: 'none'
|
||||
```
|
||||
|
||||
which prohibits iframe embedding, but not pop-ups.
|
||||
|
||||
A more secure Content-Security-Policy that has some costs to user experience is:
|
||||
|
||||
```
|
||||
frame-ancestors: 'none'; sandbox allow-same-origin allow-scripts
|
||||
```
|
||||
|
||||
`allow-popups` is not disabled by default because disabling it breaks legitimate functionality, like "Open this in a new tab", and the "JupyterHub Control Panel" menu item.
|
||||
To reiterate, the right way to avoid these issues is to enable per-user domains, where none of these concerns come up.
|
||||
|
||||
Note: even this level of protection requires administrators maintaining full control over the user server environment.
|
||||
If users can modify their server environment, these methods are ineffective, as users can readily disable them.
|
||||
|
||||
### Cookie tossing
|
||||
|
||||
Cookie tossing is a technique where another server on a subdomain or peer subdomain can set a cookie
|
||||
which will be read on another domain.
|
||||
This is not relevant unless there are other user-controlled servers on a peer domain.
|
||||
|
||||
"Domain-locked" cookies avoid this issue, but have their own restrictions:
|
||||
|
||||
- JupyterHub must be served over HTTPS
|
||||
- All secure cookies must be set on `/`, not on sub-paths, which means they are shared by all JupyterHub components in a single-domain deployment.
|
||||
|
||||
As a result, this option is only recommended when per-user subdomains are enabled,
|
||||
to prevent sending all jupyterhub cookies to all user servers.
|
||||
|
||||
To enable domain-locked cookies, set:
|
||||
|
||||
```python
|
||||
c.JupyterHub.cookie_host_prefix_enabled = True
|
||||
```
|
||||
|
||||
```{versionadded} 4.1
|
||||
|
||||
```
|
||||
|
||||
### Forced-login
|
||||
|
||||
Jupyter servers can share links with `?token=...`.
|
||||
JupyterHub prior to 5.0 will accept this request and persist the token for future requests.
|
||||
This is useful for enabling admins to create 'fully authenticated' links bypassing login.
|
||||
However, it also means users can share their own links that will log other users into their own servers,
|
||||
enabling them to serve each other notebooks and other arbitrary HTML, depending on server configuration.
|
||||
|
||||
```{versionadded} 4.1
|
||||
Setting environment variable `JUPYTERHUB_ALLOW_TOKEN_IN_URL=0` in the single-user environment can opt out of accepting token auth in URL parameters.
|
||||
```
|
||||
|
||||
```{versionadded} 5.0
|
||||
Accepting tokens in URLs is disabled by default, and `JUPYTERHUB_ALLOW_TOKEN_IN_URL=1` environment variable must be set to _allow_ token auth in URL parameters.
|
||||
```
|
||||
|
||||
## Security audits
|
||||
|
||||
We recommend that you do periodic reviews of your deployment's security. It's
|
||||
|
@@ -8,6 +8,57 @@ command line for details.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 4.1
|
||||
|
||||
### 4.1.0 - 2024-03
|
||||
|
||||
JupyterHub 4.1 is a security release, fixing [CVE-2024-28233].
|
||||
All JupyterHub deployments are encouraged to upgrade,
|
||||
especially those with other user content on peer domains to JupyterHub.
|
||||
|
||||
As always, JupyterHub deployments are especially encouraged to enable per-user domains if protecting users from each other is a concern.
|
||||
|
||||
For more information on securely deploying JupyterHub, see the [web security documentation](web-security).
|
||||
|
||||
[CVE-2024-28233]: https://github.com/jupyterhub/jupyterhub/security/advisories/GHSA-7r3h-4ph8-w38g
|
||||
|
||||
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/4.0.2...4.1.0))
|
||||
|
||||
#### Enhancements made
|
||||
|
||||
- Backport PR #4628 on branch 4.x (Include LDAP groups in local spawner gids) [#4735](https://github.com/jupyterhub/jupyterhub/pull/4735) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4561 on branch 4.x (Improve debugging when waiting for servers) [#4714](https://github.com/jupyterhub/jupyterhub/pull/4714) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4563 on branch 4.x (only set 'domain' field on session-id cookie) [#4707](https://github.com/jupyterhub/jupyterhub/pull/4707) ([@minrk](https://github.com/minrk))
|
||||
|
||||
#### Bugs fixed
|
||||
|
||||
- Backport PR #4733 on branch 4.x (Catch ValueError while waiting for server to be reachable) [#4734](https://github.com/jupyterhub/jupyterhub/pull/4734) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4679 on branch 4.x (Unescape jinja username) [#4705](https://github.com/jupyterhub/jupyterhub/pull/4705) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4630: avoid setting unused oauth state cookies on API requests [#4697](https://github.com/jupyterhub/jupyterhub/pull/4697) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4632: simplify, avoid errors in parsing accept headers [#4696](https://github.com/jupyterhub/jupyterhub/pull/4696) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4677 on branch 4.x (Improve validation, docs for token.expires_in) [#4692](https://github.com/jupyterhub/jupyterhub/pull/4692) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4570 on branch 4.x (fix mutation of frozenset in scope intersection) [#4691](https://github.com/jupyterhub/jupyterhub/pull/4691) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4562 on branch 4.x (Use `user.stop` to cleanup spawners that stopped while Hub was down) [#4690](https://github.com/jupyterhub/jupyterhub/pull/4690) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4542 on branch 4.x (Fix include_stopped_servers in paginated next_url) [#4689](https://github.com/jupyterhub/jupyterhub/pull/4689) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4651 on branch 4.x (avoid attempting to patch removed IPythonHandler with notebook v7) [#4688](https://github.com/jupyterhub/jupyterhub/pull/4688) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4560 on branch 4.x (singleuser extension: persist token from ?token=... url in cookie) [#4687](https://github.com/jupyterhub/jupyterhub/pull/4687) ([@minrk](https://github.com/minrk))
|
||||
|
||||
#### Maintenance and upkeep improvements
|
||||
|
||||
- Backport quay.io publishing [#4698](https://github.com/jupyterhub/jupyterhub/pull/4698) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4617: try to improve reliability of test_external_proxy [#4695](https://github.com/jupyterhub/jupyterhub/pull/4695) ([@minrk](https://github.com/minrk))
|
||||
- Backport PR #4618 on branch 4.x (browser test: wait for token request to finish before reloading) [#4694](https://github.com/jupyterhub/jupyterhub/pull/4694) ([@minrk](https://github.com/minrk))
|
||||
- preparing 4.x branch [#4685](https://github.com/jupyterhub/jupyterhub/pull/4685) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Contributors to this release
|
||||
|
||||
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
||||
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
||||
|
||||
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2023-08-10&to=2024-03-19&type=c))
|
||||
|
||||
@Achele ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AAchele+updated%3A2023-08-10..2024-03-19&type=Issues)) | @akashthedeveloper ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aakashthedeveloper+updated%3A2023-08-10..2024-03-19&type=Issues)) | @balajialg ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abalajialg+updated%3A2023-08-10..2024-03-19&type=Issues)) | @BhavyaT-135 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ABhavyaT-135+updated%3A2023-08-10..2024-03-19&type=Issues)) | @blink1073 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ablink1073+updated%3A2023-08-10..2024-03-19&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2023-08-10..2024-03-19&type=Issues)) | @fcollonval ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Afcollonval+updated%3A2023-08-10..2024-03-19&type=Issues)) | @I-Am-D-B ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AI-Am-D-B+updated%3A2023-08-10..2024-03-19&type=Issues)) | @jakirkham ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajakirkham+updated%3A2023-08-10..2024-03-19&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2023-08-10..2024-03-19&type=Issues)) | @kzgrzendek ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akzgrzendek+updated%3A2023-08-10..2024-03-19&type=Issues)) | @lumberbot-app ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Alumberbot-app+updated%3A2023-08-10..2024-03-19&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2023-08-10..2024-03-19&type=Issues)) | @mbiette ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ambiette+updated%3A2023-08-10..2024-03-19&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2023-08-10..2024-03-19&type=Issues)) | @rcthomas ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arcthomas+updated%3A2023-08-10..2024-03-19&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2023-08-10..2024-03-19&type=Issues)) | @sgaist ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgaist+updated%3A2023-08-10..2024-03-19&type=Issues)) | @shubham0473 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashubham0473+updated%3A2023-08-10..2024-03-19&type=Issues)) | @Temidayo32 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATemidayo32+updated%3A2023-08-10..2024-03-19&type=Issues)) | @willingc ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awillingc+updated%3A2023-08-10..2024-03-19&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2023-08-10..2024-03-19&type=Issues))
|
||||
|
||||
## 4.0
|
||||
|
||||
### 4.0.2 - 2023-08-10
|
||||
|
Reference in New Issue
Block a user