Skip to content

Commit

Permalink
Merge pull request #1636 from consideRatio/5-autohttps-cm-to-yaml
Browse files Browse the repository at this point in the history
autohttps: traefik's config now configurable and in YAML
  • Loading branch information
consideRatio committed Apr 24, 2020
2 parents 43b8500 + eb13535 commit c460c79
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 125 deletions.
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 }}
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

0 comments on commit c460c79

Please sign in to comment.