diff --git a/jupyterhub/templates/proxy/autohttps/_README.md b/jupyterhub/templates/proxy/autohttps/_README.md new file mode 100644 index 0000000000..08bd7bbab5 --- /dev/null +++ b/jupyterhub/templates/proxy/autohttps/_README.md @@ -0,0 +1,9 @@ +# Automatic HTTPS Terminator + +This directory has Kubernetes objects for automatic Let's Encrypt Support. +When enabled, we create a new deployment object that has an nginx-ingress +and kube-lego container in it. This is responsible for requesting, +storing and renewing certificates as needed from Let's Encrypt. + +The only change required outside of this directory is in the `proxy-public` +service, which targets different hubs based on automatic HTTPS status. \ No newline at end of file diff --git a/jupyterhub/templates/proxy/autohttps/deployment.yaml b/jupyterhub/templates/proxy/autohttps/deployment.yaml new file mode 100644 index 0000000000..9a23b068f8 --- /dev/null +++ b/jupyterhub/templates/proxy/autohttps/deployment.yaml @@ -0,0 +1,125 @@ +{{ $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled ) }} +{{ $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt" ) ) }} +{{ if $autoHTTPS }} +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + component: autohttps + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: autohttps +spec: + replicas: 1 + template: + metadata: + labels: + component: autohttps + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + app: kube-lego + hub.jupyter.org/network-access-proxy-http: "true" + spec: + {{- if .Values.rbac.enabled }} + serviceAccountName: autohttps + {{- end }} + nodeSelector: {{ toJson .Values.proxy.nodeSelector }} + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchExpressions: + - key: component + operator: In + values: ['hub'] + - key: release + operator: In + values: [ {{ .Release.Name | quote }} ] + containers: + - name: nginx + image: "{{ .Values.proxy.nginx.image.name }}:{{ .Values.proxy.nginx.image.tag }}" + imagePullPolicy: {{ .Values.proxy.nginx.image.pullPolicy }} + resources: +{{ toYaml .Values.proxy.nginx.resources | indent 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 + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: kube-lego + image: "{{ .Values.proxy.lego.image.name }}:{{ .Values.proxy.lego.image.tag }}" + imagePullPolicy: {{ .Values.proxy.lego.image.pullPolicy }} + resources: +{{ toYaml .Values.proxy.lego.resources | indent 12 }} + 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 + terminationGracePeriodSeconds: 60 +{{ end }} \ No newline at end of file diff --git a/jupyterhub/templates/proxy/ingress-internal.yaml b/jupyterhub/templates/proxy/autohttps/ingress-internal.yaml similarity index 59% rename from jupyterhub/templates/proxy/ingress-internal.yaml rename to jupyterhub/templates/proxy/autohttps/ingress-internal.yaml index a1504cb16f..00d0adb1c8 100644 --- a/jupyterhub/templates/proxy/ingress-internal.yaml +++ b/jupyterhub/templates/proxy/autohttps/ingress-internal.yaml @@ -1,4 +1,6 @@ -{{- if and .Values.proxy.https.enabled .Values.proxy.https.hosts }} +{{ $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled ) }} +{{ $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt" ) ) }} +{{ if $autoHTTPS }} # This is solely used to provide auto HTTPS with our bundled kube-lego apiVersion: extensions/v1beta1 kind: Ingress @@ -20,15 +22,7 @@ spec: host: {{ $host }} {{- end }} tls: - {{- if eq .Values.proxy.https.type "letsencrypt" }} - secretName: kubelego-tls-proxy-{{ .Release.Name }} - {{- else if eq .Values.proxy.https.type "manual" }} - - secretName: manual-tls-proxy-{{ .Release.Name }} - {{- else }} - # unhandled type - # it would be nice if helm had an `error` function - {{ required (printf "https.type must be 'manual' or 'letsencrypt', not '%s'" .Values.proxy.https.type) ._undefined }} - {{- end }} hosts: {{ toYaml .Values.proxy.https.hosts | indent 8 }} {{- end }} diff --git a/jupyterhub/templates/proxy/autohttps/nginx-configmap.yaml b/jupyterhub/templates/proxy/autohttps/nginx-configmap.yaml new file mode 100644 index 0000000000..3c50c4c3bf --- /dev/null +++ b/jupyterhub/templates/proxy/autohttps/nginx-configmap.yaml @@ -0,0 +1,10 @@ +{{ $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 +data: + proxy-body-size: "{{ .Values.proxy.nginx.proxyBodySize }}" +{{ end }} \ No newline at end of file diff --git a/jupyterhub/templates/proxy/rbac.yaml b/jupyterhub/templates/proxy/autohttps/rbac.yaml similarity index 93% rename from jupyterhub/templates/proxy/rbac.yaml rename to jupyterhub/templates/proxy/autohttps/rbac.yaml index fa135273f5..420c5eb057 100644 --- a/jupyterhub/templates/proxy/rbac.yaml +++ b/jupyterhub/templates/proxy/autohttps/rbac.yaml @@ -1,3 +1,6 @@ +{{ $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled ) }} +{{ $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt" ) ) }} +{{ if $autoHTTPS }} {{ if .Values.rbac.enabled -}} # This is way too many permissions, but apparently the nginx-controller # is written to sortof assume it is clusterwide ingress provider. @@ -81,7 +84,7 @@ roleRef: name: nginx-{{ .Release.Name }} subjects: - kind: ServiceAccount - name: proxy + name: autohttps namespace: {{ .Release.Namespace }} --- apiVersion: rbac.authorization.k8s.io/v1beta1 @@ -179,7 +182,7 @@ roleRef: name: nginx subjects: - kind: ServiceAccount - name: proxy + name: autohttps namespace: {{ .Release.Namespace }} --- apiVersion: rbac.authorization.k8s.io/v1beta1 @@ -196,7 +199,7 @@ roleRef: name: kube-lego subjects: - kind: ServiceAccount - name: proxy + name: autohttps namespace: {{ .Release.Namespace }} --- apiVersion: v1 @@ -207,5 +210,6 @@ metadata: chart: {{ .Chart.Name }}-{{ .Chart.Version }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - name: proxy + name: autohttps {{- end }} +{{ end }} \ No newline at end of file diff --git a/jupyterhub/templates/proxy/autohttps/service.yaml b/jupyterhub/templates/proxy/autohttps/service.yaml new file mode 100644 index 0000000000..d57f5bdb18 --- /dev/null +++ b/jupyterhub/templates/proxy/autohttps/service.yaml @@ -0,0 +1,21 @@ +{{ $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled ) }} +{{ $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt" ) ) }} +{{ if $autoHTTPS }} +apiVersion: v1 +kind: Service +metadata: + name: proxy-http + # toYaml + indent seem a lot more unstable & error prone + annotations: {{ toJson .Values.proxy.service.annotations }} + labels: {{ toJson .Values.proxy.service.labels }} +spec: + type: ClusterIP + selector: + name: proxy + component: proxy + release: {{ .Release.Name }} + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 +{{ end }} \ No newline at end of file diff --git a/jupyterhub/templates/proxy/deployment.yaml b/jupyterhub/templates/proxy/deployment.yaml index 500b25230e..389a160df8 100644 --- a/jupyterhub/templates/proxy/deployment.yaml +++ b/jupyterhub/templates/proxy/deployment.yaml @@ -1,3 +1,4 @@ +{{ $manualHTTPS := (and .Values.proxy.https.enabled (eq .Values.proxy.https.type "manual" ) ) }} apiVersion: extensions/v1beta1 kind: Deployment metadata: @@ -15,22 +16,14 @@ spec: # This lets us autorestart when the secret changes! checksum/hub-secret: {{ include (print $.Template.BasePath "/hub/secret.yaml") . | sha256sum }} checksum/proxy-secret: {{ include (print $.Template.BasePath "/proxy/secret.yaml") . | sha256sum }} - hub.jupyter.org/https-type: {{ .Values.proxy.https.type }} labels: name: proxy component: proxy release: {{ .Release.Name }} heritage: {{ .Release.Service }} - {{- if eq .Values.proxy.https.type "letsencrypt" }} - # required for kube-lego to work - app: kube-lego - {{- end }} hub.jupyter.org/network-access-hub: "true" hub.jupyter.org/network-access-singleuser: "true" spec: - {{- if .Values.rbac.enabled }} - serviceAccountName: proxy - {{- end }} nodeSelector: {{ toJson .Values.proxy.nodeSelector }} affinity: podAffinity: @@ -46,96 +39,40 @@ spec: - key: release operator: In values: [ {{ .Release.Name | quote }} ] + {{ if $manualHTTPS }} + volumes: + - name: tls-secret + secret: + secretName: manual-tls-proxy-{{ .Release.Name }} + {{ end }} containers: - - name: nginx - image: "{{ .Values.proxy.nginx.image.name }}:{{ .Values.proxy.nginx.image.tag }}" - imagePullPolicy: {{ .Values.proxy.nginx.image.pullPolicy }} - resources: -{{ toYaml .Values.proxy.nginx.resources | indent 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 - ports: - - name: http - containerPort: 80 - protocol: TCP - - name: https - containerPort: 443 - protocol: TCP - {{ if and .Values.proxy.https.hosts (and .Values.proxy.https.enabled (eq .Values.proxy.https.type "letsencrypt" ) ) -}} - - name: kube-lego - image: "{{ .Values.proxy.lego.image.name }}:{{ .Values.proxy.lego.image.tag }}" - imagePullPolicy: {{ .Values.proxy.lego.image.pullPolicy }} - resources: -{{ toYaml .Values.proxy.lego.resources | indent 12 }} - 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 - {{- end }} - name: chp image: {{ .Values.proxy.chp.image.name }}:{{ .Values.proxy.chp.image.tag }} - {{- if .Values.proxy.chp.cmd }} - command: {{ toJson .Values.proxy.chp.cmd }} - {{- end }} + command: + - configurable-http-proxy + - --ip=0.0.0.0 + - --api-ip=0.0.0.0 + - --api-port=8001 + - --default-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT) + - --error-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT)/hub/error + {{ if $manualHTTPS }} + - --port=8443 + - --redirect-port=8000 + - --ssl-key=/etc/chp/tls/tls.key + - --ssl-cert=/etc/chp/tls/tls.crt + {{ else }} + - --port=8000 + {{ end }} + {{ if .Values.debug.enabled }} + - --log-level debug + {{ end }} resources: + {{ if $manualHTTPS }} + volumeMounts: + - name: tls-secret + mountPath: /etc/chp/tls + readOnly: true + {{ end }} {{ toYaml .Values.proxy.chp.resources | indent 12 }} env: - name: CONFIGPROXY_AUTH_TOKEN @@ -145,6 +82,10 @@ spec: key: proxy.token imagePullPolicy: {{ .Values.proxy.chp.image.pullPolicy }} ports: + {{ if $manualHTTPS }} + - containerPort: 8443 + name: proxy-https + {{ end }} - containerPort: 8000 name: proxy-public - containerPort: 8001 diff --git a/jupyterhub/templates/proxy/nginx-configmap.yaml b/jupyterhub/templates/proxy/nginx-configmap.yaml deleted file mode 100644 index 7a625eb2fc..0000000000 --- a/jupyterhub/templates/proxy/nginx-configmap.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: nginx-proxy-config -data: - proxy-body-size: "{{ .Values.proxy.nginx.proxyBodySize }}" diff --git a/jupyterhub/templates/proxy/secret.yaml b/jupyterhub/templates/proxy/secret.yaml index 7b99de5cfe..beb66a9577 100644 --- a/jupyterhub/templates/proxy/secret.yaml +++ b/jupyterhub/templates/proxy/secret.yaml @@ -1,4 +1,5 @@ -{{- if and .Values.proxy.https.enabled (eq .Values.proxy.https.type "manual") }} +{{ $manualHTTPS := (and .Values.proxy.https.enabled (eq .Values.proxy.https.type "manual" ) ) }} +{{- if $manualHTTPS }} apiVersion: v1 kind: Secret metadata: diff --git a/jupyterhub/templates/proxy/service.yaml b/jupyterhub/templates/proxy/service.yaml index 6a9fbccf8f..b6a918500f 100644 --- a/jupyterhub/templates/proxy/service.yaml +++ b/jupyterhub/templates/proxy/service.yaml @@ -1,3 +1,6 @@ +{{ $HTTPS := (and .Values.proxy.https.hosts .Values.proxy.https.enabled ) }} +{{ $autoHTTPS := (and $HTTPS (eq .Values.proxy.https.type "letsencrypt" ) ) }} +{{ $manualHTTPS := (and $HTTPS (eq .Values.proxy.https.type "manual" ) ) }} apiVersion: v1 kind: Service metadata: @@ -14,24 +17,6 @@ spec: --- apiVersion: v1 kind: Service -metadata: - name: proxy-http - # toYaml + indent seem a lot more unstable & error prone - annotations: {{ toJson .Values.proxy.service.annotations }} - labels: {{ toJson .Values.proxy.service.labels }} -spec: - type: ClusterIP - selector: - name: proxy - component: proxy - release: {{ .Release.Name }} - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 ---- -apiVersion: v1 -kind: Service metadata: labels: chart: {{ .Chart.Name }}-{{ .Chart.Version }} @@ -51,23 +36,36 @@ spec: - name: http port: 80 protocol: TCP + {{ if $autoHTTPS }} targetPort: 80 + {{ else }} + targetPort: 8000 + {{ end}} # allow proxy.service.nodePort for http {{ if .Values.proxy.service.nodePorts.http -}} nodePort: {{ .Values.proxy.service.nodePorts.http }} {{- end }} + {{ if $HTTPS }} - name: https port: 443 protocol: TCP + {{ if $manualHTTPS }} + targetPort: 8443 + {{ else }} targetPort: 443 + {{ end }} {{ if .Values.proxy.service.nodePorts.https -}} nodePort: {{ .Values.proxy.service.nodePorts.https }} {{- end }} + {{ end }} selector: - name: proxy + {{ if $autoHTTPS}} + component: autohttps + {{ else }} component: proxy + {{ end }} release: {{ .Release.Name }} type: {{ .Values.proxy.service.type }} {{ if .Values.proxy.service.loadBalancerIP -}} loadBalancerIP: {{ .Values.proxy.service.loadBalancerIP }} - {{- end }} + {{- end }} \ No newline at end of file diff --git a/jupyterhub/values.yaml b/jupyterhub/values.yaml index 30aa90fa45..38022a4084 100644 --- a/jupyterhub/values.yaml +++ b/jupyterhub/values.yaml @@ -63,15 +63,6 @@ proxy: name: jupyterhub/configurable-http-proxy tag: 3.0.0 pullPolicy: IfNotPresent - cmd: - - configurable-http-proxy - - --ip=0.0.0.0 - - --port=8000 - - --api-ip=0.0.0.0 - - --api-port=8001 - - --default-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT) - - --error-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT)/hub/error - - --log-level=debug resources: requests: cpu: 0.2