Skip to content

Allow enabling NATS server-side TLS from the environment file in the self-managed stack #1342

Description

@sparve-nv

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions