From 7cb5200201dc65c09133b483416bb6f589205dc9 Mon Sep 17 00:00:00 2001 From: Michael Welles Date: Tue, 21 Jul 2026 00:04:39 -0400 Subject: [PATCH] feat(dgraph)!: synthesize the --tls superflag and switch probes to HTTPS when TLS is enabled BREAKING CHANGE: alpha.tls.enabled/zero.tls.enabled now synthesize the --tls superflag from the tls block (internalPort/clientName/clientAuthType) and switch health probes to HTTPS. Remove any --tls from extraFlags. A cert-requiring clientAuthType (REQUIREANY/REQUIREANDVERIFY) with httpGet probes fails rendering; use VERIFYIFGIVEN or exec probes. --- charts/dgraph/README.md | 10 +++++ charts/dgraph/templates/_helpers.tpl | 45 +++++++++++++++++++ .../dgraph/templates/alpha/statefulset.yaml | 31 ++++++++++++- charts/dgraph/templates/zero/statefulset.yaml | 33 +++++++++++++- charts/dgraph/values.yaml | 26 +++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) diff --git a/charts/dgraph/README.md b/charts/dgraph/README.md index 6cb55a7ad..f8f75270a 100644 --- a/charts/dgraph/README.md +++ b/charts/dgraph/README.md @@ -46,6 +46,10 @@ No manual intervention is required. Also review the [additional breaking changes **ACL and encryption flags now auto-activate**: Setting `alpha.acl.enabled: true` (or `alpha.encryption.enabled: true`) now synthesizes the matching `--acl` (or `--encryption`) superflag onto the Alpha command automatically; previously these flags had to be added by hand through `alpha.extraFlags`. If you already pass `--acl` or `--encryption` through `alpha.extraFlags`, remove it — the chart fails rendering rather than pass the flag twice. The chart points the flag at `/dgraph/acl/` and `/dgraph/enc/`, which default to `hmac_secret_file` and `enc_key_file`; override those keys if your Secret stores the file under a different name. +**TLS now activates from the tls block**: Setting `alpha.tls.enabled: true` (or `zero.tls.enabled: true`) now synthesizes the `--tls` superflag onto the Alpha (or Zero) command; previously the flag had to be added by hand through `extraFlags`. If you already pass `--tls` through `alpha.extraFlags` or `zero.extraFlags`, remove it — the chart fails rendering rather than pass the flag twice. Configure TLS through the new `tls.internalPort`, `tls.clientName`, and `tls.clientAuthType` keys; the chart reads the cert files from `/dgraph/tls` (`ca.crt`, `node.crt`, `node.key`, and `client..crt`/`.key`). + +**Health probes use HTTPS when TLS is enabled**: With `alpha.tls.enabled: true` (or `zero.tls.enabled: true`), the built-in httpGet startup, liveness, and readiness probes switch to `scheme: HTTPS`. A cert-requiring `clientAuthType` (`REQUIREANY` or `REQUIREANDVERIFY`) makes every client present a certificate, which the kubelet's probes cannot; the chart fails rendering in that case. Set `clientAuthType: VERIFYIFGIVEN`, or supply exec probes through `customStartupProbe`/`customLivenessProbe`/`customReadinessProbe`. A cert-requiring `clientAuthType` also requires `clientName` so in-cluster callers (inter-node TLS) can present a client cert. + ### Installing the Chart To install the chart with the release name `my-release`: @@ -144,6 +148,9 @@ The following table lists the configurable parameters of the `dgraph` chart and | `zero.customStartupProbe` | Zero custom startup probes (if `zero.startupProbe` not enabled) | `{}` | | `zero.customLivenessProbe` | Zero custom liveness probes (if `zero.livenessProbe` not enabled) | `{}` | | `zero.customReadinessProbe` | Zero custom readiness probes (if `zero.readinessProbe` not enabled) | `{}` | +| `zero.tls.internalPort` | Enable TLS on Zero's internal gRPC port (synthesized into `--tls`) | `true` | +| `zero.tls.clientName` | Client cert basename for Zero `--tls` (empty omits the client cert) | `""` | +| `zero.tls.clientAuthType` | Zero `--tls` client-auth-type, e.g. `REQUIREANDVERIFY` (empty omits) | `""` | | `alpha.name` | Alpha component name | `alpha` | | `alpha.metrics.enabled` | Add annotations for Prometheus metric scraping | `true` | | `alpha.extraAnnotations` | Specify annotations for template metadata | `{}` | @@ -185,6 +192,9 @@ The following table lists the configurable parameters of the `dgraph` chart and | `alpha.securityContext.runAsUser` | User ID for the Alpha container | `1001` | | `alpha.tls.enabled` | Alpha service TLS enabled | `false` | | `alpha.tls.files` | Alpha service TLS key and certificate files stored as secrets | `false` | +| `alpha.tls.internalPort` | Enable TLS on Alpha's internal gRPC port (synthesized into `--tls`) | `true` | +| `alpha.tls.clientName` | Client cert basename for Alpha `--tls` (empty omits the client cert) | `""` | +| `alpha.tls.clientAuthType` | Alpha `--tls` client-auth-type, e.g. `REQUIREANDVERIFY` (empty omits) | `""` | | `alpha.encryption.enabled` | Alpha Encryption at Rest enabled (auto-adds `--encryption`) | `false` | | `alpha.encryption.keyFile` | Filename/key of the encryption key within the mounted Secret | `enc_key_file` | | `alpha.encryption.existingSecret` | Name of a pre-created Secret holding the encryption key (suppresses the chart's own) | `""` | diff --git a/charts/dgraph/templates/_helpers.tpl b/charts/dgraph/templates/_helpers.tpl index af6fa3c41..1345fbd4c 100644 --- a/charts/dgraph/templates/_helpers.tpl +++ b/charts/dgraph/templates/_helpers.tpl @@ -328,3 +328,48 @@ leading space, "-v= --logtostderr=" plus --vmodule / --alsologtostderr {{- if .logDir }}{{ printf " --log_dir=%s" .logDir }}{{ end -}} {{- end -}} {{- end -}} + +{{/* +Does this tls block force every client to present a certificate? + +client-auth-type mirrors Go's crypto/tls.ClientAuthType, which separates +"require" from "verify". REQUIREANY and REQUIREANDVERIFY are the only values +that force a cert (REQUIREANY never verifies it, REQUIREANDVERIFY does). +VERIFYIFGIVEN leaves the cert optional but verifies one that is presented; +REQUEST asks for a cert and neither requires nor verifies it; "" and OFF +disable client auth outright. + +Returns the STRING "true" or "" (empty) -- NOT a boolean. Compare it as a +string: eq (include "dgraph.tls.certRequired" (dict "tls" .Values.alpha.tls)) "true". + +Single source of truth for the alpha/zero probe guards. Keep them reading from +here: an inline re-derivation drifts. +*/}} +{{- define "dgraph.tls.certRequired" -}} +{{- $t := .tls.clientAuthType | default "" -}} +{{- if or (eq $t "REQUIREANDVERIFY") (eq $t "REQUIREANY") -}}true{{- end -}} +{{- end -}} + +{{/* +Compose Dgraph's --tls superflag from a tier's tls map. Pass a dict +{"tls": .Values.alpha.tls, "path": "/dgraph/tls"}. Filenames follow the output +of scripts/make_tls_secrets.sh (ca.crt, node.crt, node.key, +client..crt/.key). client-cert/key and client-auth-type are emitted only +when the corresponding values are set. +*/}} +{{- define "dgraph.tlsFlag" -}} +{{- /* internalPort defaults to true (values.yaml), but Helm's `default` treats a + boolean false as empty, so an explicit `false` would be flipped back to the + default. Use a nil check so nil -> true while honoring an explicit false. */}} +{{- $ip := .tls.internalPort -}} +{{- if kindIs "invalid" $ip -}}{{- $ip = true -}}{{- end -}} +{{- $opts := list (printf "ca-cert=%s/ca.crt" .path) (printf "server-cert=%s/node.crt" .path) (printf "server-key=%s/node.key" .path) (printf "internal-port=%v" $ip) -}} +{{- if .tls.clientName -}} +{{- $opts = append $opts (printf "client-cert=%s/client.%s.crt" .path .tls.clientName) -}} +{{- $opts = append $opts (printf "client-key=%s/client.%s.key" .path .tls.clientName) -}} +{{- end -}} +{{- if .tls.clientAuthType -}} +{{- $opts = append $opts (printf "client-auth-type=%s" .tls.clientAuthType) -}} +{{- end -}} +{{- printf "--tls \"%s;\"" (join "; " $opts) -}} +{{- end -}} diff --git a/charts/dgraph/templates/alpha/statefulset.yaml b/charts/dgraph/templates/alpha/statefulset.yaml index f4fdfcea9..c005eaf72 100644 --- a/charts/dgraph/templates/alpha/statefulset.yaml +++ b/charts/dgraph/templates/alpha/statefulset.yaml @@ -23,6 +23,10 @@ {{- $hasMinioKeys := include "dgraph.backups.keys.minio.enabled" . -}} {{- $backupsEnabled := or .Values.backups.full.enabled .Values.backups.incremental.enabled }} {{- $initContainerEnabled := or .Values.alpha.initContainers.init.enabled .Values.alpha.extraInitContainers }} +{{- /* native-TLS is active for alpha when alpha.tls is on. Computed once here (a + bool) and reused for the --tls superflag, the HTTPS probe scheme, and the + extraFlags/clientAuthType guards below. */}} +{{- $nativeTLS := .Values.alpha.tls.enabled }} {{- /* ACL and encryption-at-rest activate from `enabled` below by synthesizing the matching superflag onto the alpha command. Fail rather than emit a duplicate flag if the operator also hand-set it in extraFlags. */}} @@ -32,6 +36,22 @@ {{- if and .Values.alpha.encryption.enabled (contains "--encryption" (.Values.alpha.extraFlags | default "")) }} {{- fail "alpha.encryption.enabled synthesizes the --encryption superflag, but alpha.extraFlags already contains --encryption. Remove --encryption from alpha.extraFlags (configure alpha.encryption.keyFile / alpha.encryption.existingSecret instead) so the flag is not passed twice." }} {{- end }} +{{- /* native TLS synthesizes --tls from alpha.tls; a hand-set --tls in extraFlags + would be passed twice. */}} +{{- if and $nativeTLS (contains "--tls" (.Values.alpha.extraFlags | default "")) }} +{{- fail "alpha.extraFlags contains --tls, but the chart synthesizes it when alpha.tls.enabled=true. Remove --tls from alpha.extraFlags and set alpha.tls.internalPort / clientName / clientAuthType instead." }} +{{- end }} +{{- /* client-auth-type applies to the external ports the built-in probes hit, so a + cert-requiring mode breaks the certless kubelet probe handshake. */}} +{{- $certRequiringAuth := eq (include "dgraph.tls.certRequired" (dict "tls" .Values.alpha.tls)) "true" }} +{{- if and $nativeTLS $certRequiringAuth }} +{{- if not .Values.alpha.tls.clientName }} +{{- fail (printf "alpha.tls.clientAuthType=%s requires alpha.tls.clientName so in-cluster callers (inter-node TLS) can present a client certificate." .Values.alpha.tls.clientAuthType) }} +{{- end }} +{{- if or (and .Values.alpha.startupProbe.enabled (not .Values.alpha.customStartupProbe)) (and .Values.alpha.livenessProbe.enabled (not .Values.alpha.customLivenessProbe)) (and .Values.alpha.readinessProbe.enabled (not .Values.alpha.customReadinessProbe)) }} +{{- fail (printf "alpha.tls.clientAuthType=%s forces every client to present a certificate, which the built-in httpGet probes cannot do (the kubelet has no client cert). Relax alpha.tls.clientAuthType (e.g. VERIFYIFGIVEN) on the external ports, or supply alpha.customStartupProbe / customLivenessProbe / customReadinessProbe (exec probes that present the client cert)." .Values.alpha.tls.clientAuthType) }} +{{- end }} +{{- end }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -223,7 +243,7 @@ spec: {{- /* TODO: Remove awk-gsub once dgraph-io/dgraph#6837 is merged and back-ported. */}} - | set -ex - exec dgraph alpha --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):7080 --zero {{ template "multi_zeros" . }}{{ template "dgraph.logFlags" .Values.alpha }} {{ .Values.alpha.extraFlags }}{{- if .Values.alpha.acl.enabled }} --acl "secret-file=/dgraph/acl/{{ .Values.alpha.acl.secretFile }};"{{- end }}{{- if .Values.alpha.encryption.enabled }} --encryption "key-file=/dgraph/enc/{{ .Values.alpha.encryption.keyFile }};"{{- end }} + exec dgraph alpha --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):7080 --zero {{ template "multi_zeros" . }}{{ template "dgraph.logFlags" .Values.alpha }} {{ .Values.alpha.extraFlags }}{{- if .Values.alpha.acl.enabled }} --acl "secret-file=/dgraph/acl/{{ .Values.alpha.acl.secretFile }};"{{- end }}{{- if .Values.alpha.encryption.enabled }} --encryption "key-file=/dgraph/enc/{{ .Values.alpha.encryption.keyFile }};"{{- end }}{{- if $nativeTLS }} {{ include "dgraph.tlsFlag" (dict "tls" .Values.alpha.tls "path" "/dgraph/tls") }}{{- end }} resources: {{ toYaml .Values.alpha.resources | indent 10 }} {{- if .Values.alpha.startupProbe.enabled }} @@ -231,6 +251,9 @@ spec: httpGet: port: {{ .Values.alpha.startupProbe.port }} path: {{ .Values.alpha.startupProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} periodSeconds: {{ .Values.alpha.startupProbe.periodSeconds }} timeoutSeconds: {{ .Values.alpha.startupProbe.timeoutSeconds }} successThreshold: {{ .Values.alpha.startupProbe.successThreshold }} @@ -243,6 +266,9 @@ spec: httpGet: port: {{ .Values.alpha.livenessProbe.port }} path: {{ .Values.alpha.livenessProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} initialDelaySeconds: {{ .Values.alpha.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.alpha.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.alpha.livenessProbe.timeoutSeconds }} @@ -256,6 +282,9 @@ spec: httpGet: port: {{ .Values.alpha.readinessProbe.port }} path: {{ .Values.alpha.readinessProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} initialDelaySeconds: {{ .Values.alpha.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.alpha.readinessProbe.periodSeconds }} timeoutSeconds: {{ .Values.alpha.readinessProbe.timeoutSeconds }} diff --git a/charts/dgraph/templates/zero/statefulset.yaml b/charts/dgraph/templates/zero/statefulset.yaml index 87458e020..1dbef7c6f 100644 --- a/charts/dgraph/templates/zero/statefulset.yaml +++ b/charts/dgraph/templates/zero/statefulset.yaml @@ -16,6 +16,26 @@ {{- printf "--idx " -}} {{- end -}} {{- end -}} +{{- /* native-TLS is active for zero when zero.tls is on. Computed once here (a + bool) and reused for the --tls superflag, the HTTPS probe scheme, and the + extraFlags/clientAuthType guards below. */}} +{{- $nativeTLS := .Values.zero.tls.enabled }} +{{- /* native TLS synthesizes --tls from zero.tls; a hand-set --tls in extraFlags + would be passed twice. */}} +{{- if and $nativeTLS (contains "--tls" (.Values.zero.extraFlags | default "")) }} +{{- fail "zero.extraFlags contains --tls, but the chart synthesizes it when zero.tls.enabled=true. Remove --tls from zero.extraFlags and set zero.tls.internalPort / clientName / clientAuthType instead." }} +{{- end }} +{{- /* client-auth-type applies to the external ports the built-in probes hit, so a + cert-requiring mode breaks the certless kubelet probe handshake. */}} +{{- $certRequiringAuth := eq (include "dgraph.tls.certRequired" (dict "tls" .Values.zero.tls)) "true" }} +{{- if and $nativeTLS $certRequiringAuth }} +{{- if not .Values.zero.tls.clientName }} +{{- fail (printf "zero.tls.clientAuthType=%s requires zero.tls.clientName so inter-node TLS can present a client certificate." .Values.zero.tls.clientAuthType) }} +{{- end }} +{{- if or (and .Values.zero.startupProbe.enabled (not .Values.zero.customStartupProbe)) (and .Values.zero.livenessProbe.enabled (not .Values.zero.customLivenessProbe)) (and .Values.zero.readinessProbe.enabled (not .Values.zero.customReadinessProbe)) }} +{{- fail (printf "zero.tls.clientAuthType=%s forces every client to present a certificate, which the built-in httpGet probes cannot do (the kubelet has no client cert). Relax zero.tls.clientAuthType (e.g. VERIFYIFGIVEN) on the external ports, or supply zero.customStartupProbe / customLivenessProbe / customReadinessProbe (exec probes that present the client cert)." .Values.zero.tls.clientAuthType) }} +{{- end }} +{{- end }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -145,9 +165,9 @@ spec: ordinal=${BASH_REMATCH[1]} idx=$(($ordinal + 1)) if [[ $ordinal -eq 0 ]]; then - exec dgraph zero --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):5080 {{ template "raft_index_flag" . }}$idx --replicas {{ .Values.zero.shardReplicaCount }}{{ template "dgraph.logFlags" .Values.zero }} {{ .Values.zero.extraFlags }} + exec dgraph zero --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):5080 {{ template "raft_index_flag" . }}$idx --replicas {{ .Values.zero.shardReplicaCount }}{{ template "dgraph.logFlags" .Values.zero }} {{ .Values.zero.extraFlags }}{{- if $nativeTLS }} {{ include "dgraph.tlsFlag" (dict "tls" .Values.zero.tls "path" "/dgraph/tls") }}{{- end }} else - exec dgraph zero --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):5080 --peer {{ template "peer_zero" . }} {{ template "raft_index_flag" . }}$idx --replicas {{ .Values.zero.shardReplicaCount }}{{ template "dgraph.logFlags" .Values.zero }} {{ .Values.zero.extraFlags }} + exec dgraph zero --my=$(hostname -f | awk '{gsub(/\.$/,""); print $0}'):5080 --peer {{ template "peer_zero" . }} {{ template "raft_index_flag" . }}$idx --replicas {{ .Values.zero.shardReplicaCount }}{{ template "dgraph.logFlags" .Values.zero }} {{ .Values.zero.extraFlags }}{{- if $nativeTLS }} {{ include "dgraph.tlsFlag" (dict "tls" .Values.zero.tls "path" "/dgraph/tls") }}{{- end }} fi resources: {{ toYaml .Values.zero.resources | indent 10 }} @@ -156,6 +176,9 @@ spec: httpGet: port: {{ .Values.zero.startupProbe.port }} path: {{ .Values.zero.startupProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} periodSeconds: {{ .Values.zero.startupProbe.periodSeconds }} timeoutSeconds: {{ .Values.zero.startupProbe.timeoutSeconds }} successThreshold: {{ .Values.zero.startupProbe.successThreshold }} @@ -168,6 +191,9 @@ spec: httpGet: port: {{ .Values.zero.livenessProbe.port }} path: {{ .Values.zero.livenessProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} initialDelaySeconds: {{ .Values.zero.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.zero.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.zero.livenessProbe.timeoutSeconds }} @@ -181,6 +207,9 @@ spec: httpGet: port: {{ .Values.zero.readinessProbe.port }} path: {{ .Values.zero.readinessProbe.path }} + {{- if $nativeTLS }} + scheme: HTTPS + {{- end }} initialDelaySeconds: {{ .Values.zero.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.zero.readinessProbe.periodSeconds }} timeoutSeconds: {{ .Values.zero.readinessProbe.timeoutSeconds }} diff --git a/charts/dgraph/values.yaml b/charts/dgraph/values.yaml index a5fb62361..80a8e1992 100644 --- a/charts/dgraph/values.yaml +++ b/charts/dgraph/values.yaml @@ -223,6 +223,19 @@ zero: ## Files created from './tls' directory set with `dgraph cert` command ## Can use make_tls_secrets.sh to generate secrets.yaml files: {} + ## Read only when tls.enabled=true; the chart then builds Dgraph's --tls + ## superflag from the keys below. + ## Enable TLS on the internal gRPC port for inter-node traffic (Dgraph's + ## --tls "internal-port="). A boolean, not a port number. + internalPort: true + ## Client cert basename; selects client..crt/.key at /dgraph/tls. + ## Empty omits the client cert from --tls. + clientName: "" + ## Dgraph client-auth-type for the external ports (e.g. REQUIREANDVERIFY). + ## Empty omits the field. REQUIREANY/REQUIREANDVERIFY require a clientName and + ## are incompatible with the default httpGet probes (use VERIFYIFGIVEN or + ## custom exec probes). + clientAuthType: "" ## dgraph data Persistent Volume Storage Class @@ -463,6 +476,19 @@ alpha: ## Files created from './tls' directory set with `dgraph cert` command ## Can use make_tls_secrets.sh to generate secrets.yaml files: {} + ## Read only when tls.enabled=true; the chart then builds Dgraph's --tls + ## superflag from the keys below. + ## Enable TLS on the internal gRPC port for inter-node traffic (Dgraph's + ## --tls "internal-port="). A boolean, not a port number. + internalPort: true + ## Client cert basename; selects client..crt/.key at /dgraph/tls. + ## Empty omits the client cert from --tls. + clientName: "" + ## Dgraph client-auth-type for the external ports (e.g. REQUIREANDVERIFY). + ## Empty omits the field. REQUIREANY/REQUIREANDVERIFY require a clientName and + ## are incompatible with the default httpGet probes (use VERIFYIFGIVEN or + ## custom exec probes). + clientAuthType: "" ## ACL Configuration ## ref: https://docs.dgraph.io/installation/configuration/enable-acl