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

autohttps: traefik's config now configurable and in YAML #1636

Merged
merged 5 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
109 changes: 109 additions & 0 deletions jupyterhub/templates/proxy/autohttps/_configmap-dynamic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{{- define "jupyterhub.dynamic.yaml" -}}
# Content of dynamic.yaml to be merged merged with
# proxy.traefik.extraDynamicConfig.
# ----------------------------------------------------------------------------
http:
# Middlewares tweaks requests. We define them here and reference them in
# our routers. We use them to redirect http traffic and headers to proxied
# web requests.
#
# ref: https://docs.traefik.io/middlewares/overview/
middlewares:
hsts:
# A middleware to add a HTTP Strict-Transport-Security (HSTS) response
# header, they function as a request for browsers to enforce HTTPS on
# their end in for a given time into the future, and optionally
# subdomains for requests to subdomains as well.
#
# ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
headers:
stsIncludeSubdomains: {{ .Values.proxy.traefik.hsts.includeSubdomains }}
stsPreload: {{ .Values.proxy.traefik.hsts.preload }}
stsSeconds: {{ .Values.proxy.traefik.hsts.maxAge | int64 }}
# A middleware to redirect to https
redirect:
redirectScheme:
permanent: true
scheme: https
# A middleware to add a X-Scheme (X-Forwarded-Proto) header that
# JupyterHub's Tornado web-server needs if expecting to serve https
# traffic. Without it we would run into issues like:
# https://github.com/jupyterhub/jupyterhub/issues/2284
scheme:
headers:
customRequestHeaders:
# DISCUSS ME: Can we use the X-Forwarded-Proto header instead? It
# seems more recognized. Mozilla calls it the de-facto standard
# header for this purpose, and Tornado recognizes both.
#
# ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
# ref: https://www.tornadoweb.org/en/stable/httpserver.html#http-server
X-Scheme: https

# Routers routes web requests to a service and optionally tweaks them with
# middleware.
#
# ref: https://docs.traefik.io/routing/routers/
routers:
# Route secure https traffic to the configurable-http-proxy managed by
# JupyterHub.
default:
entrypoints:
- "https"
middlewares:
- "hsts"
- "scheme"
rule: PathPrefix(`/`)
service: default
# Use our predefined TLS options and certificate resolver, enabling
# this route to act as a TLS termination proxy with high security
# standards.
tls:
certResolver: default
domains:
{{- range $host := .Values.proxy.https.hosts }}
- main: {{ $host }}
{{- end }}
options: default

# Route insecure http traffic to https
insecure:
entrypoints:
- "http"
middlewares:
- "redirect"
rule: PathPrefix(`/`)
service: default

# Services represents the destinations we route traffic to.
#
# ref: https://docs.traefik.io/routing/services/
services:
# Represents the configurable-http-proxy (chp) server that is managed by
# JupyterHub to route traffic both to itself and to user pods.
default:
loadBalancer:
servers:
- url: 'http://proxy-http:8000/'

# Configure TLS to give us an A+ in the ssllabs.com test
#
# ref: https://www.ssllabs.com/ssltest/
tls:
options:
default:
# Allowed ciphers adapted from Mozillas SSL Configuration Generator
# configured for Intermediate support which doesn't support very old
# systems but doesn't require very modern either.
#
# ref: https://ssl-config.mozilla.org/#server=traefik&version=2.1.2&config=intermediate&guideline=5.4
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
minVersion: VersionTLS12
sniStrict: true
{{- end }}
68 changes: 68 additions & 0 deletions jupyterhub/templates/proxy/autohttps/_configmap-traefik.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{{- define "jupyterhub.traefik.yaml" -}}
# Content of traefik.yaml to be merged merged with
# proxy.traefik.extraStaticConfig.
# ----------------------------------------------------------------------------

# Config of logs about web requests
#
# ref: https://docs.traefik.io/observability/access-logs/
accessLog:
# Redact commonly sensitive headers
fields:
headers:
names:
Authorization: redacted
Cookie: redacted
Set-Cookie: redacted
X-Xsrftoken: redacted
# Only log errors
filters:
statusCodes:
- 500-599

# Automatically acquire certificates certificates form a Certificate
# Authority (CA) like Let's Encrypt using the ACME protocol's HTTP-01
# challenge.
#
# ref: https://docs.traefik.io/https/acme/#certificate-resolvers
certificatesResolvers:
default:
acme:
caServer: {{ .Values.proxy.https.letsencrypt.acmeServer }}
email: {{ .Values.proxy.https.letsencrypt.contactEmail }}
httpChallenge:
entryPoint: http
storage: /etc/acme/acme.json

# Let Traefik listen to port 80 and port 443
#
# ref: https://docs.traefik.io/routing/entrypoints/
entryPoints:
# Port 80, used for:
# - ACME HTTP-01 challenges
# - Redirects to HTTPS
http:
address: ':80'
# Port 443, used for:
# - TLS Termination Proxy, where HTTPS transitions to HTTP.
https:
address: ':443'
# Configure a high idle timeout for our websockets connections
transport:
respondingTimeouts:
idleTimeout: 10m0s

# Config of logs about what happens to Traefik itself (startup,
# configuration, events, shutdown, and so on).
#
# ref: https://docs.traefik.io/observability/logs
log:
level: {{ if .Values.debug.enabled -}} DEBUG {{- else -}} INFO {{- end }}

# Let Traefik monitor another file we mount for dynamic configuration. As we
# mount this file through this configmap, we can make a `kubectl edit` on the
# configmap and have Traefik update on changes to dynamic.yaml.
providers:
file:
filename: /etc/traefik/dynamic.yaml
{{- end }}
138 changes: 16 additions & 122 deletions jupyterhub/templates/proxy/autohttps/configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,134 +1,28 @@
{{- $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled) }}
{{- $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt")) }}
{{- if $autoHTTPS -}}
{{- $_ := required .Values.proxy.https.letsencrypt.contactEmail "proxy.https.letsencrypt.contactEmail is a required field" -}}

# This configmap contains Traefik configuration files to be mounted.
# - traefik.yaml will only be read during startup (static configuration)
# - dynamic.yaml will be read on change (dynamic configuration)
#
# ref: https://docs.traefik.io/getting-started/configuration-overview/
#
# The configuration files are first rendered with Helm templating to large YAML
# strings. Then we use the fromYAML function on these strings to get an object,
# that we in turn merge with user provided extra configuration.
#
kind: ConfigMap
apiVersion: v1
metadata:
name: traefik-proxy-config
labels:
{{- include "jupyterhub.labels" . | nindent 4 }}
data:
# This configmap provides the complete configuration for our traefik proxy.
# traefik has 'static' config and 'dynamic' config (https://docs.traefik.io/getting-started/configuration-overview/)
# traefik.toml contains the static config, while dynamic.toml has the dynamic config.
traefik.toml: |
traefik.yaml: |
{{- include "jupyterhub.traefik.yaml" . | fromYaml | merge .Values.proxy.traefik.extraStaticConfig | toYaml | nindent 4 }}
GeorgianaElena marked this conversation as resolved.
Show resolved Hide resolved
dynamic.yaml: |
{{- include "jupyterhub.dynamic.yaml" . | fromYaml | merge .Values.proxy.traefik.extraDynamicConfig | toYaml | nindent 4 }}

# We wanna listen on both port 80 & 443
[entryPoints]
[entryPoints.http]
# traefik is used only for TLS termination, so port 80 is just for redirects
# No configuration for timeouts, etc needed
address = ":80"

[entryPoints.https]
address = ":443"

[entryPoints.https.transport.respondingTimeouts]
# High idle timeout, because we have websockets
idleTimeout = "10m0s"

[log]
level = "INFO"

[accessLog]
[accessLog.filters]
# Only error codes
statusCodes = ["500-599"]

# Redact possible sensitive headers from log
[accessLog.fields.headers]
[accessLog.fields.headers.names]
Authorization = "redact"
Cookie = "redact"
Set-Cookie = "redact"
X-Xsrftoken = "redact"

# We want certificates to come from Let's Encrypt, with the HTTP-01 challenge
[certificatesResolvers.le.acme]
email = {{ required "proxy.https.letsencrypt.contactEmail is a required field" .Values.proxy.https.letsencrypt.contactEmail | quote }}
storage = "/etc/acme/acme.json"
{{- if .Values.proxy.https.letsencrypt.acmeServer }}
caServer = {{ .Values.proxy.https.letsencrypt.acmeServer | quote }}
{{- end}}
[certificatesResolvers.le.acme.httpChallenge]
# Use our port 80 http endpoint for the HTTP-01 challenge
entryPoint = "http"

[providers]
[providers.file]
# Configuration for routers & other dynamic items come from this file
# This file is also provided by this configmap
filename = '/etc/traefik/dynamic.toml'

dynamic.toml: |
# Configure TLS to give us an A+ in the ssllabs test
[tls]
[tls.options]
[tls.options.default]
sniStrict = true
# Do not support insecureTLS 1.0 or 1.1
minVersion = "VersionTLS12"
# Ciphers to support, adapted from https://ssl-config.mozilla.org/#server=traefik&server-version=1.7.12&config=intermediate
# This supports a reasonable number of browsers.
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"
]

# traefik uses middlewares to set options in specific routes
# These set up the middleware options, which are then referred to by the routes
[http.middlewares]
# Used to do http -> https redirects
[http.middlewares.redirect.redirectScheme]
scheme = "https"

# Used to set appropriate headers in requests sent to CHP
[http.middlewares.https.headers]
[http.middlewares.https.headers.customRequestHeaders]
# Tornado needs this for referrer checks
# You run into stuff like https://github.com/jupyterhub/jupyterhub/issues/2284 otherwise
X-Scheme = "https"

# Used to set HSTS headers based on user provided options
[http.middlewares.hsts.headers]
stsSeconds = {{ int64 .Values.proxy.traefik.hsts.maxAge }}
{{ if .Values.proxy.traefik.hsts.includeSubdomains }}
stsIncludeSubdomains = true
{{- end }}


# Routers match conditions (rule) to options (middlewares) and a backend (service)
[http.routers]
# Listen on the http endpoint (port 80), redirect everything to https
[http.routers.httpredirect]
rule = "PathPrefix(`/`)"
service = "chp"
entrypoints = ["http"]
middlewares = ["redirect"]

# Listen on https endpoint (port 443), proxy everything to chp
[http.routers.chp]
rule = "PathPrefix(`/`)"
entrypoints = ["https"]
middlewares = ["hsts", "https"]
service = "chp"

[http.routers.chp.tls]
# use our nice TLS defaults, and get HTTPS from Let's Encrypt
options = "default"
certResolver = "le"
{{- range $host := .Values.proxy.https.hosts }}
[[http.routers.chp.tls.domains]]
main = "{{ $host }}"
{{- end}}

# Set CHP to be our only backend where traffic is routed to
[http.services]
[http.services.chp.loadBalancer]
[[http.services.chp.loadBalancer.servers]]
url = "http://proxy-http:8000/"
{{- end }}
7 changes: 6 additions & 1 deletion jupyterhub/templates/proxy/autohttps/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{- $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled) }}
{{- $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt")) }}
{{- if $autoHTTPS -}}

apiVersion: apps/v1
kind: Deployment
metadata:
Expand All @@ -18,7 +19,11 @@ spec:
{{- include "jupyterhub.matchLabels" . | nindent 8 }}
hub.jupyter.org/network-access-proxy-http: "true"
annotations:
checksum/config-map: {{ include (print .Template.BasePath "/proxy/autohttps/configmap.yaml") . | sha256sum }}
# Only force a restart through a change to this checksum when the static
# configuration is changed, as the dynamic can be updated after start.
# Any disruptions to this deployment impacts everything, it is the
# entrypoint of all network traffic.
checksum/static-config: {{ include "jupyterhub.traefik.yaml" . | fromYaml | merge .Values.proxy.traefik.extraStaticConfig | toYaml | sha256sum }}
spec:
{{- if .Values.rbac.enabled }}
serviceAccountName: autohttps
Expand Down
7 changes: 5 additions & 2 deletions jupyterhub/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,12 @@ proxy:
name: traefik
tag: v2.2 # ref: https://hub.docker.com/_/traefik?tab=tags
hsts:
maxAge: 15724800 # About 6 months
includeSubdomains: false
preload: false
maxAge: 15724800 # About 6 months
resources: {}
extraStaticConfig: {}
extraDynamicConfig: {}
secretSync:
image:
name: jupyterhub/k8s-secret-sync
Expand All @@ -171,7 +174,7 @@ proxy:
letsencrypt:
contactEmail: ''
# Specify custom server here (https://acme-staging-v02.api.letsencrypt.org/directory) to hit staging LE
acmeServer: ''
acmeServer: https://acme-v02.api.letsencrypt.org/directory
manual:
key:
cert:
Expand Down