Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating native-proxy #501

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d0be294
Merge jhsingle-native-proxy.
jwindgassen Sep 10, 2024
26059b0
Rename to `standalone`
jwindgassen Sep 11, 2024
fae833b
Remove `forward-user-info`, `query-user-info` and remains of `present…
jwindgassen Sep 12, 2024
36c8e17
Create StandaloneHubProxyHandler and timeout Argument. Fix authentica…
jwindgassen Sep 13, 2024
087c11d
Send Activity Notifications to JupyterHub
jwindgassen Sep 13, 2024
0d4e701
Fix Authentication with JupyterHub
jwindgassen Sep 18, 2024
fa8621f
Remove jupyter-server Authentication
jwindgassen Dec 6, 2024
5cae144
Set env & mappath via CLI, add Args for Unix Sockets
jwindgassen Dec 6, 2024
6b9336d
Merge `StandaloneProxyHandler` into `StandaloneHubProxyHandler`, extr…
jwindgassen Dec 6, 2024
51ee31c
Fix SSL Configuration, Add SlashHandler to Application
jwindgassen Oct 2, 2024
f547e5f
Add Slash to Prefix
jwindgassen Oct 7, 2024
2a83343
Fix error generation
jwindgassen Oct 11, 2024
325217b
Fixed ordering in handlers
jwindgassen Oct 15, 2024
7f85c9f
Defer xsrf checking to proxied app
jwindgassen Oct 18, 2024
87efc56
Add Documentation for standalone
jwindgassen Nov 12, 2024
7b33d45
Add Tests for the StandaloneProxy
jwindgassen Nov 15, 2024
941356f
Fix Typos and minor cleanups
jwindgassen Dec 3, 2024
0228e86
Switch from argparse to traitlets.Application and use config.ServerPr…
jwindgassen Dec 6, 2024
7ed8974
Refactor and reuse Proxy generation in standalone
jwindgassen Dec 7, 2024
1ff6051
Update standalone tests
jwindgassen Dec 7, 2024
e07a615
Allow configuration via traitlets
jwindgassen Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix Typos and minor cleanups
jwindgassen committed Dec 6, 2024
commit 941356f9761ce549a7acf1a4b4a54e419189e5e9
45 changes: 22 additions & 23 deletions docs/source/standalone.md
Original file line number Diff line number Diff line change
@@ -3,11 +3,11 @@
# Spawning and proxying a web service from JupyterHub

The `standalone` feature of Jupyter Server Proxy enables JupyterHub Admins to launch and proxy arbitrary web services
directly, in place of the JupyterLab or Notebook. You can use Jupyter Server Proxy to spawn a single proxy,
directly, instead of JupyterLab or Notebook. You can use Jupyter Server Proxy to spawn a single proxy,
without it being attached to a Jupyter server. The proxy securely authenticates and restricts access to authorized
users through JupyterHub, giving a unified way to securely provide arbitrary applications.
users through JupyterHub, providing a unified way to access arbitrary applications securely.

This works similar to {ref}`proxying Server Processes <server-process>`, where a server process is started and proxied.
This works similarly to {ref}`proxying Server Processes <server-process>`, where a server process is started and proxied.
The Proxy is usually started from the command line, often by modifying the `Spawner.cmd` in your
[JupyterHub Configuration](https://jupyterhub.readthedocs.io/en/stable/tutorial/getting-started/spawners-basics.html).

@@ -16,7 +16,7 @@ This feature builds upon the work of [Dan Lester](https://github.com/danlester),

## Installation

This feature has a dependency to JupyterHub and must be explicitly installed via an optional dependency:
This feature has a dependency on JupyterHub and must be explicitly installed via an optional dependency:

```shell
pip install jupyter-server-proxy[standalone]
@@ -32,17 +32,17 @@ The standalone proxy is controlled with the `jupyter standaloneproxy` command. Y
jupyter standaloneproxy -- voila --no-browser --port={port} /path/to/some/Notebook.ipynb
```

Executing this command will spawn a new HTTP Server, which will spawn the voilà dashboard and render the notebook.
Executing this command will spawn a new HTTP Server, creating the voilà dashboard and rendering the notebook.
Any template strings (like the `--port={port}`) inside the command will be automatically replaced when the command is
executed.

The CLI has multiple advanced options to customize the behavior of the proxy. Execute `jupyter standaloneproxy --help`
The CLI has multiple advanced options to customize the proxy behavior. Execute `jupyter standaloneproxy --help`
to get a complete list of all arguments.

### Specify address and port
### Specify the address and port

The proxy will try to extract the address and port from the `JUPYTERHUB_SERVICE_URL` environment variable, which is
set if an application is launched by JupyterHub. Otherwise, it will be launched on `127.0.0.1:8888`.
The proxy will try to extract the address and port from the `JUPYTERHUB_SERVICE_URL` environment variable. This variable
will be set by JupyterHub. Otherwise, the server will be launched on `127.0.0.1:8888`.
You can also explicitly overwrite these values:

```shell
@@ -52,7 +52,7 @@ jupyter standaloneproxy --address=localhost --port=8000 ...
### Disable Authentication

For testing, it can be useful to disable the authentication with JupyterHub. Passing `--skip-authentication` will
not triggering the login process when accessing the application.
not trigger the login process when accessing the application.

```{warning} Disabling authentication will leave the application open to anyone! Be careful with it,
especially on multi-user systems.
@@ -61,15 +61,15 @@ especially on multi-user systems.
## Usage with JupyterHub

To launch a standalone proxy with JupyterHub, you need to customize the `Spawner` inside the configuration
using traitlets:
using `traitlets`:

```python
c.Spawner.cmd = "jupyter-standaloneproxy"
c.Spawner.args = ["--", "voila", "--no-browser", "--port={port}", "/path/to/some/Notebook.ipynb"]
```

This will hard-code JupyterHub to launch voilà instead of `jupyterhub-singleuser`. In case you want to give the users
of the JupyterHub the ability to select which application to launch (like selecting either JupyterLab or voilà),
of JupyterHub the ability to select which application to launch (like selecting either JupyterLab or voilà),
you will want to make this configuration optional:

```python
@@ -108,15 +108,14 @@ This executable is usually a wrapper around the `JupyterLab` or `Notebook` appli
additions regarding authentication and multi-user systems.
In the standalone feature, we try to mimic these additions, but instead of using `JupyterLab` or `Notebook`, we
will wrap them around an arbitrary web application.
This will ensure only authenticated access to the application, while providing direct access to the application
without needing a Jupyter server to be running in the background.
The different additions will be discussed in more detail below.
This will ensure direct, authenticated access to the application, without needing a Jupyter server to be running
in the background. The different additions will be discussed in more detail below.

### Structure

The standalone feature is built on top of the `SuperviseAndProxyhandler`, which will spawn a process and proxy
requests to this server. While this process is called _Server_ in the documentation, I will call it _Application_
here, to avoid confusion with the other server where the `SuperviseAndProxyhandler` is attached to.
requests to this server. While this process is called _Server_ in the documentation, the term _Application_ will be
used here, to avoid confusion with the other server where the `SuperviseAndProxyhandler` is attached to.
When using jupyter-server-proxy, the proxies are attached to the Jupyter server and will proxy requests
to the application.
Since we do not want to use the Jupyter server here, we instead require an alternative server, which will be used
@@ -127,9 +126,9 @@ For that, we use tornado `HTTPServer`.

One central component is the authentication with the JupyterHub Server.
Any client accessing the application will need to authenticate with the JupyterHub API, which will ensure only
the user themselves (or otherwise allowed users, e.g., admins) can access the application.
users themselves (or otherwise allowed users, e.g., admins) can access the application.
The Login process is started by deriving our `StandaloneProxyHandler` from
[jupyterub.services.auth.HubOAuthenticated](https://github.com/jupyterhub/jupyterhub/blob/5.0.0/jupyterhub/services/auth.py#L1541)
[jupyterhub.services.auth.HubOAuthenticated](https://github.com/jupyterhub/jupyterhub/blob/5.0.0/jupyterhub/services/auth.py#L1541)
and decorating any methods we want to authenticate with `tornado.web.authenticated`.
For the proxy, we just decorate the `proxy` method with `web.authenticated`, which will authenticate all routes on all HTTP Methods.
`HubOAuthenticated` will automatically provide the login URL for the authentication process and any
@@ -140,21 +139,21 @@ This redirect will be received on the `/oauth_callback` path, from where we need
root of the application.
We use the [HubOAuthCallbackHander](https://github.com/jupyterhub/jupyterhub/blob/5.0.0/jupyterhub/services/auth.py#L1547),
another handler from the JupyterHub package, for this.
It will also cache the received OAuth state from the login, so that we can skip authentication for the next requests
It will also cache the received OAuth state from the login so that we can skip authentication for the next requests
and do not need to go through the whole login process for each request.

### SSL certificates

In some JupyterHub configurations, the launched application will be configured to use an SSL certificate for request
In some JupyterHub configurations, the launched application will be configured to use an SSL certificate for requests
between the JupyterLab / Notebook and the JupyterHub API. The path of the certificate is given in the
`JUPYTERHUB_SSL_*` environment variables. We use these variables to create a new SSL Context for both
the `AsyncHTTPClient` (used for Activity Notification, see below) and the `HTTPServer`.

### Activity Notifications

The `jupyterhub-singleuser` will periodically send an activity notification to the JupyterHub API and inform it that
the currently running application is still active. Whether this information is actually used or not depends on the
specific configuration of this JupyterHub.
the currently running application is still active. Whether this information is used or not depends on the specific
configuration of this JupyterHub.

### Environment Variables

8 changes: 4 additions & 4 deletions jupyter_server_proxy/standalone/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations # For Python 3.8 compatibility
from __future__ import annotations

import argparse
import logging
@@ -66,7 +66,7 @@ def run(
port = port or address_port_default[1]

if skip_authentication:
log.warn("Disabling Authentication with JuypterHub Server!")
log.warn("Disabling Authentication with JupyterHub Server!")

prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")

@@ -95,7 +95,7 @@ def run(
# Periodically send JupyterHub Notifications, that we are still running
if activity_interval > 0:
log.info(
f"Sending Acitivity Notivication to JupyterHub with interval={activity_interval}s"
f"Sending Activity Notification to JupyterHub with interval={activity_interval}s"
)
start_activity_update(activity_interval)

@@ -140,7 +140,7 @@ def main():
parser.add_argument(
"--socket-auto",
action="store_true",
help="Use Unix Socket for proxying, but let Jupyter Server Proxy automatically create one.",
help="Use Unix Socket for proxying, but let jupyter-server-proxy automatically create one.",
)
parser.add_argument(
"--env",
2 changes: 1 addition & 1 deletion jupyter_server_proxy/standalone/activity.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
async def notify_activity():
"""
Regularly notify JupyterHub of activity.
See `jupyrehub/singleuser/extensions#L396`
See https://github.com/jupyterhub/jupyterhub/blob/4.x/jupyterhub/singleuser/extension.py#L389
"""

client = httpclient.AsyncHTTPClient()
4 changes: 2 additions & 2 deletions jupyter_server_proxy/standalone/proxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations # For Python 3.8 compatibility
from __future__ import annotations

import os
import re
@@ -81,7 +81,7 @@ def get_timeout(self):


def configure_ssl():
# See jupyter_server/serverapp:init_webapp
# See https://github.com/jupyter-server/jupyter_server/blob/v2.0.0/jupyter_server/serverapp.py#L2053-L2073
keyfile = os.environ.get("JUPYTERHUB_SSL_KEYFILE", "")
certfile = os.environ.get("JUPYTERHUB_SSL_CERTFILE", "")
client_ca = os.environ.get("JUPYTERHUB_SSL_CLIENT_CA", "")