Skip to content

feat(nats): add TLS support via cert-manager - #2684

Merged
Arsolitt (Arsolitt) merged 8 commits into
mainfrom
feat/tls-nats
May 29, 2026
Merged

feat(nats): add TLS support via cert-manager#2684
Arsolitt (Arsolitt) merged 8 commits into
mainfrom
feat/tls-nats

Conversation

@Arsolitt

@Arsolitt Arsolitt (Arsolitt) commented May 19, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds TLS support to the NATS managed service via cert-manager.

  • Introduces a nullable tls.enabled field with tri-state semantics: when unset, defaults to the value of external (auto-on for externally published services, off for cluster-internal). Explicit tls.enabled always wins.
  • Renders a self-contained cert-manager chain in the tenant namespace: self-signed Issuer, CA Certificate, CA Issuer, leaf Certificate.
  • SAN policy covers the three DNS forms per service (bare, .ns.svc, .ns.svc.<cluster-domain>) plus the external hostname when external: true.
  • Wires the leaf cert into the NATS HelmRelease so client connections and cluster routes both use the chart-managed CA chain.

Verified end-to-end on a sandbox cluster: cert chain reaches Ready, TLS handshake against the tenant service succeeds with the chart CA bundle.

Release note

feat(nats): add TLS support via cert-manager (tri-state tls.enabled)

Summary by CodeRabbit

  • New Features

    • Added optional TLS support for NATS with a nullable/tri-state tls.enabled (true/false/null) that defaults to external access when unset
    • Conditional cert-manager integration to provision CA and TLS certificates and enable cluster TLS, and toggle cluster routing to FQDN when TLS is enabled
  • Documentation

    • Updated configuration docs and schema defaults to include new TLS settings and fallback behavior
  • Tests

    • Added tests validating tri-state TLS behavior, cert-chain generation, SANs, and conditional rendering

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68df44cd-3d28-46e9-acea-2413c061bdcb

📥 Commits

Reviewing files that changed from the base of the PR and between 1f83080 and d9a11a2.

📒 Files selected for processing (11)
  • api/apps/v1alpha1/nats/types.go
  • api/apps/v1alpha1/nats/zz_generated.deepcopy.go
  • packages/apps/nats/Makefile
  • packages/apps/nats/README.md
  • packages/apps/nats/templates/_tls.tpl
  • packages/apps/nats/templates/certmanager.yaml
  • packages/apps/nats/templates/nats.yaml
  • packages/apps/nats/tests/certmanager_test.yaml
  • packages/apps/nats/tests/nats_tls_test.yaml
  • packages/apps/nats/values.schema.json
  • packages/apps/nats/values.yaml
💤 Files with no reviewable changes (9)
  • packages/apps/nats/README.md
  • packages/apps/nats/values.yaml
  • packages/apps/nats/Makefile
  • packages/apps/nats/templates/_tls.tpl
  • packages/apps/nats/values.schema.json
  • packages/apps/nats/templates/certmanager.yaml
  • packages/apps/nats/templates/nats.yaml
  • packages/apps/nats/tests/certmanager_test.yaml
  • packages/apps/nats/tests/nats_tls_test.yaml
✅ Files skipped from review due to trivial changes (1)
  • api/apps/v1alpha1/nats/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/apps/v1alpha1/nats/types.go

📝 Walkthrough

Walkthrough

Adds a tri-state TLS option for NATS (defaults follow external when omitted), extends the Go API types and deepcopy logic, adds Helm values/schema and a TLS helper, conditionally renders cert-manager and NATS TLS config, updates Makefile/schema patching and embedded schema ordering, and adds tests covering behaviors.

Changes

NATS TLS Configuration Support

Layer / File(s) Summary
API Type Extension
api/apps/v1alpha1/nats/types.go, api/apps/v1alpha1/nats/zz_generated.deepcopy.go
New TLS struct with optional Enabled boolean added to ConfigSpec; autogenerated deepcopy methods updated to deep-copy the TLS field and its pointer.
Helm values, schema, README, Makefile
packages/apps/nats/values.yaml, packages/apps/nats/values.schema.json, packages/apps/nats/README.md, packages/apps/nats/Makefile, packages/system/nats-rd/cozyrds/nats.yaml
Helm values add a tls object and documented TLS type; JSON schema adds nullable tls.enabled; README documents the option; Makefile patches and validates schema; embedded ApplicationDefinition schema and keysOrder updated.
Helm TLS helper template
packages/apps/nats/templates/_tls.tpl
Helper template nats.tls.enabled normalizes the tri-state tls.enabled with fallback to external for template conditionals.
cert-manager template and tests
packages/apps/nats/templates/certmanager.yaml, packages/apps/nats/tests/certmanager_test.yaml
Add cert-manager template that renders self-signed Issuer, CA certificate, and leaf TLS certificate with internal/external SANs and ECDSA/P-256 settings; tests validate rendering, SAN coverage, EKU/usages, tri-state defaults, and failure cases.
NATS template and TLS tests
packages/apps/nats/templates/nats.yaml, packages/apps/nats/tests/nats_tls_test.yaml
NATS template conditions tlsCA, nats.tls, and cluster.tls on TLS flag and toggles cluster route FQDN when TLS is enabled; tests assert enabled/disabled/tri-state behaviors, override precedence, tlsCA, and routeURLs outcomes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 TLS hops into the NATS glade tonight,
Certificates snug, signed under moonlight,
A tri-state switch decides the secure way,
Tests guard the SANs so connections hold sway,
Little rabbit cheers — encrypted dreams take flight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding TLS support via cert-manager to NATS, which is the core feature across all modified files (types, templates, tests, configuration).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tls-nats

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/feature Categorizes issue or PR as related to a new feature labels May 19, 2026
@Arsolitt
Arsolitt (Arsolitt) marked this pull request as ready for review May 19, 2026 13:03
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates TLS support into the NATS managed service via cert-manager. It introduces a tri-state configuration for TLS, enabling it automatically for external services while remaining configurable for internal ones. The changes include the generation of a self-signed CA chain, proper wiring into the NATS HelmRelease, and comprehensive unit tests to ensure correct behavior across different configuration scenarios.

Highlights

  • TLS Support: Added TLS support for the NATS managed service using cert-manager, including a self-signed CA chain.
  • Tri-state Configuration: Introduced a nullable tls.enabled field that defaults to the external flag status, allowing for flexible configuration.
  • SAN Policy: Configured comprehensive Subject Alternative Names (SANs) for service DNS forms and external hostnames.
  • Testing: Added extensive helm unittest suites to verify the cert-manager chain rendering and NATS HelmRelease configuration.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: **/zz_generated.*.go (1)
    • api/apps/v1alpha1/nats/zz_generated.deepcopy.go
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/apps/nats/Makefile`:
- Around line 7-8: Before performing the jq patch that sets
.properties.tls.properties.enabled.type in values.schema.json, first run a
precondition check that the path .properties.tls.properties.enabled exists (fail
with an error and exit if it does not) so the patch cannot create missing paths
and mask generator drift; after the precondition succeeds, apply the existing jq
patch to set the type and keep the existing post-check that validates the type
equals ["boolean","null"]. Use the same values.schema.json target and the same
jq-based validation/exit pattern so the new check sits immediately before the jq
patch line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 586baa32-7685-471c-bb7a-863534a7230e

📥 Commits

Reviewing files that changed from the base of the PR and between c5e9287 and 025c7f7.

📒 Files selected for processing (12)
  • api/apps/v1alpha1/nats/types.go
  • api/apps/v1alpha1/nats/zz_generated.deepcopy.go
  • packages/apps/nats/Makefile
  • packages/apps/nats/README.md
  • packages/apps/nats/templates/_tls.tpl
  • packages/apps/nats/templates/certmanager.yaml
  • packages/apps/nats/templates/nats.yaml
  • packages/apps/nats/tests/certmanager_test.yaml
  • packages/apps/nats/tests/nats_tls_test.yaml
  • packages/apps/nats/values.schema.json
  • packages/apps/nats/values.yaml
  • packages/system/nats-rd/cozyrds/nats.yaml

Comment thread packages/apps/nats/Makefile Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces TLS support for the NATS application, implementing a tri-state logic for the tls.enabled flag that defaults to the external access setting if omitted. The implementation includes new cert-manager templates for automated certificate generation, updates to the NATS HelmRelease configuration, and extensive unit tests. Review feedback recommends using the dig function in Helm templates when accessing nested values like _namespace and _cluster to ensure defensive handling of potentially missing keys.

Comment thread packages/apps/nats/templates/certmanager.yaml
Comment thread packages/apps/nats/templates/certmanager.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/apps/nats/tests/certmanager_test.yaml (1)

271-287: ⚡ Quick win

Add an explicit tls.enabled: null tri-state test case.

You validate “unset” behavior via omission; adding enabled: null will guard the schema-supported null path explicitly and prevent regressions in helper logic.

Suggested test addition
+  - it: renders cert-manager chain when tls.enabled is null and external is true
+    set:
+      tls:
+        enabled: null
+      external: true
+    asserts:
+      - hasDocuments:
+          count: 4
+
+  - it: renders no documents when tls.enabled is null and external is false
+    set:
+      tls:
+        enabled: null
+      external: false
+    asserts:
+      - hasDocuments:
+          count: 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/apps/nats/tests/certmanager_test.yaml` around lines 271 - 287, Add
an explicit tri-state test that sets tls.enabled: null to ensure the null path
is covered: mirror the existing "renders cert-manager chain when tls.enabled is
not set and external is true" test by adding a new test entry named e.g.
"renders cert-manager chain when tls.enabled is null and external is true" with
set: { external: true, tls: { enabled: null } } and asserts: hasDocuments:
count: 4 (you can also add the equivalent null+external: false case expecting 0
if desired); place it alongside the existing tests so the helper logic is
validated for schema-supported null values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/apps/nats/tests/certmanager_test.yaml`:
- Around line 271-287: Add an explicit tri-state test that sets tls.enabled:
null to ensure the null path is covered: mirror the existing "renders
cert-manager chain when tls.enabled is not set and external is true" test by
adding a new test entry named e.g. "renders cert-manager chain when tls.enabled
is null and external is true" with set: { external: true, tls: { enabled: null }
} and asserts: hasDocuments: count: 4 (you can also add the equivalent
null+external: false case expecting 0 if desired); place it alongside the
existing tests so the helper logic is validated for schema-supported null
values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 35132ae4-7778-4fbe-9e90-c0bcaf906b12

📥 Commits

Reviewing files that changed from the base of the PR and between 025c7f7 and cd7b067.

📒 Files selected for processing (12)
  • api/apps/v1alpha1/nats/types.go
  • api/apps/v1alpha1/nats/zz_generated.deepcopy.go
  • packages/apps/nats/Makefile
  • packages/apps/nats/README.md
  • packages/apps/nats/templates/_tls.tpl
  • packages/apps/nats/templates/certmanager.yaml
  • packages/apps/nats/templates/nats.yaml
  • packages/apps/nats/tests/certmanager_test.yaml
  • packages/apps/nats/tests/nats_tls_test.yaml
  • packages/apps/nats/values.schema.json
  • packages/apps/nats/values.yaml
  • packages/system/nats-rd/cozyrds/nats.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/nats/README.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT LGTM — clean cert chain with correct usages, the upstream chart's config-reloader sidecar handles cert rotation cleanly via SIGHUP (verified through nats.reloaderConfig extracting cert/key/CA paths), and the dashboard RBAC only exposes <release>-credentials (no TLS-secret leak like #2680). One blocker: the cluster-route URLs don't match the SAN list, and replicas: 2 is the default for this chart so this hits on first install.

Business context: Add TLS for NATS managed service — client connections on :4222 and inter-pod cluster routes on :6222 — via a self-contained cert-manager chain, with tls.enabled tri-state falling back to external when unset.

Blockers

B1: Cluster TLS handshake fails because route URLs use short-form pod hostnames not in the SAN list

File: packages/apps/nats/templates/nats.yaml:93-102 + packages/apps/nats/templates/certmanager.yaml:60-64

Issue: The upstream NATS chart at packages/system/nats/charts/nats/files/config/cluster.yaml:12-17 constructs each cluster route URL as:

{{ proto }}://{{ release }}-{{ i }}.{{ release }}-headless:{{ port }}

…unless routeURLs.useFQDN: true, in which case it appends .{{ ns }}.svc.{{ clusterDomain }}. The upstream default is useFQDN: false (verified at packages/system/nats/charts/nats/values.yaml:60), and this PR's HelmRelease values don't override it — only routeURLs.k8sClusterDomain is set, which has no effect when useFQDN is off.

The cert SAN list (certmanager.yaml:60-64) covers <release>-headless{,.<ns>.svc{,.<cluster>}} plus the wildcard *.<release>-headless.<ns>.svc.<cluster>. X.509 wildcards match exactly one DNS label at the leftmost position — so the wildcard matches <release>-0.<release>-headless.<ns>.svc.<cluster> (FQDN form) but does NOT match <release>-0.<release>-headless (short form). The short form has fewer labels than the wildcard pattern.

Evidence: packages/system/nats/charts/nats/files/config/cluster.yaml:12-17 — the $domain variable stays at $.Values.headlessService.name (i.e. <release>-headless) when useFQDN is false; the route URL template printf "%s://%s%s-%d.%s:%d" then yields tls://<release>-N.<release>-headless:6222. NATS Go client TLS verification (used internally by the server for cluster route dialing) compares the SAN list against the literal URL hostname, not the post-resolution FQDN. No insecure option is set in the cluster TLS block.

Impact: Default install (this chart ships replicas: 2, see values.yaml:59) + TLS auto-on (whenever external: true is set, which also triggers TLS via the tri-state fallback) → cluster won't form. Pods log x509: certificate is valid for <list>, not <release>-0.<release>-headless and reroute attempts loop. Single-replica deployments happen to work because no peer route is dialed.

Fix: Force FQDN route URLs in the HelmRelease values whenever TLS is enabled:

cluster:
  enabled: true
  routeURLs:
    useFQDN: true
    k8sClusterDomain: {{ $clusterDomain }}
  replicas: {{ .Values.replicas }}

This swaps the route URL to <release>-N.<release>-headless.<ns>.svc.<cluster> which the existing wildcard SAN covers. No SAN changes needed.

Non-blocking follow-ups

  1. certmanager.yaml:25-26: CA privateKey only sets rotationPolicy: Never, leaving algorithm/size at cert-manager defaults (RSA-2048). The leaf certificate has no privateKey block at all. Non-uniform across the sibling TLS PRs (mariadb uses RSA-4096 explicitly); RSA-2048 is still sound for a tenant-scoped CA but worth a brief look for a project-level convention.

  2. _tls.tpl:11-17kindIs "invalid" correctly handles tls.enabled == nil (the Makefile patches the schema to ["boolean","null"]). One edge case: if a future codegen drift unknowingly changes the schema to ["boolean"] only, the Makefile's precondition check (commit 68264383) will catch the schema rebuild but not a manually-passed null — though Helm itself will reject null against "type":"boolean", so the failure mode is at install time, not template time. Acceptable; the Makefile precondition is a good safeguard.

  3. nats.yaml:62-66: tlsCA.enabled: true with secretName: <release>-ca mounts the CA secret cluster-wide for verification. cert-manager's CA secret (<release>-ca) is a kubernetes.io/tls secret that also contains tls.key (the CA private key). The NATS upstream chart's tlsCAVolume mounts the whole secret. Tenant pods don't have direct read access to that secret (no separate RBAC change exposes it), but it's worth noting that NATS pods themselves can read the CA private key from the mounted volume. Strictly speaking the pod doesn't need the CA private key — only ca.crt — but the chart mounts everything from the Secret. Not a blocker (this is on the NATS pod, not on tenant users), but a defense-in-depth improvement would be a separate CA-cert-only ConfigMap.

@lexfrei Aleksei Sviridkin (lexfrei) added area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) and removed area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review labels May 25, 2026
@Arsolitt

Copy link
Copy Markdown
Contributor Author

Addressed your review.

Blockers:

  • B1 (cluster route URLs use short-form hostnames not covered by the cert SAN wildcard) — fixed in a62225796. The HelmRelease now sets cluster.routeURLs.useFQDN: true when TLS is enabled, which makes NATS render peer routes as tls://<release>-N.<release>-headless.<ns>.svc.<cluster>:6222 — matching the existing wildcard SAN *.<release>-headless.<ns>.svc.<cluster>. No SAN changes required. Chose this over per-replica short-form SANs to avoid cert reissue on scale-up.

Non-blocking follow-ups:

  • FU1 (CA privateKey algorithm/size left at cert-manager defaults) — addressed via series-wide ECDSA unification below.
  • FU2 (kindIs "invalid" schema vulnerability to a future schema relaxation) — addressed via series-wide schema cleanup below; null is now explicitly disallowed at the schema level.
  • FU3 (NATS pod has access to CA Secret with private key via mount) — unchanged. Defense-in-depth via ca-cert-only ConfigMap is a larger refactor; deferred.

Series-wide cleanups applied here too:

  • Schema cleanup (1f830806d): dropped ["boolean","null"] from tls.enabled (eliminates the silent apiextv1.JSONSchemaProps unmarshal failure). Template uses kindIs "invalid".
  • Private keys unified to ECDSA P-256 for both CA and leaf in b00b17448. Cert chain reissue on first reconcile; existing trust anchors must be refreshed.

Ready for re-review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Both findings from my prior review on this branch are addressed in this push, plus the schema-array regression and the missing digital signature are folded in.

  • a62225796 fix(nats): force cluster route useFQDN when TLS enabled to match cert SAN wildcard — closes the SAN-vs-route-URL mismatch I flagged. The nats.useFQDN: true override under $tlsEnabled aligns the route URL (<release>-N.<release>-headless.<ns>.svc.<cluster-domain>) with the cert's wildcard *.<release>-headless.<ns>.svc.<cluster-domain> SAN.
  • b00b17448 refactor(nats): switch CA and leaf private keys to ECDSA P-256 + leaf usages [digital signature, key encipherment, server auth, client auth]digital signature was present already, but the explicit ECDSA + the keep of the same usages survives the rewrite.
  • 682643831 fix(nats): add precondition check before schema patch in Makefile + cd7b06797 chore(nats): regenerate codegen artifacts + 1f830806d refactor(nats): drop null from tls.enabled schema, use kindIs invalid pattern — same schema-shape fix as the rest of the batch (apiextv1.JSONSchemaProps can't unmarshal ["boolean","null"]).

dashboard RBAC exposes only <release>-credentials — the TLS Secret isn't granted to tenant use, so the B3 pattern that's blocking the chart-side cert-manager peer PRs doesn't apply here. NATS config-reloader sidecar handles SIGHUP-driven cert rotation in the chart already, so no Reloader annotation needed.

- Extend values.yaml and JSON schema with tls.enabled (*bool tri-state) and tls.secretName fields
- Update Go types and regenerate README for TLS field
- Enable TLS toggle in nats-rd cozyrds config
- Wire helm-unittest into Makefile as test target

Signed-off-by: Arsolitt <arsolitt@gmail.com>
- Add _tls.tpl helper with tri-state resolver (nil auto-detects, true/false explicit)
- Add certmanager.yaml to provision Certificate and Issuer resources for client, server, and leaf certs
- Mount CA and certs in nats.yaml with correct EKUs for cluster mTLS and external client auth

Signed-off-by: Arsolitt <arsolitt@gmail.com>
- certmanager_test.yaml: verify Certificate and Issuer resources are rendered correctly for all TLS scenarios
- nats_tls_test.yaml: verify nats.yaml mounts, config blocks, and tri-state TLS logic across enabled/disabled/auto modes

Signed-off-by: Arsolitt <arsolitt@gmail.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
Guard the tls.enabled nullable patch with an existence check so that
generator drift (cozyvalues-gen changing its output shape) is detected
immediately rather than being masked by jq silently creating the path.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
… SAN wildcard

The upstream NATS chart defaults routeURLs.useFQDN to false, which causes
peer routes to resolve as tls://<release>-N.<release>-headless:6222 (short
form). The cert SAN wildcard *.<release>-headless.<ns>.svc.<cluster> matches
FQDN forms but not the short form, so inter-pod cluster traffic runs in
encryption-only mode without hostname verification.

Setting useFQDN: true when TLS is enabled makes peer routes use the FQDN
form that matches the wildcard SAN, restoring authenticated TLS for cluster
routing.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
RSA is slower and produces larger keys and certificates than ECDSA. P-256
provides equivalent security with smaller cert size and faster TLS handshakes,
which matters for frequent cluster-internal reconnections.

CA retains rotationPolicy: Never; leaf retains rotationPolicy: Always.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
… pattern

Remove the jq post-processing step from the Makefile that was patching
tls.enabled to ["boolean","null"]. The plain "type":"boolean" form that
cozyvalues-gen emits is correct: cozystack-api unmarshals openAPISchema into
apiextv1.JSONSchemaProps where Type is a single string, so an array type
silently zeroes out specSchema and disables server-side defaulting.

Tri-state semantics (nil / true / false) are preserved entirely on the
Helm side via kindIs "invalid" guards in the templates.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
@github-actions github-actions Bot added the area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review label May 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — TLS via cert-manager is correctly wired for both client (4222) and cluster (6222) traffic, the cluster-route SAN mismatch from the earlier review is fixed and test-covered, generated artifacts are in sync, and all 51 helm-unittest cases pass.

Business context: adds TLS to the managed NATS app via a self-contained cert-manager chain (SelfSigned issuer → CA → leaf), with a tri-state tls.enabled that falls back to external when unset.

Non-blocking follow-ups

  1. The CA uses ECDSA P-256 with rotationPolicy: Never and the leaf ECDSA P-256 with rotationPolicy: Always + renewBefore: 720h — consistent and sound. Worth confirming the sibling TLS PRs converge on the same algorithm/curve for cross-app uniformity (packages/apps/nats/templates/certmanager.yaml:25-28,51-54).
  2. tlsCA mounts the whole <release>-ca secret (including the CA private key) into NATS pods, though only ca.crt is needed for trust. Not a tenant-facing leak (no RBAC surfaces it; the dashboard exposes only <release>-credentials) and the pod is the trust anchor anyway — a CA-cert-only ConfigMap would be defence-in-depth, not a requirement (packages/apps/nats/templates/nats.yaml:62-66).
  3. Tri-state default-on means an existing install with external: true and no tls set flips TLS on upon upgrade, breaking plaintext external clients. This matches the secure-by-default stance and is documented — worth calling out as intended breaking behaviour in the release note.

@Arsolitt
Arsolitt (Arsolitt) merged commit e76bde7 into main May 29, 2026
17 of 19 checks passed
@Arsolitt
Arsolitt (Arsolitt) deleted the feat/tls-nats branch May 29, 2026 10:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants