Skip to content

Commit

Permalink
controller - single ingress, alt certs
Browse files Browse the repository at this point in the history
  • Loading branch information
qrkourier committed Oct 15, 2024
1 parent 51a156d commit c00b265
Show file tree
Hide file tree
Showing 13 changed files with 624 additions and 160 deletions.
8 changes: 4 additions & 4 deletions charts/ziti-controller/Chart.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
dependencies:
- name: cert-manager
repository: https://charts.jetstack.io
version: v1.11.5
version: v1.14.7
- name: trust-manager
repository: https://charts.jetstack.io
version: v0.7.1
- name: ingress-nginx
repository: https://kubernetes.github.io/ingress-nginx/
version: 4.5.2
digest: sha256:45621df69ea3c636ef2805d11bd9dd78f7400d008576d4cc48698ec31c52dd39
generated: "2024-06-04T19:46:32.266420465-04:00"
version: 4.10.4
digest: sha256:78a699dc2893617bb3440db32e9bcabed6a105695193e047ecd02c9d88b59071
generated: "2024-10-04T17:15:32.404071493-04:00"
10 changes: 5 additions & 5 deletions charts/ziti-controller/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
apiVersion: v3
appVersion: 1.1.9
apiVersion: v2
appVersion: 1.1.15
dependencies:
- condition: cert-manager.enabled
name: cert-manager
repository: https://charts.jetstack.io
version: ~1.11.0
version: ~1.14.0
- condition: trust-manager.enabled
name: trust-manager
repository: https://charts.jetstack.io
version: ~0.7.0
- condition: ingress-nginx.enabled
name: ingress-nginx
repository: https://kubernetes.github.io/ingress-nginx/
version: ~4.5.2
version: ~4.10.1
description: Host an OpenZiti controller in Kubernetes
name: ziti-controller
type: application
version: 1.0.16
version: 1.0.19
136 changes: 106 additions & 30 deletions charts/ziti-controller/README.md

Large diffs are not rendered by default.

73 changes: 66 additions & 7 deletions charts/ziti-controller/README.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@

## Overview

This chart runs a Ziti controller in Kubernetes. It uses the custom resources provided by [cert-manager](https://cert-manager.io/docs/installation/) and [trust-manager](https://cert-manager.io/docs/projects/trust-manager/#installation), i.e., Issuer, Certificate, and Bundle. Delete the controller pod after an upgrade for the new controller configuration to take effect.
This chart runs a Ziti controller in Kubernetes. It uses the custom resources provided by [cert-manager](https://cert-manager.io/docs/installation/) and [trust-manager](https://cert-manager.io/docs/projects/trust-manager/#installation), i.e., Issuer, Certificate, and Bundle.

Here's a checklist that must be satisfied for each of the controller's TLS servers: router control plane (`ctrlPlane`), each web listener(s) (`clientApi`, at least).

1. Each of the controller's TLS servers must have an advertised host (FQDN) and TCP port. The chart ensures the DNS SAN is added for this address. The port is always 443 if ingress is enabled for `ClusterIP` service.
1. A K8s Service must be configured and reachable by all clients. (i.e., type `ClusterIP`+`Ingress`, `NodePort`, or `LoadBalancer`)
1. Each K8s Service must be configured for TLS passthrough (i.e., server TLS must not be terminated by an intermediary LB).
1. All clients must access the controller through the advertised address(es).
The client API must be published with a TLS passthrough Ingress, NodePort, or LoadBalancer. The ctrl plane and management API share the client API's TLS listener, so they're reached through the same address by default.

## Requirements

Expand Down Expand Up @@ -209,4 +204,68 @@ For more information, please check [here](https://openziti.io/docs/learn/core-co
* Deploy Prometheus scraper configuration when `prometheus.enabled = true`
* cert-manager allows issuing only one cert per key, i.e., ClientCertKeyReuseIssue prevents us from issuing a user cert and server cert backed by same private key, hence the controller config.yaml re-uses server certs in place of user certs to allow startup and testing to continue

## Alternative Web Server Certificates

The purpose of the alt_server_certs feature is to bind a publicly trusted server certificate to the controller's web listener. This is useful for publishing the controller's client API with a different DNS name for BrowZer and console clients that must verify the controller's identity with their OS trusted root store.

### Request an alternative server certificate from a cert-manager issuer

The most automatic way to bind an alt cert is the certManager mode provided by this chart. This example implies you have separately created a cert-manager ClusterIssuer named "cloudflare-dns01-issuer" that is able to obtain a certificate for the specified DNS name. If publishing the client API's alternative DNS name as a separate Ingress, you may reference that advertised host when requesting the alternative server certificate as shown here with an inline template to ensure they match.

<!-- {% raw %} "raw" escapes this code block's handlebars from GH Pages Jekyll, and {{``}} escapes the Go template from helm-docs -->
```yaml
clientApi:
advertisedHost: edge.ziti.example.com
ingress:
enabled: true
ingressClassName: nginx
annotations:
kubernetes.io/ingress.allow-http: "false"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
service:
enabled: true
type: ClusterIP
altIngress:
enabled: true
ingressClassName: nginx
advertisedHost: alt-edge.ziti.example.com # this must be different from clientApi.advertisedHost and must match one of the dnsNames in the altServerCert
annotations:
kubernetes.io/ingress.allow-http: "false"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"

webBindingPki:
enabled: true
altServerCerts:
- mode: certManager
secretName: my-alt-server-cert
dnsNames:
- "{{`{{ .Values.clientApi.altIngress.advertisedHost }}`}}"
issuerRef:
group: cert-manager.io
kind: ClusterIssuer
name: cloudflare-dns01-issuer
mountPath: /etc/ziti/alt-server-cert
```
<!-- {% endraw %} -->

### Use an alternative certificate and key from a tls secret

The alternative server certificate and key may also be provided from a Kubernetes TLS secret. Declare the tls secret in the additionalVolumes section and reference it in the altServerCerts section.

<!-- {% raw %} "raw" escapes this code block's handlebars from GH Pages Jekyll, and {{``}} escapes the Go template from helm-docs -->
```yaml
additionalVolumes:
- name: my-alt-server-cert
volumeType: secret
mountPath: /etc/ziti/my-alt-server-cert
secretName: my-alt-server-cert

webBindingPki:
altServerCerts:
- mode: secret
secretName: my-alt-server-cert
```
<!-- {% endraw %} -->


<!-- README.md generated by helm-docs from README.md.gotmpl -->
56 changes: 54 additions & 2 deletions charts/ziti-controller/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,60 @@ ctrl-plane-cas.crt
{{- define "ziti-controller.console" -}}
{{- if .Values.managementApi.service.enabled -}}
https://{{ .Values.managementApi.advertisedHost }}:{{ .Values.managementApi.advertisedPort }}/zac/
https://{{ include "ziti-controller.tplOrLiteral" (dict "value" .Values.managementApi.advertisedHost "context" .) }}:{{ include "ziti-controller.tplOrLiteral" (dict "value" .Values.managementApi.advertisedPort "context" .) }}/zac/
{{- else -}}
https://{{ .Values.clientApi.advertisedHost }}:{{ .Values.clientApi.advertisedPort }}/zac/
{{- end }}
{{- end }}
{{- end }}
{{/*
help the alt-certificate template find the members of webBindingPki.altServerCerts
that are managed by cert-manager
*/}}
{{- define "ziti-controller.getCertManagerAltServerCerts" -}}
{{- $filteredCerts := list -}}
{{- range . -}}
{{- if eq .mode "certManager" -}}
{{- $filteredCerts = append $filteredCerts . -}}
{{- end -}}
{{- end -}}
{{- dict "certManagerCerts" $filteredCerts | toJson -}}
{{- end -}}
{{/*
help the configmap template find the mount path of an alternative server
certificate by looking up the secret name in the list of additional volumes
*/}}
{{- define "ziti-controller.lookupVolumeMountPath" -}}
{{- $secretName := .secretName -}}
{{- $matchingVolumeMountPath := "" -}}
{{- range .additionalVolumes }}
{{- if and (eq .volumeType "secret") (eq .secretName $secretName) }}
{{- $matchingVolumeMountPath = .mountPath }}
{{- end }}
{{- end }}
{{- if $matchingVolumeMountPath }}
{{- $matchingVolumeMountPath }}
{{- else }}
{{- fail (printf "No matching additionalVolume found for secretName: %s" $secretName) }}
{{- end }}
{{- end -}}
{{/*
render as an inline template if the value is a string containing a go template,
else return the literal value
*/}}
{{- define "ziti-controller.tplOrLiteral" -}}
{{- $value := .value -}}
{{- $context := .context -}}
{{- if typeIs "string" $value -}}
{{- $trimmed := trim $value -}}
{{- if and (hasPrefix "{{" $trimmed) (hasSuffix "}}" $trimmed) -}}
{{- tpl $value $context -}}
{{- else -}}
{{- $value -}}
{{- end -}}
{{- else -}}
{{- $value -}}
{{- end -}}
{{- end -}}
37 changes: 37 additions & 0 deletions charts/ziti-controller/templates/alt-certificate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# openziti-helm-charts/charts/ziti-controller/templates/alt-certificate.yaml
{{- if gt (len $.Values.webBindingPki.altServerCerts) 0 -}}
{{- $root := . }}
{{- $values := $.Values }}
{{- $certs := ((include "ziti-controller.getCertManagerAltServerCerts" $values.webBindingPki.altServerCerts) | fromJson).certManagerCerts -}}

{{- if gt (len $certs) 0 }}
{{- range $index, $cert := $certs }}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ printf "%s-alt-cert-%d" (include "ziti-controller.fullname" $root) $index }}
namespace: {{ $root.Release.Namespace }}
spec:
{{- if $cert.secretName }}
secretName: {{ $cert.secretName | quote }}
{{- else }}
{{- fail (printf "No secretName found for cert %d" $index) -}}
{{- end }}
issuerRef:
{{- toYaml $cert.issuerRef | nindent 4 }}
{{- if gt (len $cert.dnsNames) 0 }}
dnsNames:
{{- range $cert.dnsNames }}
- {{ include "ziti-controller.tplOrLiteral" (dict "value" . "context" $root) }}
{{- end }}
{{- else }}
dnsNames: []
{{- end }}
usages:
- digital signature
- key encipherment
- server auth
{{- end }}
{{- end }}
{{- end }}
39 changes: 31 additions & 8 deletions charts/ziti-controller/templates/ca-router-ctrl-identity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ spec:
algorithm: RSA
size: 4096
usages:
- client auth # remove when we fix #ClientCertKeyReuseIssue
- server auth
- digital signature
- content commitment
Expand All @@ -115,13 +114,15 @@ spec:
- {{ include "ziti-controller.fullname" . }}-ctrl.{{ .Release.Namespace }}
- {{ include "ziti-controller.fullname" . }}-ctrl.{{ .Release.Namespace }}.svc
- {{ include "ziti-controller.fullname" . }}-ctrl.{{ .Release.Namespace }}.svc.{{ .Values.ca.clusterDomain }}
- {{ .Values.ctrlPlane.advertisedHost | default (printf "%s-ctrl.%s.svc.%s" .Release.Name .Release.Namespace .Values.ca.clusterDomain)}}
- {{ include "ziti-controller.tplOrLiteral" (dict "value" .Values.ctrlPlane.advertisedHost "context" .) | default (printf "%s-ctrl.%s.svc.%s" .Release.Name .Release.Namespace .Values.ca.clusterDomain)}}
{{- if .Values.ctrlPlane.dnsNames }}
{{- range .Values.ctrlPlane.dnsNames }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if and .Values.clientApi.service.enabled (not .Values.webBindingPki.enabled) }}
{{/* add client API DNS SANs if client API does not have a separate PKI or
if ctrl plane shares a TLS listener with the client API */}}
{{- if and .Values.clientApi.service.enabled (or (not .Values.webBindingPki.enabled) (not .Values.ctrlPlane.service.enabled)) }}
- {{ include "ziti-controller.fullname" . }}-client
- {{ include "ziti-controller.fullname" . }}-client.{{ .Release.Namespace }}
- {{ include "ziti-controller.fullname" . }}-client.{{ .Release.Namespace }}.svc
Expand All @@ -133,15 +134,15 @@ spec:
{{- end }}
{{- end }}
{{- end }}
{{- if and .Values.managementApi.service.enabled (not .Values.webBindingPki.enabled) }}
{{/* add management API DNS SANs if management API does not have a separate
PKI or it shares a TLS listener with the ctrl plane */}}
{{- if and .Values.managementApi.service.enabled (or (not .Values.webBindingPki.enabled) (not .Values.ctrlPlane.service.enabled)) }}
- {{ include "ziti-controller.fullname" . }}-mgmt
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}.svc
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}.svc.{{ .Values.ca.clusterDomain }}
{{- if .Values.managementApi.advertisedHost }}
- {{ .Values.managementApi.advertisedHost }}
{{- end }}
{{- if .Values.managementApi.dnsNames }}
- {{ include "ziti-controller.tplOrLiteral" (dict "value" .Values.managementApi.advertisedHost "context" .) }}
{{- if gt (len .Values.managementApi.dnsNames) 0 }}
{{- range .Values.managementApi.dnsNames }}
- {{ . | quote }}
{{- end }}
Expand All @@ -151,6 +152,28 @@ spec:
# - spiffe://{{ .Values.ca.clusterDomain }}/ns/sandbox/sa/example
ipAddresses:
- 127.0.0.1
- "::1"
issuerRef:
kind: Issuer
name: {{ include "ziti-controller.fullname" . }}-ctrl-plane-intermediate-issuer

---
# the controller's client certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "ziti-controller.fullname" . }}-ctrl-plane-client-identity
labels:
{{- include "ziti-controller.labels" . | nindent 4 }}
spec:
isCA: false
commonName: {{ include "ziti-controller.fullname" . }}-ctrl-plane-client-identity
secretName: {{ include "ziti-controller.fullname" . }}-ctrl-plane-client-identity-secret
privateKey:
algorithm: ECDSA
size: 256
usages:
- client auth
issuerRef:
kind: Issuer
name: {{ include "ziti-controller.fullname" . }}-ctrl-plane-intermediate-issuer
48 changes: 24 additions & 24 deletions charts/ziti-controller/templates/ca-web-identity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,8 @@ spec:
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}.svc
- {{ include "ziti-controller.fullname" . }}-mgmt.{{ .Release.Namespace }}.svc.{{ .Values.ca.clusterDomain }}
{{- if .Values.managementApi.advertisedHost }}
- {{ .Values.managementApi.advertisedHost }}
{{- end }}
{{- if .Values.managementApi.dnsNames }}
- {{ include "ziti-controller.tplOrLiteral" (dict "value" .Values.managementApi.advertisedHost "context" .) }}
{{- if gt (len .Values.managementApi.dnsNames) 0 }}
{{- range .Values.managementApi.dnsNames }}
- {{ . | quote }}
{{- end }}
Expand All @@ -134,28 +132,30 @@ spec:
# - spiffe://{{ .Values.ca.clusterDomain }}/ns/sandbox/sa/example
ipAddresses:
- 127.0.0.1
- "::1"
issuerRef:
kind: Issuer
name: {{ include "ziti-controller.fullname" . }}-web-intermediate-issuer

# #ClientCertKeyReuseIssue: Currently we don't use separate client/server certs as they require to be issued
# via the same private key - I currently have no Idea how to solve this with CertManager
# ---
# # the controller's client certificate
# apiVersion: cert-manager.io/v1
# kind: Certificate
# metadata:
# name: ziti-ctrl-client
# spec:
# isCA: false
# commonName: ziti-ctrl-client
# secretName: ziti-controller-client-cert-secret
# privateKey:
# algorithm: ECDSA
# size: 256
# usages:
# - client auth
# issuerRef:
# kind: Issuer
# name: ziti-controller-intermediate-ca-issuer
{{- end }}

---
# the controller's web client identity
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "ziti-controller.fullname" . }}-web-client-identity
labels:
{{- include "ziti-controller.labels" . | nindent 4 }}
spec:
isCA: false
commonName: {{ include "ziti-controller.fullname" . }}-web-client-identity
secretName: {{ include "ziti-controller.fullname" . }}-web-client-identity-secret
privateKey:
algorithm: ECDSA
size: 256
usages:
- client auth
issuerRef:
kind: Issuer
name: {{ include "ziti-controller.fullname" . }}-web-intermediate-issuer
Loading

0 comments on commit c00b265

Please sign in to comment.