Skip to content

Commit

Permalink
checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
minrk committed Mar 23, 2023
1 parent 077bfe0 commit bdf9943
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 96 deletions.
4 changes: 4 additions & 0 deletions integration-tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ def test_manual_https(preserve_config):
set_config_value(CONFIG_FILE, "https.enabled", True)
set_config_value(CONFIG_FILE, "https.tls.key", key)
set_config_value(CONFIG_FILE, "https.tls.cert", cert)
# hub needs to reload
# because it is responsible for much of the TLS config now
reload_component("proxy")
reload_component("hub")
for i in range(10):
time.sleep(i)
try:
Expand All @@ -95,6 +98,7 @@ def test_manual_https(preserve_config):
set_config_value(CONFIG_FILE, "https.enabled", False)

reload_component("proxy")
reload_component("hub")


def test_extra_traefik_config():
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"backoff",
"requests",
"bcrypt",
"jupyterhub-traefik-proxy==0.3.*",
"jupyterhub-traefik-proxy==1.0.0b1",
],
entry_points={
"console_scripts": [
Expand Down
121 changes: 78 additions & 43 deletions tests/test_traefik.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,29 @@ def test_default_config(tmpdir, tljh_dir):
traefik.ensure_traefik_config(str(state_dir))
assert state_dir.join("traefik.toml").exists()
traefik_toml = os.path.join(state_dir, "traefik.toml")
rules_dir = os.path.join(state_dir, "rules")
with open(traefik_toml) as f:
toml_cfg = f.read()
# print config for debugging on failure
print(config.CONFIG_FILE)
print(toml_cfg)
cfg = toml.loads(toml_cfg)
assert cfg["defaultEntryPoints"] == ["http"]
assert len(cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"]) == 1
# runtime generated entry, value not testable
cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"] = [""]

assert cfg["api"] == {}
assert cfg["entryPoints"] == {
"http": {"address": ":80"},
"http": {
"address": ":80",
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "127.0.0.1:8099",
"auth": {"basic": {"users": [""]}},
"whiteList": {"sourceRange": ["127.0.0.1"]},
},
}
assert "tls" not in cfg
assert "certResolvers" not in cfg
assert cfg["providers"] == {
"providersThrottleDuration": "0s",
"file": {"directory": rules_dir, "watch": True},
}


def test_letsencrypt_config(tljh_dir):
Expand All @@ -59,27 +63,48 @@ def test_letsencrypt_config(tljh_dir):
print(config.CONFIG_FILE)
print(toml_cfg)
cfg = toml.loads(toml_cfg)
assert cfg["defaultEntryPoints"] == ["http", "https"]
assert "acme" in cfg
assert len(cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"]) == 1
# runtime generated entry, value not testable
cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"] = [""]

assert cfg["entryPoints"] == {
"http": {"address": ":80", "redirect": {"entryPoint": "https"}},
"https": {"address": ":443", "tls": {"minVersion": "VersionTLS12"}},
"http": {
"address": ":80",
"redirections": {
"entryPoint": {
"scheme": "https",
"to": "https",
},
},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"https": {
"address": ":443",
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "127.0.0.1:8099",
"auth": {"basic": {"users": [""]}},
"whiteList": {"sourceRange": ["127.0.0.1"]},
},
}
assert cfg["acme"] == {
assert "tls" in cfg
assert cfg["tls"] == {
"options": {"default": {"minVersion": "VersionTLS12"}},
"stores": {
"default": {
"defaultGeneratedCert": {
"resolver": "letsencrypt",
"domain": {
"main": "testing.jovyan.org",
"sans": [],
},
}
}
},
}
assert "certificateResolvers" in cfg
assert "letsencrypt" in cfg["certificateResolvers"]

assert cfg["certificateResolvers"]["letsencrypt"]["acme"] == {
"email": "fake@jupyter.org",
"storage": "acme.json",
"entryPoint": "https",
"httpChallenge": {"entryPoint": "http"},
"domains": [{"main": "testing.jovyan.org"}],
}


Expand All @@ -96,26 +121,37 @@ def test_manual_ssl_config(tljh_dir):
print(config.CONFIG_FILE)
print(toml_cfg)
cfg = toml.loads(toml_cfg)
assert cfg["defaultEntryPoints"] == ["http", "https"]
assert "acme" not in cfg
assert len(cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"]) == 1
# runtime generated entry, value not testable
cfg["entryPoints"]["auth_api"]["auth"]["basic"]["users"] = [""]

assert cfg["entryPoints"] == {
"http": {"address": ":80", "redirect": {"entryPoint": "https"}},
"http": {
"address": ":80",
"redirections": {
"entryPoint": {
"scheme": "https",
"to": "https",
},
},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"https": {
"address": ":443",
"tls": {
"minVersion": "VersionTLS12",
"certificates": [
{"certFile": "/path/to/ssl.cert", "keyFile": "/path/to/ssl.key"}
],
},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "127.0.0.1:8099",
"auth": {"basic": {"users": [""]}},
"whiteList": {"sourceRange": ["127.0.0.1"]},
},
}
assert "tls" in cfg

assert cfg["tls"] == {
"options": {"default": {"minVersion": "VersionTLS12"}},
"stores": {
"default": {
"defaultCertificate": {
"certFile": "/path/to/ssl.cert",
"keyFile": "/path/to/ssl.key",
}
}
},
}

Expand All @@ -132,18 +168,18 @@ def test_extra_config(tmpdir, tljh_dir):
toml_cfg = toml.load(traefik_toml)

# Make sure the defaults are what we expect
assert toml_cfg["logLevel"] == "INFO"
assert toml_cfg["log"]["level"] == "INFO"
with pytest.raises(KeyError):
toml_cfg["checkNewVersion"]
toml_cfg["api"]["dashboard"]
assert toml_cfg["entryPoints"]["auth_api"]["address"] == "127.0.0.1:8099"

extra_config = {
# modify existing value
"logLevel": "ERROR",
# modify existing value with multiple levels
"entryPoints": {"auth_api": {"address": "127.0.0.1:9999"}},
"log": {
"level": "ERROR",
},
# add new setting
"checkNewVersion": False,
"api": {"dashboard": True},
}

with open(os.path.join(extra_config_dir, "extra.toml"), "w+") as extra_config_file:
Expand All @@ -156,6 +192,5 @@ def test_extra_config(tmpdir, tljh_dir):
toml_cfg = toml.load(traefik_toml)

# Check that the defaults were updated by the extra config
assert toml_cfg["logLevel"] == "ERROR"
assert toml_cfg["checkNewVersion"] == False
assert toml_cfg["entryPoints"]["auth_api"]["address"] == "127.0.0.1:9999"
assert toml_cfg["log"]["level"] == "ERROR"
assert toml_cfg["api"]["dashboard"] == True
13 changes: 11 additions & 2 deletions tljh/configurer.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,17 @@ def update_traefik_api(c, config):
"""
Set traefik api endpoint credentials
"""
c.TraefikTomlProxy.traefik_api_username = config["traefik_api"]["username"]
c.TraefikTomlProxy.traefik_api_password = config["traefik_api"]["password"]
c.TraefikFileProviderProxy.traefik_api_username = config["traefik_api"]["username"]
c.TraefikFileProviderProxy.traefik_api_password = config["traefik_api"]["password"]
if config["https"]["enabled"]:
c.TraefikFileProviderProxy.traefik_entrypoint = "https"
if config["https"]["letsencrypt"]["email"]:
c.TraefikFileProviderProxy.traefik_cert_resolver = "letsencrypt"
elif config["https"]["tls"]["cert"]:
c.TraefikFileProviderProxy.ssl_cert = config["https"]["tls"]["cert"]
c.TraefikFileProviderProxy.ssl_key = config["https"]["tls"]["key"]
else:
c.TraefikFileProviderProxy.traefik_entrypoint = "http"


def set_cull_idle_service(config):
Expand Down
7 changes: 3 additions & 4 deletions tljh/jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from tljh.config import INSTALL_PREFIX, USER_ENV_PREFIX, CONFIG_DIR
from tljh.utils import get_plugin_manager
from tljh.user_creating_spawner import UserCreatingSpawner
from jupyterhub_traefik_proxy import TraefikTomlProxy

c.JupyterHub.spawner_class = UserCreatingSpawner

Expand All @@ -19,11 +18,11 @@
# Use a high port so users can try this on machines with a JupyterHub already present
c.JupyterHub.hub_port = 15001

c.TraefikTomlProxy.should_start = False
c.TraefikFileProviderProxy.should_start = False

dynamic_conf_file_path = os.path.join(INSTALL_PREFIX, "state", "rules", "rules.toml")
c.TraefikTomlProxy.toml_dynamic_config_file = dynamic_conf_file_path
c.JupyterHub.proxy_class = TraefikTomlProxy
c.TraefikFileProviderProxy.dynamic_config_file = dynamic_conf_file_path
c.JupyterHub.proxy_class = "traefik_file"

c.SystemdSpawner.extra_paths = [os.path.join(USER_ENV_PREFIX, "bin")]
c.SystemdSpawner.default_shell = "/bin/bash"
Expand Down
1 change: 1 addition & 0 deletions tljh/traefik.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def ensure_traefik_config(state_dir):
config["traefik_api"]["username"],
config["traefik_api"]["password"],
)
config["traefik_dynamic_config_dir"] = traefik_dynamic_config_dir

with open(os.path.join(os.path.dirname(__file__), "traefik.toml.tpl")) as f:
template = Template(f.read())
Expand Down
93 changes: 47 additions & 46 deletions tljh/traefik.toml.tpl
Original file line number Diff line number Diff line change
@@ -1,74 +1,75 @@
# traefik.toml file template
{% if https['enabled'] %}
defaultEntryPoints = ["http", "https"]
{% else %}
defaultEntryPoints = ["http"]
{% endif %}

logLevel = "INFO"
# enable API
[api]

[log]
level = "INFO"

# log errors, which could be proxy errors
[accessLog]
format = "json"

[accessLog.filters]
statusCodes = ["500-999"]

[accessLog.fields.headers]
[accessLog.fields.headers.names]
Authorization = "redact"
Cookie = "redact"
Set-Cookie = "redact"
X-Xsrftoken = "redact"

[respondingTimeouts]
idleTimeout = "10m0s"

[entryPoints]
[entryPoints.http]
address = ":{{http['port']}}"
address = ":{{ http['port'] }}"
[entryPoints.http.transport.respondingTimeouts]
idleTimeout = "10m"
{% if https['enabled'] %}
[entryPoints.http.redirect]
entryPoint = "https"
{% endif %}
[entryPoints.http.redirections.entryPoint]
to = "https"
scheme = "https"

{% if https['enabled'] %}
[entryPoints.https]
address = ":{{https['port']}}"
[entryPoints.https.tls]
minVersion = "VersionTLS12"
{% if https['tls']['cert'] %}
[[entryPoints.https.tls.certificates]]
certFile = "{{https['tls']['cert']}}"
keyFile = "{{https['tls']['key']}}"
{% endif %}
address = ":{{ https['port'] }}"
[entryPoints.https.transport.respondingTimeouts]
idleTimeout = "10m"
{% endif %}

[entryPoints.auth_api]
address = "127.0.0.1:{{traefik_api['port']}}"
[entryPoints.auth_api.whiteList]
sourceRange = ['{{traefik_api['ip']}}']
[entryPoints.auth_api.auth.basic]
users = ['{{ traefik_api['basic_auth'] }}']
address = "127.0.0.1:{{ traefik_api['port'] }}"

[wss]
protocol = "http"

[api]
dashboard = true
entrypoint = "auth_api"
{% if https['enabled'] %}
[tls]
[tls.options.default]
minVersion = "VersionTLS12"

{% if https['enabled'] and https['letsencrypt']['email'] %}
[acme]
email = "{{https['letsencrypt']['email']}}"
storage = "acme.json"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
{% if https['tls']['cert'] %}
[tls.stores.default.defaultCertificate]
certFile = "{{ https['tls']['cert'] }}"
keyFile = "{{ https['tls']['key'] }}"
{% endif %}

{% for domain in https['letsencrypt']['domains'] %}
[[acme.domains]]
main = "{{domain}}"
{% endfor %}
{% if https['letsencrypt']['email'] and https['letsencrypt']['domains'] %}
[tls.stores.default.defaultGeneratedCert]
resolver = "letsencrypt"
[tls.stores.default.defaultGeneratedCert.domain]
main = "{{ https['letsencrypt']['domains'][0] }}"
sans = [
{% for domain in https['letsencrypt']['domains'][1:] %}
"{{ domain }}",
{% endfor %}
]
[certificateResolvers.letsencrypt.acme]
email = "{{ https['letsencrypt']['email'] }}"
storage = "acme.json"
[certificateResolvers.letsencrypt.acme.httpChallenge]
entryPoint = "http"
{% endif %}
{% endif %}

[file]
directory = "rules"
[providers]
providersThrottleDuration = "0s"
[providers.file]
directory = "{{ traefik_dynamic_config_dir }}"
watch = true

0 comments on commit bdf9943

Please sign in to comment.