From 39daff309975a34e3b1745c96a1c7cb85adf2382 Mon Sep 17 00:00:00 2001 From: Edward Leonard Date: Mon, 6 Jun 2016 22:05:27 -0500 Subject: [PATCH] added example config with nginx reverse proxy --- docs/source/getting-started.md | 118 ++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/docs/source/getting-started.md b/docs/source/getting-started.md index 64c6929a..9beecdf6 100644 --- a/docs/source/getting-started.md +++ b/docs/source/getting-started.md @@ -128,6 +128,8 @@ Configuring only the main IP and port of JupyterHub should be sufficient for mos However, more customized scenarios may need additional networking details to be configured. + + ### Configuring the Proxy's REST API communication IP address and port (optional) The Hub service talks to the proxy via a REST API on a secondary port, whose network interface and port can be configured separately. @@ -390,7 +392,7 @@ It is recommended to put all of the files used by JupyterHub into standard UNIX * `/etc/jupyterhub` for all configuration files * `/var/log` for log files -## Example +## Example with GitHub OAuth In the following example, we show a configuration files for a fairly standard JupyterHub deployment with the following assumptions: @@ -462,6 +464,120 @@ export CONFIGPROXY_AUTH_TOKEN=super-secret jupyterhub -f /path/to/aboveconfig.py ``` +## Example with nginx reverse proxy + +In the following example, we show configuration files for a JupyterHub server running locally on port `8000` but accessible from the outside on the standard SSL port `443`. This could be useful if the JupyterHub server machine is also hosting other domains or content on `443`. The goal here is to have the following be true: + +* JupyterHub is running on a server, accessed *only* via `HUB.DOMAIN.TLD:443` +* On the same machine, `NO_HUB.DOMAIN.TLD` strictly serves different content, also on port `443` +* `nginx` is used to manage the web servers / reverse proxy (which means that only nginx will be able to bind two servers to `443`) +* After testing, the server in question should be able to score an A+ on the Qualys SSL Labs [SSL Server Test](https://www.ssllabs.com/ssltest/) + +Let's start out with `jupyterhub_config.py`: + +```python +#Force the proxy to only listen to connections to 127.0.0.1 +c.JupyterHub.ip = '127.0.0.1' +``` + +The `nginx` server config files are fairly standard fare except for the two `location` blocks within the `HUB.DOMAIN.TLD` config file: + +```bash +# HTTP server to redirect all 80 traffic to SSL/HTTPS +server { + listen 80; + server_name HUB.DOMAIN.TLD; + + # Tell all requests to port 80 to be 302 redirected to HTTPS + return 302 https://$host$request_uri; +} + +# HTTPS server to handle JupyterHub +server { + listen 443; + ssl on; + + server_name HUB.DOMAIN.TLD; + + ssl_certificate /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem + ssl_certificate_key /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_stapling on; + ssl_stapling_verify on; + add_header Strict-Transport-Security max-age=15768000; + + # Managing literal requests to the JupyterHub front end + location / { + proxy_pass https://127.0.0.1:8000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Managing WebHook/Socket requests between hub user servers and external proxy + location ~* /(api/kernels/[^/]+/(channels|iopub|shell|stdin)|terminals/websocket)/? { + proxy_pass https://127.0.0.1:8000; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + } + + # Managing requests to verify letsencrypt host + location ~ /.well-known { + allow all; + } + + +} +``` + +`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 of the site as well as the applicable location call: + +```bash +server { + listen 80; + server_name NO_HUB.DOMAIN.TLD; + + # Tell all requests to port 80 to be 302 redirected to HTTPS + return 302 https://$host$request_uri; +} + +server { + listen 443; + ssl on; + + # INSERT OTHER SSL PARAMETERS HERE AS ABOVE + + # Set the appropriate root directory + root /var/www/html + + # Set URI handling + location / { + try_files $uri $uri/ =404; + } + + # Managing requests to verify letsencrypt host + location ~ /.well-known { + allow all; + } + +} + + +Now just restart `nginx`, restart the JupyterHub, and enjoy accessing https://HUB.DOMAIN.TLD while serving other content securely on https://NO_HUB.DOMAIN.TLD. + # Further reading - [Custom Authenticators](./authenticators.html)