diff --git a/chartpress.yaml b/chartpress.yaml index 64b49007ca..6d025ccb33 100644 --- a/chartpress.yaml +++ b/chartpress.yaml @@ -6,6 +6,8 @@ charts: published: https://jupyterhub.github.io/helm-chart images: + secret-sync: + valuesPath: proxy.secretSync.image hub: valuesPath: hub.image buildArgs: diff --git a/images/secret-sync/Dockerfile b/images/secret-sync/Dockerfile new file mode 100644 index 0000000000..fd6e05d51b --- /dev/null +++ b/images/secret-sync/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.7-alpine + +RUN pip install --no-cache kubernetes + +COPY secret-sync.py /usr/local/bin/secret-sync.py diff --git a/images/secret-sync/secret-sync.py b/images/secret-sync/secret-sync.py new file mode 100755 index 0000000000..898a98e8ab --- /dev/null +++ b/images/secret-sync/secret-sync.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +""" +Helper script to perform two-way sync of files to k8s secret objects. + +traefik expects a JSON file (acme.json) to persist across time, +to make sure Let's Encrypt certificates work. In kubernetes, +pod restarts clear out the filesystem, making this hard. We could +add a persistent volume to the proxy, but this is excessive for +a single file. + +This script can do a 'two way' sync of a given file and a key +in a kubernetes secret object. The file should be in an emptyDir +volume in the traefik pod, which should also have this script +running as a sidecar. + +## Kubernetes Secret -> File system + +This needs to happen only once when the pod starts - we do not +support modifications to the secret by other actors. The +'load' command is used to specify the secret name, key and +path to load it into + +## File system -> Kubernetes secret + +traefik might write new contents to the acme.json file over +time, and we need to sync it to the kubernetes secret object. +Ideally, we would watch for changes to the file with inotify +and update the secret object as needed. However, for now we +just operate in a 30s loop. This is good enough, since +traefik can always re-generate certs if needed. +""" +import sys +import os +import subprocess +import argparse +import time +import tarfile +import io +import base64 +import logging +from kubernetes import client, config + +def update_secret(namespace, secret_name, labels, key, value): + """ + Update a secret object's key with the value + """ + try: + config.load_kube_config() + except: + config.load_incluster_config() + + v1 = client.CoreV1Api() + try: + secret = v1.read_namespaced_secret(namespace=namespace, name=secret_name) + except client.rest.ApiException as e: + if e.status == 404: + secret = client.V1Secret( + metadata=client.V1ObjectMeta(name=secret_name, labels=labels), + data={} + ) + resp = v1.create_namespaced_secret(namespace=namespace, body=secret) + logging.info(f"Created secret {secret_name} since it does not exist") + else: + raise + # Value should be base64'd string + new_value = base64.standard_b64encode(value).decode() + if secret.data is None: + secret.data = {} + if new_value != secret.data.get(key): + secret.data[key] = base64.standard_b64encode(value).decode() + v1.patch_namespaced_secret(namespace=namespace, name=secret_name, body=secret) + logging.info(f"Updated secret {secret_name} with new value for key {key}") + +def get_secret_value(namespace, secret_name, key): + try: + config.load_kube_config() + except: + config.load_incluster_config() + + v1 = client.CoreV1Api() + try: + secret = v1.read_namespaced_secret(namespace=namespace, name=secret_name) + except client.rest.ApiException as e: + if e.status == 404: + # Secret doesn't exist + return None + raise + if secret.data is None or key not in secret.data: + return None + return base64.standard_b64decode(secret.data[key]) + +def setup_logging(): + """ + Set up root logger to log to stderr + """ + logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO, stream=sys.stderr) + +def main(): + argparser = argparse.ArgumentParser() + argparser.add_argument( + '--namespace', + help='Namespace the secret exists in' + ) + + argparser.add_argument( + 'action', + choices=['load', 'watch-save'] + ) + + argparser.add_argument( + 'secret_name', + help="Name of secret to sync with. Will be created if needed." + ) + + argparser.add_argument( + 'key', + help="Key in secret object to sync file to" + ) + + argparser.add_argument( + 'path', + help="Path in filesystem to sync to" + ) + + argparser.add_argument( + '--label', + help="Labels (of form key=value) to add to the k8s secret when it is created", + action="append" + ) + + args = argparser.parse_args() + + setup_logging() + + if not args.namespace: + try: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace") as f: + args.namespace = f.read().strip() + except FileNotFoundError: + print("Can not determine a namespace, must be explicitly set with --namespace", file=sys.stderr) + sys.exit(1) + + if args.action == 'load': + value = get_secret_value(args.namespace, args.secret_name, args.key) + if value: + with open(args.path, 'wb') as f: + f.write(value) + os.fchmod(f.fileno(), 0o600) + elif args.action == 'watch-save': + labels = {} + for label in args.label: + l_splits = label.split('=', 1) + labels[l_splits[0]] = l_splits[1] + # FIXME: use inotifiy + while True: + if os.path.exists(args.path): + with open(args.path, 'rb') as f: + update_secret(args.namespace, args.secret_name, labels, args.key, f.read()) + time.sleep(30) + +if __name__ == '__main__': + main() diff --git a/jupyterhub/templates/proxy/autohttps/configmap-nginx.yaml b/jupyterhub/templates/proxy/autohttps/configmap-nginx.yaml deleted file mode 100644 index 5487e766e2..0000000000 --- a/jupyterhub/templates/proxy/autohttps/configmap-nginx.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled) }} -{{- $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt")) }} -{{- if $autoHTTPS -}} -kind: ConfigMap -apiVersion: v1 -metadata: - name: nginx-proxy-config - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} -data: - proxy-body-size: "{{ .Values.proxy.nginx.proxyBodySize }}" - hsts-include-subdomains: "{{ .Values.proxy.nginx.hstsIncludeSubdomains }}" -{{- end }} diff --git a/jupyterhub/templates/proxy/autohttps/configmap.yaml b/jupyterhub/templates/proxy/autohttps/configmap.yaml new file mode 100644 index 0000000000..5f26c8801f --- /dev/null +++ b/jupyterhub/templates/proxy/autohttps/configmap.yaml @@ -0,0 +1,135 @@ +{{- $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled) }} +{{- $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt")) }} +{{- if $autoHTTPS -}} +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: | + + # 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] + sslProxyHeaders = true + [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 }} diff --git a/jupyterhub/templates/proxy/autohttps/deployment.yaml b/jupyterhub/templates/proxy/autohttps/deployment.yaml index 816cdf46a9..e1d81a9aba 100644 --- a/jupyterhub/templates/proxy/autohttps/deployment.yaml +++ b/jupyterhub/templates/proxy/autohttps/deployment.yaml @@ -6,31 +6,19 @@ kind: Deployment metadata: name: autohttps labels: - {{- /* - NOTE: The deployment's own app label does not have to deviate like it's - pod need to. - */}} {{- include "jupyterhub.labels" . | nindent 4 }} spec: replicas: 1 selector: matchLabels: - {{- /* - NOTE: A dynamically created service in the kube-lego project have - hardcoded the selection of a pod based on the `app: kube-lego` label. - For the details, see: - - https://github.com/jetstack/kube-lego/blob/d0c664295772bc5b4bfb95e495c6e0ebe8327319/pkg/service/service.go#L115-L117 - - https://github.com/jetstack/kube-lego/blob/56fa724cdbc31a241d4bdd875461afcabfbc25d6/pkg/kubelego_const/consts.go#L29 - */}} - {{- $_ := merge (dict "appLabel" "kube-lego") . }} - {{- include "jupyterhub.matchLabels" $_ | nindent 6 }} + {{- include "jupyterhub.matchLabels" . | nindent 6 }} template: metadata: labels: - {{- /* Changes here will cause the Deployment to restart the pods. */}} - {{- $_ := merge (dict "appLabel" "kube-lego") . }} - {{- include "jupyterhub.matchLabels" $_ | nindent 8 }} + {{- 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 }} spec: {{- if .Values.rbac.enabled }} serviceAccountName: autohttps @@ -38,47 +26,36 @@ spec: {{- if .Values.scheduling.podPriority.enabled }} priorityClassName: {{ .Release.Name }}-default-priority {{- end }} - terminationGracePeriodSeconds: 60 nodeSelector: {{ toJson .Values.proxy.nodeSelector }} {{- include "jupyterhub.coreAffinity" . | nindent 6 }} + volumes: + - name: certificates + emptyDir: {} + - name: traefik-config + configMap: + name: traefik-proxy-config + initContainers: + - name: load-acme + image: "{{ .Values.proxy.secretSync.image.name }}:{{ .Values.proxy.secretSync.image.tag }}" + {{- with .Values.proxy.secretSync.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + command: ["/usr/local/bin/secret-sync.py", "load", "proxy-public-tls-acme", "acme.json", "/etc/acme/acme.json"] + env: + # We need this to get logs immediately + - name: PYTHONUNBUFFERED + value: "True" + volumeMounts: + - name: certificates + mountPath: /etc/acme containers: - - name: nginx - image: "{{ .Values.proxy.nginx.image.name }}:{{ .Values.proxy.nginx.image.tag }}" - {{- with .Values.proxy.nginx.image.pullPolicy }} + - name: traefik + image: "{{ .Values.proxy.traefik.image.name }}:{{ .Values.proxy.traefik.image.tag }}" + {{- with .Values.proxy.traefik.image.pullPolicy }} imagePullPolicy: {{ . }} {{- end }} resources: - {{- .Values.proxy.nginx.resources | toYaml | trimSuffix "\n" | nindent 12 }} - args: - - /nginx-ingress-controller - - --default-backend-service={{ .Release.Namespace }}/proxy-http - - --configmap={{ .Release.Namespace }}/nginx-proxy-config - - --ingress-class=jupyterhub-proxy-tls - - --watch-namespace={{ .Release.Namespace }} - {{- if .Values.debug.enabled }} - - --v=3 - {{- end }} - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - livenessProbe: - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - initialDelaySeconds: 10 - timeoutSeconds: 1 - readinessProbe: - httpGet: - path: /healthz - port: 10254 - scheme: HTTP + {{- .Values.proxy.traefik.resources | toYaml | trimSuffix "\n" | nindent 12 }} ports: - name: http containerPort: 80 @@ -86,45 +63,31 @@ spec: - name: https containerPort: 443 protocol: TCP - - name: kube-lego - image: "{{ .Values.proxy.lego.image.name }}:{{ .Values.proxy.lego.image.tag }}" - {{- with .Values.proxy.lego.image.pullPolicy }} + volumeMounts: + - name: traefik-config + mountPath: /etc/traefik + - name: certificates + mountPath: /etc/acme + - name: secret-sync + image: "{{ .Values.proxy.secretSync.image.name }}:{{ .Values.proxy.secretSync.image.tag }}" + {{- with .Values.proxy.secretSync.image.pullPolicy }} imagePullPolicy: {{ . }} {{- end }} - resources: - {{- .Values.proxy.lego.resources | toYaml | trimSuffix "\n" | nindent 12 }} + command: + - /usr/local/bin/secret-sync.py + - watch-save + - --label=app={{ include "jupyterhub.appLabel" . }} + - --label=release={{ .Release.Name }} + - --label=chart={{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + - --label=heritage=secret-sync + - proxy-public-tls-acme + - acme.json + - /etc/acme/acme.json env: - - name: LEGO_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: LEGO_WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: LEGO_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: LEGO_EMAIL - # {{ required "proxy.https.letsencrypt.contactEmail is a required field" .Values.proxy.https.letsencrypt.contactEmail }} - value: {{ .Values.proxy.https.letsencrypt.contactEmail | quote }} - - name: LEGO_SUPPORTED_INGRESS_PROVIDER - value: "nginx" - - name: LEGO_SUPPORTED_INGRESS_CLASS - value: "jupyterhub-proxy-tls,dummy" - - name: LEGO_DEFAULT_INGRESS_CLASS - value: "jupyterhub-proxy-tls" - - name: LEGO_KUBE_ANNOTATION - value: "hub.jupyter.org/tls-terminator" - - name: LEGO_URL - value: "https://acme-v01.api.letsencrypt.org/directory" - ports: - - containerPort: 8080 - readinessProbe: - httpGet: - path: /healthz - port: 8080 - initialDelaySeconds: 5 - timeoutSeconds: 1 + # We need this to get logs immediately + - name: PYTHONUNBUFFERED + value: "True" + volumeMounts: + - name: certificates + mountPath: /etc/acme {{- end }} diff --git a/jupyterhub/templates/proxy/autohttps/ingress-internal.yaml b/jupyterhub/templates/proxy/autohttps/ingress-internal.yaml deleted file mode 100644 index cc3e51a742..0000000000 --- a/jupyterhub/templates/proxy/autohttps/ingress-internal.yaml +++ /dev/null @@ -1,37 +0,0 @@ -{{- $HTTPS := .Values.proxy.https.enabled -}} -{{- $autoHTTPS := and $HTTPS (eq .Values.proxy.https.type "letsencrypt") .Values.proxy.https.hosts -}} -{{- if $autoHTTPS -}} -# This is solely used to provide auto HTTPS with our bundled kube-lego -# -## FUTURE: When k8s 1.20 is released or when we can assume k8s 1.14, switch from -## apiVersion: extensions/v1beta1 to using apiVersion: -## networking.k8s.io/v1beta1. -## -## ref: https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ -## -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: jupyterhub-internal - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} - annotations: - kubernetes.io/ingress.provider: nginx - kubernetes.io/ingress.class: jupyterhub-proxy-tls - hub.jupyter.org/tls-terminator: "true" -spec: - rules: - {{- range $host := .Values.proxy.https.hosts }} - - http: - paths: - - path: / - backend: - serviceName: proxy-http - servicePort: 8000 - host: {{ $host }} - {{- end }} - tls: - - secretName: kubelego-tls-proxy-{{ .Release.Name }} - hosts: - {{- .Values.proxy.https.hosts | toYaml | trimSuffix "\n" | nindent 8 }} -{{- end }} diff --git a/jupyterhub/templates/proxy/autohttps/rbac.yaml b/jupyterhub/templates/proxy/autohttps/rbac.yaml index 5862a6c7c3..3a864c6851 100644 --- a/jupyterhub/templates/proxy/autohttps/rbac.yaml +++ b/jupyterhub/templates/proxy/autohttps/rbac.yaml @@ -1,198 +1,36 @@ {{- $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled) }} {{- $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt")) }} {{- if (and $autoHTTPS .Values.rbac.enabled) -}} -# This is way too many permissions, but apparently the nginx-controller -# is written to sortof assume it is clusterwide ingress provider. -# So we keep this as is, for now. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: autohttps - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: nginx-{{ .Release.Name }} - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} -rules: - - apiGroups: - - "" - resources: - - configmaps - - endpoints - - nodes - - pods - - secrets - verbs: - - list - - watch - - apiGroups: - - "" - resources: - - namespaces - resourceNames: - - "{{ .Release.Namespace }}" - verbs: - - get - - apiGroups: - - "" - resources: - - nodes - verbs: - - get - - apiGroups: - - "" - resources: - - services - verbs: - - get - - list - - update - - watch - - apiGroups: - - extensions - resources: - - ingresses - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - apiGroups: - - extensions - resources: - - ingresses/status - verbs: - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: nginx-{{ .Release.Name }} - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: nginx-{{ .Release.Name }} -subjects: - - kind: ServiceAccount - name: autohttps - namespace: {{ .Release.Namespace }} ---- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: nginx - labels: - {{- include "jupyterhub.labels" . | nindent 4 }} -rules: - - apiGroups: - - "" - resources: - - configmaps - - namespaces - - pods - - secrets - verbs: - - get - - apiGroups: - - "" - resources: - - configmaps - resourceNames: - - ingress-controller-leader-jupyterhub-proxy-tls - verbs: - - get - - update - - apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - apiGroups: - - "" - resources: - - endpoints - verbs: - - create - - get - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: kube-lego + name: autohttps labels: {{- include "jupyterhub.labels" . | nindent 4 }} rules: -- apiGroups: - - "" - resources: - - services - verbs: - - create - - get - - delete -- apiGroups: - - extensions - resources: - - ingresses - verbs: - - get - - update - - create - - list - - patch - - delete - - watch -- apiGroups: - - "" - resources: - - endpoints - - secrets - verbs: - - get - - create - - update +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "patch", "list", "create"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: nginx + name: autohttps labels: {{- include "jupyterhub.labels" . | nindent 4 }} +subjects: +- kind: ServiceAccount + name: autohttps + apiGroup: roleRef: kind: Role - name: nginx + name: autohttps apiGroup: rbac.authorization.k8s.io -subjects: - - kind: ServiceAccount - name: autohttps - namespace: {{ .Release.Namespace }} --- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding +apiVersion: v1 +kind: ServiceAccount metadata: - name: kube-lego + name: autohttps labels: {{- include "jupyterhub.labels" . | nindent 4 }} -roleRef: - kind: Role - name: kube-lego - apiGroup: rbac.authorization.k8s.io -subjects: - - kind: ServiceAccount - name: autohttps - namespace: {{ .Release.Namespace }} {{- end }} diff --git a/jupyterhub/values.yaml b/jupyterhub/values.yaml index ff7ed377df..498edfb934 100644 --- a/jupyterhub/values.yaml +++ b/jupyterhub/values.yaml @@ -146,17 +146,18 @@ proxy: requests: cpu: 200m memory: 512Mi - nginx: + traefik: image: - name: quay.io/kubernetes-ingress-controller/nginx-ingress-controller - tag: 0.15.0 - proxyBodySize: 64m - hstsIncludeSubdomains: 'false' + name: traefik + tag: v2.1 + hsts: + maxAge: 15724800 # About 6 months + includeSubdomains: false resources: {} - lego: + secretSync: image: - name: jetstack/kube-lego - tag: 0.1.7 + name: jupyterhub/k8s-secret-sync + tag: 'set-by-chartpress' resources: {} labels: {} nodeSelector: {} @@ -169,6 +170,8 @@ proxy: #type: letsencrypt, manual, offload, secret letsencrypt: contactEmail: '' + # Specify custom server here (https://acme-staging-v02.api.letsencrypt.org/directory) to hit staging LE + acmeServer: '' manual: key: cert: diff --git a/tools/templates/lint-and-validate-values.yaml b/tools/templates/lint-and-validate-values.yaml index 5fd98f9c88..5da8b74210 100644 --- a/tools/templates/lint-and-validate-values.yaml +++ b/tools/templates/lint-and-validate-values.yaml @@ -83,20 +83,19 @@ proxy: requests: cpu: 100m memory: 512Mi - nginx: - proxyBodySize: 64m + traefik: resources: requests: - cpu: 100m + cpu: 200m memory: 512Mi limits: cpu: 200m memory: 1Gi - lego: + secretSync: resources: requests: cpu: 100m - memory: 512Mi + memory: 128Mi limits: cpu: 200m memory: 1Gi