Skip to content

Commit

Permalink
Merge pull request #3548 from manics/subdomain-host
Browse files Browse the repository at this point in the history
Support subdomain_host (CHP needs --host-routing)
  • Loading branch information
minrk authored Oct 24, 2024
2 parents 26eb8ef + dcd031a commit 014de3c
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 8 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test-chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,14 @@ jobs:
--set hub.image.name=quay.io/jupyterhub/k8s-hub-slim
--set prePuller.hook.enabled=true
--set prePuller.hook.pullOnlyOnChanges=true
- k3s-channel: v1.31 # also test hub.existingSecret
- k3s-channel: v1.31 # also test hub.existingSecret and subdomain_host
test: install
local-chart-extra-args: >-
--set hub.existingSecret=test-hub-existing-secret
--set proxy.secretToken=aaaa1111
--set hub.cookieSecret=bbbb2222
--set hub.config.CryptKeeper.keys[0]=cccc3333
--set hub.config.JupyterHub.subdomain_host=jupyterhub.example.org
create-k8s-test-resources: true

# We run three upgrade tests where we first install an already released
Expand Down Expand Up @@ -368,6 +369,9 @@ jobs:
continue-on-error: ${{ matrix.accept-failure == true }}
run: |
. ./ci/common
if [ "${{ contains(matrix.local-chart-extra-args, 'subdomain_host') }}" = "true" ]; then
export CI_SUBDOMAIN_HOST=jupyterhub.example.org
fi
# If you have problems with the tests add '--capture=no' to show stdout
pytest --verbose --maxfail=2 --color=yes ./tests
Expand Down
41 changes: 41 additions & 0 deletions docs/source/administrator/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,44 @@ proxy:
```
This would restrict the access to only two IP addresses: `111.111.111.111` and `222.222.222.222`.

(jupyterhub_subdomains)=

## Host user servers on a subdomain

You can reduce the chance of cross-origin attacks by giving each user
their own subdomain `<user>.jupyter.example.org`.
This requires setting [`subdomain_host`](schema_hub.config.JupyterHub.subdomain_host), creating a wildcard DNS record `*.jupyter.example.org`, and creating a wildcard SSL certificate.

```yaml
hub:
config:
JupyterHub:
subdomain_host: jupyter.example.org
```

If you are using a Kubernetes ingress this must include hosts
`jupyter.example.org` and `*.jupyter.example.org`.
For example:

```yaml
ingress:
enabled: true
hosts:
- jupyter.example.org
- "*.jupyter.example.org"
tls:
- hosts:
- jupyter.example.org
- "*.jupyter.example.org"
secretName: example-tls
```

where `example-tls` is the name of a Kubernetes secret containing the wildcard certificate and key.

The chart does not support the automatic creation of wildcard HTTPS certificates.
You must obtain a certificate from an external source,
for example by using an ACME client such as [cert-manager with the DNS-01 challenge](https://cert-manager.io/docs/configuration/acme/dns01/),
and ensure the certificate and key are stored in the secret.

See {ref}`jupyterhub:subdomains` in the JupyterHub documentation for more information.
3 changes: 3 additions & 0 deletions jupyterhub/templates/proxy/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ spec:
{{- if .Values.debug.enabled }}
- --log-level=debug
{{- end }}
{{- if .Values.hub.config.JupyterHub.subdomain_host }}
- --host-routing
{{- end }}
{{- range .Values.proxy.chp.extraCommandLineFlags }}
- {{ tpl . $ }}
{{- end }}
Expand Down
21 changes: 20 additions & 1 deletion jupyterhub/values.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ properties:
values, you need to use [`hub.extraConfig`](schema_hub.extraConfig)
instead.
```{admonition} Currently intended only for auth config
```{admonition} Some configuration must be set in multiple places
:class: warning
This config _currently_ (0.11.0) only influence the software in the
`hub` Pod, but some Helm chart config options such as
Expand Down Expand Up @@ -271,6 +271,25 @@ properties:
the `--values` or `-f` flag. During merging, lists are replaced while
dictionaries are updated.
```
properties:
JupyterHub:
type: object
additionalProperties: true
description: |
JupyterHub Traitlets configuration.
See {py:mod}`jupyterhub:jupyterhub.app` for the full list,
but take note of the [above warnings](schema_hub.config).
properties:
subdomain_host:
type: string
description: |
The subdomain to use for hosting singleuser servers.
This helps protect against some cross-origin attacks by giving each user
their own subdomain `<user>.jupyter.example.org`.
See {ref}`jupyterhub_subdomains`.
extraFiles: &extraFiles
type: object
additionalProperties: false
Expand Down
43 changes: 37 additions & 6 deletions tests/test_spawn.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import json
import os
import subprocess
import time

import pytest
import requests

# If we're testing subdomain hosts in GitHub CI our workflow will set this
CI_SUBDOMAIN_HOST = os.getenv("CI_SUBDOMAIN_HOST")


def test_spawn_basic(
api_request,
Expand All @@ -28,12 +32,39 @@ def test_spawn_basic(
api_request, jupyter_user, request_data["test_timeout"]
)
assert server_model
r = requests.get(
request_data["hub_url"].partition("/hub/api")[0]
+ server_model["url"]
+ "api",
verify=pebble_acme_ca_cert,
)

hub_parent_url = request_data["hub_url"].partition("/hub/api")[0]

if CI_SUBDOMAIN_HOST:
# We can't make a proper request since wildcard DNS isn't setup,
# but we can set the Host header to test that CHP correctly forwards
# the request to the singleuser server
assert (
server_model["url"]
== f"https://{jupyter_user}.{CI_SUBDOMAIN_HOST}/user/{jupyter_user}/"
)

# It shouldn't be possible to access the server without the subdomain,
# should instead be redirected to hub
r_incorrect = requests.get(
f"{hub_parent_url}/user/{jupyter_user}/api",
verify=pebble_acme_ca_cert,
allow_redirects=False,
)
assert r_incorrect.status_code == 302

r = requests.get(
f"{hub_parent_url}/user/{jupyter_user}/api",
headers={"Host": f"{jupyter_user}.{CI_SUBDOMAIN_HOST}"},
verify=False,
allow_redirects=False,
)
else:
r = requests.get(
hub_parent_url + server_model["url"] + "api",
verify=pebble_acme_ca_cert,
)

assert r.status_code == 200
assert "version" in r.json()

Expand Down

0 comments on commit 014de3c

Please sign in to comment.