Is this related to a problem? Please describe.
There is no way to enable NATS server-side TLS from the plain environment file (deploy/stacks/self-managed/environments/<env>.yaml) the way every other operator override is set.
In a split-plane deployment (workers outside the control-plane cluster reaching NATS over a public, L4-passthrough load balancer), TLS must terminate at the NATS server — the load balancer cannot terminate it, because the NATS client is INFO-first, so a TLS-terminating LB deadlocks the handshake. Enabling server TLS is therefore mandatory for that topology. The setting lives in the synadia nats subchart at config.nats.tls, plus config.merge.allow_non_tls: true so in-cluster plaintext clients keep working on the same :4222.
The problem is the wiring:
- The
nats release's chart values are only ../global.yaml.gotmpl + ../secrets/<env>-secrets.yaml — the env file is not in the release's values: list (deploy/stacks/self-managed/helmfile.d/01-dependencies.yaml.gotmpl). The env file reaches a release only where global.yaml.gotmpl explicitly re-emits it via dig … .Values.
- In
global.yaml.gotmpl's nats: block (:201-260), the only env-overridable knobs are reloader.image.* (:222-224) and podDisruptionBudget (:257-259), plus a storageClass-gated config: block (:249-255). There is no passthrough for config.nats.tls or config.merge.
- The wrapper (
deploy/helm/nats/values.yaml) exposes config.cluster/jetstream/merge but no tls.
Net effect: a nats.config.nats.tls value placed in environments/<env>.yaml is silently ignored, and NATS starts with no cert — which the INFO-first worker client experiences as a silent hang, not an error. The only ways to turn it on today are (a) putting the config in secrets/<env>-secrets.yaml — misusing the secrets file as a config carrier for a non-secret setting — or (b) carrying a local overlay file + a values:-list patch on an OSS-tracked helmfile.
Describe the solution you'd like
Implemented in #1442. The snippet below reflects what merged; it folds into the single existing config: block (which already emits merge.server_tags and the storageClass-gated jetstream override), rather than replacing a storageClass-only block.
Add a disabled-by-default dig-based passthrough in global.yaml.gotmpl's nats: block (already in every release's values: list), so server TLS can be enabled from the plain env file. It must fold into the one existing merged config: block — emitting a second config: (or merge:) key under nats: produces duplicate keys that helm/helmfile silently collapse to the last one. So allow_non_tls nests under the existing merge next to server_tags, and tls is added under config.nats; both are emitted only when set:
{{- $natsTls := dig "nats" "config" "nats" "tls" dict .Values }}
{{- $allowNonTls := dig "nats" "config" "merge" "allow_non_tls" nil .Values }}
config:
merge:
server_tags:
{{- toYaml $natsServerTags | nindent 8 }}
{{- if kindIs "bool" $allowNonTls }}
allow_non_tls: {{ $allowNonTls }}
{{- end }}
{{- if .Values.global.storageClass }}
jetstream:
fileStore:
pvc:
storageClassName: {{ .Values.global.storageClass }}
{{- end }}
{{- with $natsTls }}
nats:
tls:
{{- toYaml . | nindent 8 }}
{{- end }}
Operators then set it in environments/<env>.yaml:
nats:
config:
nats:
tls:
enabled: true
secretName: nats-server-tls # cert-manager-issued, publicly trusted
merge:
allow_non_tls: true
Defaults to off, so existing installs render byte-identical; no new file is added to any values: list (a missing values file is a hard helmfile error, which a file-based change would risk).
Describe alternatives you've considered
- Put the TLS config in
secrets/<env>-secrets.yaml. Works today (that file is a values source for the nats release), but it misuses a secrets file to carry non-secret configuration (enabled, secretName), which is the wrong pattern for a setting that every split-plane operator needs.
- Local overlay file +
values:-list edit on the nats release (mirroring the openbao-server extra-file pattern). Functional, but it's a maintained patch against an OSS-tracked helmfile that must be rebased on every upstream update.
- A separate second
config: block in global.yaml.gotmpl. Broken: duplicate config: keys under nats: are silently collapsed to the last one (no error), dropping the TLS block — hence the single merged block above.
- Do nothing. Not viable for split-plane: without server TLS, remote workers cannot establish a NATS connection.
Additional context
This mirrors the existing dig "…" … | default env-override pattern already used in the same nats: block for reloader.image.* (:222-224) and podDisruptionBudget (:257-259). Verified locally that supplying these exact values to the wrapper chart (helm-nvcf-nats 0.7.0 + synadia nats 1.3.15) renders the client-listener tls block and allow_non_tls: true while preserving the auth-callout config (accounts/auth_callout/server_tags), and that the unset case emits nothing (default-off, backward-compatible).
Is this related to a problem? Please describe.
There is no way to enable NATS server-side TLS from the plain environment file (
deploy/stacks/self-managed/environments/<env>.yaml) the way every other operator override is set.In a split-plane deployment (workers outside the control-plane cluster reaching NATS over a public, L4-passthrough load balancer), TLS must terminate at the NATS server — the load balancer cannot terminate it, because the NATS client is INFO-first, so a TLS-terminating LB deadlocks the handshake. Enabling server TLS is therefore mandatory for that topology. The setting lives in the synadia
natssubchart atconfig.nats.tls, plusconfig.merge.allow_non_tls: trueso in-cluster plaintext clients keep working on the same:4222.The problem is the wiring:
natsrelease's chart values are only../global.yaml.gotmpl+../secrets/<env>-secrets.yaml— the env file is not in the release'svalues:list (deploy/stacks/self-managed/helmfile.d/01-dependencies.yaml.gotmpl). The env file reaches a release only whereglobal.yaml.gotmplexplicitly re-emits it viadig … .Values.global.yaml.gotmpl'snats:block (:201-260), the only env-overridable knobs arereloader.image.*(:222-224) andpodDisruptionBudget(:257-259), plus astorageClass-gatedconfig:block (:249-255). There is no passthrough forconfig.nats.tlsorconfig.merge.deploy/helm/nats/values.yaml) exposesconfig.cluster/jetstream/mergebut notls.Net effect: a
nats.config.nats.tlsvalue placed inenvironments/<env>.yamlis silently ignored, and NATS starts with no cert — which the INFO-first worker client experiences as a silent hang, not an error. The only ways to turn it on today are (a) putting the config insecrets/<env>-secrets.yaml— misusing the secrets file as a config carrier for a non-secret setting — or (b) carrying a local overlay file + avalues:-list patch on an OSS-tracked helmfile.Describe the solution you'd like
Add a disabled-by-default
dig-based passthrough inglobal.yaml.gotmpl'snats:block (already in every release'svalues:list), so server TLS can be enabled from the plain env file. It must fold into the one existing mergedconfig:block — emitting a secondconfig:(ormerge:) key undernats:produces duplicate keys that helm/helmfile silently collapse to the last one. Soallow_non_tlsnests under the existingmergenext toserver_tags, andtlsis added underconfig.nats; both are emitted only when set:{{- $natsTls := dig "nats" "config" "nats" "tls" dict .Values }} {{- $allowNonTls := dig "nats" "config" "merge" "allow_non_tls" nil .Values }} config: merge: server_tags: {{- toYaml $natsServerTags | nindent 8 }} {{- if kindIs "bool" $allowNonTls }} allow_non_tls: {{ $allowNonTls }} {{- end }} {{- if .Values.global.storageClass }} jetstream: fileStore: pvc: storageClassName: {{ .Values.global.storageClass }} {{- end }} {{- with $natsTls }} nats: tls: {{- toYaml . | nindent 8 }} {{- end }}Operators then set it in
environments/<env>.yaml:Defaults to off, so existing installs render byte-identical; no new file is added to any
values:list (a missing values file is a hard helmfile error, which a file-based change would risk).Describe alternatives you've considered
secrets/<env>-secrets.yaml. Works today (that file is a values source for thenatsrelease), but it misuses a secrets file to carry non-secret configuration (enabled,secretName), which is the wrong pattern for a setting that every split-plane operator needs.values:-list edit on thenatsrelease (mirroring theopenbao-serverextra-file pattern). Functional, but it's a maintained patch against an OSS-tracked helmfile that must be rebased on every upstream update.config:block inglobal.yaml.gotmpl. Broken: duplicateconfig:keys undernats:are silently collapsed to the last one (no error), dropping the TLS block — hence the single merged block above.Additional context
This mirrors the existing
dig "…" … | defaultenv-override pattern already used in the samenats:block forreloader.image.*(:222-224) andpodDisruptionBudget(:257-259). Verified locally that supplying these exact values to the wrapper chart (helm-nvcf-nats0.7.0 + synadianats1.3.15) renders the client-listenertlsblock andallow_non_tls: truewhile preserving the auth-callout config (accounts/auth_callout/server_tags), and that the unset case emits nothing (default-off, backward-compatible).