Files
the-littlest-jupyterhub/tests/test_traefik.py
2023-05-17 09:41:55 +02:00

243 lines
7.2 KiB
Python

"""Test traefik configuration"""
import os
import pytest
import toml
from tljh import config, traefik
def test_download_traefik(tmpdir):
traefik_bin = tmpdir.mkdir("bin").join("traefik")
traefik.ensure_traefik_binary(str(tmpdir))
assert traefik_bin.exists()
# ignore higher-order permission bits, only verify ugo permissions
assert (traefik_bin.stat().mode & 0o777) == 0o755
def _read_toml(path):
"""Read a toml file
print config for debugging on failure
"""
print(path)
with open(path) as f:
toml_cfg = f.read()
print(toml_cfg)
return toml.loads(toml_cfg)
def _read_static_config(state_dir):
return _read_toml(os.path.join(state_dir, "traefik.toml"))
def _read_dynamic_config(state_dir):
return _read_toml(os.path.join(state_dir, "rules", "dynamic.toml"))
def test_default_config(tmpdir, tljh_dir):
state_dir = tmpdir.mkdir("state")
traefik.ensure_traefik_config(str(state_dir))
assert state_dir.join("traefik.toml").exists()
os.path.join(state_dir, "traefik.toml")
rules_dir = os.path.join(state_dir, "rules")
cfg = _read_static_config(state_dir)
assert cfg["api"] == {}
assert cfg["entryPoints"] == {
"http": {
"address": ":80",
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "localhost:8099",
},
}
assert cfg["providers"] == {
"providersThrottleDuration": "0s",
"file": {"directory": rules_dir, "watch": True},
}
dynamic_config = _read_dynamic_config(state_dir)
assert dynamic_config == {}
def test_letsencrypt_config(tljh_dir):
state_dir = config.STATE_DIR
config.set_config_value(config.CONFIG_FILE, "https.enabled", True)
config.set_config_value(
config.CONFIG_FILE, "https.letsencrypt.email", "fake@jupyter.org"
)
config.set_config_value(
config.CONFIG_FILE, "https.letsencrypt.domains", ["testing.jovyan.org"]
)
traefik.ensure_traefik_config(str(state_dir))
cfg = _read_static_config(state_dir)
assert cfg["entryPoints"] == {
"http": {
"address": ":80",
"http": {
"redirections": {
"entryPoint": {
"scheme": "https",
"to": "https",
},
},
},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"https": {
"address": ":443",
"http": {"tls": {"options": "default"}},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "localhost:8099",
},
}
assert "tls" not in cfg
dynamic_config = _read_dynamic_config(state_dir)
assert dynamic_config["tls"] == {
"options": {
"default": {
"minVersion": "VersionTLS12",
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
],
}
},
"stores": {
"default": {
"defaultGeneratedCert": {
"resolver": "letsencrypt",
"domain": {
"main": "testing.jovyan.org",
"sans": [],
},
}
}
},
}
assert "certificatesResolvers" in cfg
assert "letsencrypt" in cfg["certificatesResolvers"]
assert cfg["certificatesResolvers"]["letsencrypt"]["acme"] == {
"email": "fake@jupyter.org",
"storage": "acme.json",
"tlsChallenge": {},
}
def test_manual_ssl_config(tljh_dir):
state_dir = config.STATE_DIR
config.set_config_value(config.CONFIG_FILE, "https.enabled", True)
config.set_config_value(config.CONFIG_FILE, "https.tls.key", "/path/to/ssl.key")
config.set_config_value(config.CONFIG_FILE, "https.tls.cert", "/path/to/ssl.cert")
traefik.ensure_traefik_config(str(state_dir))
cfg = _read_static_config(state_dir)
assert cfg["entryPoints"] == {
"http": {
"address": ":80",
"http": {
"redirections": {
"entryPoint": {
"scheme": "https",
"to": "https",
},
},
},
"transport": {
"respondingTimeouts": {
"idleTimeout": "10m",
}
},
},
"https": {
"address": ":443",
"http": {"tls": {"options": "default"}},
"transport": {"respondingTimeouts": {"idleTimeout": "10m"}},
},
"auth_api": {
"address": "localhost:8099",
},
}
assert "tls" not in cfg
dynamic_config = _read_dynamic_config(state_dir)
assert "tls" in dynamic_config
assert dynamic_config["tls"] == {
"options": {
"default": {
"minVersion": "VersionTLS12",
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
],
},
},
"stores": {
"default": {
"defaultCertificate": {
"certFile": "/path/to/ssl.cert",
"keyFile": "/path/to/ssl.key",
}
}
},
}
def test_extra_config(tmpdir, tljh_dir):
extra_config_dir = os.path.join(tljh_dir, config.CONFIG_DIR, "traefik_config.d")
state_dir = tmpdir.mkdir("state")
traefik_toml = os.path.join(state_dir, "traefik.toml")
# Generate default config
traefik.ensure_traefik_config(str(state_dir))
# Read the default config
toml_cfg = toml.load(traefik_toml)
# Make sure the defaults are what we expect
assert toml_cfg["log"]["level"] == "INFO"
with pytest.raises(KeyError):
toml_cfg["api"]["dashboard"]
assert toml_cfg["entryPoints"]["auth_api"]["address"] == "localhost:8099"
extra_config = {
# modify existing value
"log": {
"level": "ERROR",
},
# add new setting
"api": {"dashboard": True},
}
with open(os.path.join(extra_config_dir, "extra.toml"), "w+") as extra_config_file:
toml.dump(extra_config, extra_config_file)
# Merge the extra config with the defaults
traefik.ensure_traefik_config(str(state_dir))
# Read back the merged config
toml_cfg = toml.load(traefik_toml)
# Check that the defaults were updated by the extra config
assert toml_cfg["log"]["level"] == "ERROR"
assert toml_cfg["api"]["dashboard"] == True