Skip to content

feat(rabbitmq): add TLS support for AMQPS and management HTTPS - #2683

Merged
Aleksei Sviridkin (lexfrei) merged 47 commits into
mainfrom
feat/tls-rabbitmq
Sep 7, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 47 commits into
mainfrom
feat/tls-rabbitmq

Conversation

@Arsolitt

@Arsolitt Arsolitt (Arsolitt) commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Adds TLS for AMQP and the management API, and hands the tenant a key-free trust anchor instead of a private key.

  • A chart-authored cert-manager chain issues the broker certificate. The chart names its CA Secret in a TenantProjection sentinel; the CA-extraction controller reads ca.crt from that Secret and writes a key-free projection, stamping internal.cozystack.io/tenant-ca on the projection it owns. That label is what the ResourceDefinition selects. Nothing labels the CA Secret itself, which holds the CA private key and is excluded by name.
  • The leaf <release>-tls carries the broker's private key and reaches no tenant. Neither the dashboard Role nor the ResourceDefinition grants it, and the ResourceDefinition excludes it by name as well, so a label mis-stamped onto it later still does not get through.
  • SAN coverage now lists every resolvable form explicitly, including the bare *.<release>-nodes wildcard that the operator uses for RABBITMQ_NODENAME.

TLS is opt-in here, unlike the sibling charts

tls.enabled defaults to false and is not inferred from external. That diverges from nats, qdrant and kafka, which infer it, and the divergence is deliberate for a reason specific to this operator.

Setting spec.tls.secretName makes the cluster operator rewrite the port key in the <release>-default-user Secret from 5672 to 5671, on the mere fact of TLS and regardless of whether the plaintext listener is still open. That Secret is the Service Binding the chart hands the tenant. A client with a hardcoded :5672 keeps working. A client that reads the endpoint from the binding, which is the documented way, gets moved to the TLS port and must then speak TLS and be given a CA to verify against, neither of which it was configured for. Inferring TLS from external would fire all of that on an ordinary upgrade, with no configuration change, against exactly the clients that followed the documentation.

Whether the other engines should follow is an epic-level question rather than this PR's to answer; for nats it is raised in #3348.

The plaintext ports are still published, and this PR says so

There is no operator-supported way to publish the TLS ports alone. spec.override.service.spec.ports is a strategic merge keyed on port against defaults the operator rebuilds every reconcile, so it adds and mutates entries but never subtracts one, and a TLS-only port list there would read as a guarantee while changing nothing. The chart renders no service override at all and says why at the callsite.

tls.disableNonTLSListeners is offered as an explicit opt-in, and it refuses to render rather than failing silently in three situations.

  • Together with users or vhosts. The flag forces messaging-topology-operator onto TLS regardless of its own configuration, after which it verifies the broker against the system certificate pool, where a per-release CA cannot be. Every topology object bound to the release would stop reconciling, silently: already-created users keep working while password rotation and new users simply never apply. There is no working route left for it, so the chart refuses instead. Managing those objects as hand-written CRs is not a way out, because the operator takes TLS from the RabbitmqCluster and not from whoever created the CR, so manual objects break identically and without the render-time refusal. rabbitmqctl or a definitions import are.
  • While topology objects are still live in the namespace. See the next section.
  • With TLS off, where no listener of either kind would remain.

Erlang distribution (25672) and epmd (4369) stay plaintext under every setting; the flag does not touch them, so disableNonTLSListeners: true is not "TLS everywhere".

A third route, closing AMQP at the broker through spec.rabbitmq.additionalConfig while leaving the operator on plaintext management, has been suggested and is plausible, but the operator's sources are not vendored here and it was not verified. It is recorded as an open question rather than promised.

Closing the listeners takes two upgrades, and the chart enforces it

Clearing users and vhosts and setting tls.disableNonTLSListeners: true in one edit is the natural thing to try, and it wedges the objects being removed. The values-level refusal cannot catch it, because by then both maps are empty. Helm applies every object in the target release before pruning the ones the release dropped, so the broker closes plaintext and loses the rabbitmq.com/operator-connection-uri annotation while the topology objects still hold deletion.finalizers.<plural>.rabbitmq.com. Their operator then has to reach a management API it can only get to over a certificate it verifies against the system trust pool. The delete never completes, and the upgrade reports success over objects left in Terminating, with the users still live in the broker and kubectl delete namespace on that tenant hanging.

The chart now reads the namespace rather than the values. When the flag is being introduced and any topology object is still there, the render refuses and names them, gives the two-upgrade order and the kubectl get that confirms nothing bound to this release is left, and says how to unwedge a release that is already stuck. The check keys on the live RabbitmqCluster as well, so it applies only to the transition: once the listeners are closed, an object a tenant creates afterwards is theirs to clean up rather than something that stops the release rendering. Eleven unit cases cover the firing and the silent paths.

The set is wider than the three kinds this chart creates. All twelve kinds messaging-topology-operator drives through its shared TopologyReconciler carry that finalizer and share one deletion path, so a Queue or a Shovel written by hand against the release wedges exactly the same way, and a runbook step that checks only users, vhosts and permissions says the namespace is clear when it is not. The list is derived in one place and read from both the lookup and the kubectl get the refusal prints, and a unit test holds it against the operator's own CRDs so a bump cannot introduce a kind the guard has never heard of. SuperStream is the one CRD left out: it runs on its own reconciler with no finalizer, and the objects it owns are already on the list. An object that binds through connectionSecret instead of naming the cluster stays outside the guard, which the comment says.

Breaking for metrics scrapers

Enabling TLS moves the metrics endpoint from 15692 to 15691. A ServiceMonitor selecting the port by the name prometheus silently stops matching and the instance's metrics disappear with no error anywhere. Nothing in this repository scrapes RabbitMQ, so only user-defined monitoring is affected, silently, which is why it is stated here.

Known limitations

  1. Turning TLS back off leaves the raw CA material behind. The anchor itself is collected: the projection carries an owner reference to the TenantProjection sentinel, and Helm deletes the sentinel along with the rest of the chain. What survives is <release>-ca and <release>-tls, because cert-manager runs with enableCertificateOwnerRef: false and does not delete the Secrets it issued, so the README says to remove them by hand. Disabling TLS leaves the published tenant trust anchor behind — the contract has no withdrawal signal #3349 asks the same question of the label-driven leg of the contract, which this chart does not use.
  2. No e2e coverage for the TLS path. The one-upgrade wedge above is the case that most deserves it: install with TLS and users, upgrade to no users plus the flag, and assert the User is actually gone rather than merely marked for deletion. A template test cannot reach that.

On certificate renewal, the leaf carries duration: 8760h and renewBefore: 720h, so cert-manager rewrites the Secret around day 335. The cluster operator mounts it as a projected volume with no subPath, so the kubelet refreshes the files in place, and the operator does not watch a user-supplied TLS Secret, so nothing rolls the pods. The last hop, the broker picking the new file up, rests on Erlang's ssl_pem_cache re-reading by mtime and has only been read from sources rather than exercised against a live broker. The merged qdrant chart ships the same duration and rotationPolicy, so this is a platform-wide property rather than something this PR introduces.

Downstream repositories

packages/apps/rabbitmq/values.schema.json gains the tls object, so cozystack/terraform-provider-cozystack needs the field modelled for cozystack_rabbitmq; tracked in cozystack/terraform-provider-cozystack#42.

Release note

feat(rabbitmq): opt-in TLS for AMQP and the management API, with the CA published to tenants as a key-free trust anchor. Enabling TLS moves the metrics port from 15692 to 15691 (ServiceMonitors selecting the port named `prometheus` must be updated) and moves the `port` value in the `<release>-default-user` binding Secret from 5672 to 5671. Plaintext AMQP and management ports remain published; `tls.disableNonTLSListeners` closes them but also drops in-cluster plaintext clients, and must be enabled in a separate upgrade after the topology objects are gone.

@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds tri-state TLS for RabbitMQ: API TLS type and deepcopy, nullable tls.enabled Helm value and schema, cert-manager CA/leaf templates, RabbitmqCluster and RBAC wiring, comprehensive helm-unittest suites, Makefile schema post-processing, and README/system schema updates.

Changes

RabbitMQ TLS Configuration Feature

Layer / File(s) Summary
TLS type definition and API contract
api/apps/v1alpha1/rabbitmq/types.go, api/apps/v1alpha1/rabbitmq/zz_generated.deepcopy.go
TLS type with optional Enabled *bool added to ConfigSpec; autogenerated deepcopy helpers added for proper cloning.
Helm values, schema, and Makefile
packages/apps/rabbitmq/values.yaml, packages/apps/rabbitmq/values.schema.json, packages/apps/rabbitmq/Makefile
Adds tls: {} default and tls.enabled schema (boolean) with additionalProperties:false; Makefile post-processes schema via jq and adds a test target for helm unittest.
Cert-manager TLS resource templates
packages/apps/rabbitmq/templates/certmanager.yaml
Tri-state TLS resolution; when enabled renders self-signed Issuer, CA Certificate (isCA, long duration, rotationPolicy: Never), CA Issuer, and leaf Certificate with computed DNS SANs (internal, clusterDomain variants, wildcard node entries, optional external host).
RabbitMQ cluster TLS configuration template
packages/apps/rabbitmq/templates/rabbitmq.yaml
Resolves tri-state $tlsEnabled; when true sets spec.tls.secretName, injects management TLS into rabbitmq.additionalConfig, and when external also true updates external LoadBalancer override ports to expose only TLS endpoints.
Dashboard RBAC wiring
packages/apps/rabbitmq/templates/dashboard-resourcemap.yaml
Derives tri-state $tlsEnabled and conditionally grants RBAC access to the release TLS secret (<release>-tls) when TLS is enabled.
Cert-manager template tests & fixture
packages/apps/rabbitmq/tests/certmanager_test.yaml, packages/apps/rabbitmq/tests/fixtures/tls_default_values.yaml
Helm-unittest suite validating cert-manager chain rendering across tls.enabled true/false/unset: document counts, issuer/certificate wiring, CA usages, leaf SAN coverage (internal/external/custom cluster-domain), durations, EKUs, crypto/rotation policies, and negative schema test.
Dashboard resourcemap tests
packages/apps/rabbitmq/tests/dashboard_resourcemap_test.yaml
Ensures Role grants TLS secret access only when TLS is enabled and verifies RoleBinding document ordering/name.
RabbitMQ cluster template test suite
packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml
Validates cluster wiring: absence/presence of spec.tls, management SSL additionalConfig, absence of disableNonTLSListeners, tri-state default/precedence behavior, and conditional LoadBalancer port overrides.
Documentation and system integration
packages/apps/rabbitmq/README.md, packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml
README documents TLS parameters and scope (AMQPS 5671, management HTTPS 15671; Erlang inter-node remains plaintext). System definition openAPISchema adds spec.tls, updates keysOrder, and includes TLS secret in secrets.include.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

kind/api-change, area/api, area/networking

Suggested reviewers

  • kvaps
  • lllamnyp
  • sircthulhu
  • IvanHunters
  • myasnikovdaniil

Poem

🐰 I hopped through charts and API trees,

stitched TLS flags with nimble ease,
SANs and certs, a careful song,
ports and RBAC hopping along,
secure queues hum—rabbit's work complete.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding RabbitMQ TLS support for AMQPS and management HTTPS.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tls-rabbitmq

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.

@github-actions github-actions Bot added 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 labels May 19, 2026
@Arsolitt
Arsolitt (Arsolitt) marked this pull request as ready for review May 19, 2026 13:03
@dosubot dosubot Bot added the lifecycle/active Indicates that an issue or PR is actively being worked on by a contributor label May 19, 2026
@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 introduces TLS support for the RabbitMQ managed service, allowing secure communication for AMQPS and the management interface. It leverages cert-manager to handle certificate generation and rotation, while providing a flexible configuration schema that defaults to secure settings when external access is enabled. The changes ensure that existing plaintext traffic remains unaffected while enabling encrypted alternatives.

Highlights

  • TLS Support: Added TLS support for AMQPS (5671) and Management HTTPS (15671) using cert-manager.
  • Tri-state Configuration: Introduced a nullable tls.enabled field. When unset, TLS is automatically enabled if external access is true.
  • Cert-manager Integration: Implemented a self-contained cert-manager chain (Issuer, CA, Certificate) within the tenant namespace.
  • Backward Compatibility: Preserved plaintext ports (5672, 15672) alongside the new TLS ports.
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/rabbitmq/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: 3

🧹 Nitpick comments (2)
packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml (2)

136-169: ⚡ Quick win

Add tri-state coverage for LoadBalancer port overrides.

The unset-tls.enabled path is validated for spec.tls.secretName and management SSL, but not for spec.override.service.spec.ports. A regression where LB overrides require explicit tls.enabled: true would slip through.

Suggested additional test
+  - it: renders LoadBalancer override ports when tls.enabled is unset (default) and external is true
+    release:
+      name: myrabbitmq
+      namespace: tenant-test
+    values:
+      - fixtures/tls_default_values.yaml
+    set:
+      external: true
+      _namespace.host: example.com
+    documentSelector:
+      path: kind
+      value: RabbitmqCluster
+    asserts:
+      - contains:
+          path: spec.override.service.spec.ports
+          content:
+            name: management-tls
+            port: 15671
+            targetPort: 15671
+            protocol: TCP

Also applies to: 253-293

🤖 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/rabbitmq/tests/rabbitmq_tls_test.yaml` around lines 136 - 169,
Add tri-state coverage for LoadBalancer port overrides: extend the existing test
blocks (the ones using fixtures/tls_default_values.yaml, release name
myrabbitmq, documentSelector kind: RabbitmqCluster) to also assert
spec.override.service.spec.ports when tls.enabled is unset (default) and
external: true; specifically add an asserts block that checks
spec.override.service.spec.ports contains the expected LB port entries (the same
check you have for the other two tests that validate spec.tls.secretName and
management.ssl.port) and mirror the same additional test for the other range
(lines 253-293) so the unset/true/false matrix is fully covered.

320-330: ⚡ Quick win

Avoid index-coupled baseline assertion.

Line 330 uses documentIndex: 0, which is brittle if another manifest is rendered first. Select by kind like the other tests to keep this baseline stable.

Stable selector-based variant
   - it: always renders RabbitmqCluster
     release:
       name: myrabbitmq
       namespace: tenant-test
     set:
       tls.enabled: false
       external: false
+    documentSelector:
+      path: kind
+      value: RabbitmqCluster
     asserts:
       - isKind:
           of: RabbitmqCluster
-        documentIndex: 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/rabbitmq/tests/rabbitmq_tls_test.yaml` around lines 320 - 330,
The baseline test currently asserts the rendered RabbitmqCluster using a
documentIndex (documentIndex: 0), which is brittle; update the assertion to
select the RabbitmqCluster by kind instead of by index so the test doesn't break
if other manifests are rendered first — replace the assertion that includes
"documentIndex: 0" with a selector-style isKind check that targets
"RabbitmqCluster" (matching how other tests select by kind) so the test locates
the resource by its kind rather than its position.
🤖 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/rabbitmq/templates/certmanager.yaml`:
- Around line 54-57: The SAN list creation in the $dnsNames template hardcodes
"cluster.local" which breaks clusters with custom DNS; change the third entry to
use a configurable cluster domain (e.g. .Values.clusterDomain) instead of the
literal "cluster.local" so the SAN becomes (printf "%s.%s.svc.%s" .Release.Name
.Release.Namespace .Values.clusterDomain), and add a default clusterDomain:
"cluster.local" to values.yaml so .Values.clusterDomain is available.
- Around line 5-10: The current logic only checks hasKey and then assigns
$tlsEnabled = .Values.tls.enabled which treats a null value as false and
prevents falling back to .Values.external; change the branch to explicitly treat
null as "inherit" by checking for nil (e.g., use hasKey and ne
.Values.tls.enabled nil) or use Helm's default semantics to set $tlsEnabled to
.Values.tls.enabled when it is non-null, otherwise to .Values.external; update
the assignment that sets $tlsEnabled (referencing $tlsEnabled,
.Values.tls.enabled, .Values.external, and hasKey) so that a null
.Values.tls.enabled falls back to .Values.external.

In `@packages/apps/rabbitmq/templates/rabbitmq.yaml`:
- Around line 4-9: The template treats a present-but-null .Values.tls.enabled as
an explicit value because it uses hasKey, which causes the external fallback
(.Values.external) to be skipped; replace the tri-state check with a
default-based expression (e.g. set $tlsEnabled using default .Values.external
.Values.tls.enabled or the Helm "or" idiom) so that nil/absent tls.enabled falls
back to .Values.external and downstream logic that emits spec.tls.secretName
uses the correct value; update the block that defines $tlsEnabled (currently
using hasKey .Values.tls "enabled" and .Values.tls.enabled) to the single
default/or expression.

---

Nitpick comments:
In `@packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml`:
- Around line 136-169: Add tri-state coverage for LoadBalancer port overrides:
extend the existing test blocks (the ones using
fixtures/tls_default_values.yaml, release name myrabbitmq, documentSelector
kind: RabbitmqCluster) to also assert spec.override.service.spec.ports when
tls.enabled is unset (default) and external: true; specifically add an asserts
block that checks spec.override.service.spec.ports contains the expected LB port
entries (the same check you have for the other two tests that validate
spec.tls.secretName and management.ssl.port) and mirror the same additional test
for the other range (lines 253-293) so the unset/true/false matrix is fully
covered.
- Around line 320-330: The baseline test currently asserts the rendered
RabbitmqCluster using a documentIndex (documentIndex: 0), which is brittle;
update the assertion to select the RabbitmqCluster by kind instead of by index
so the test doesn't break if other manifests are rendered first — replace the
assertion that includes "documentIndex: 0" with a selector-style isKind check
that targets "RabbitmqCluster" (matching how other tests select by kind) so the
test locates the resource by its kind rather than its position.
🪄 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: 3980558a-e1ed-4ebc-9242-c75c97984d18

📥 Commits

Reviewing files that changed from the base of the PR and between c5e9287 and 676576e.

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

Comment thread packages/apps/rabbitmq/templates/certmanager.yaml Outdated
Comment thread packages/apps/rabbitmq/templates/certmanager.yaml Outdated
Comment thread packages/apps/rabbitmq/templates/rabbitmq.yaml 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 RabbitMQ application, including the necessary API changes, Helm templates for cert-manager integration, and updated schema definitions. It also adds comprehensive unit tests to verify the TLS configuration and service port overrides. Regarding the implementation, consider parameterizing the hardcoded .cluster.local domain in the cert-manager templates to better support environments with custom cluster domains.

Comment thread packages/apps/rabbitmq/templates/certmanager.yaml Outdated

@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

♻️ Duplicate comments (3)
packages/apps/rabbitmq/templates/certmanager.yaml (2)

5-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat null as “inherit”, not as disabled.

A present-but-null tls.enabled still takes the hasKey branch, so tls.enabled: null resolves to falsey here instead of falling back to .Values.external. That breaks the advertised tri-state inheritance for external releases.

Proposed fix
-{{- $tlsEnabled := false }}
-{{- if hasKey .Values.tls "enabled" }}
-  {{- $tlsEnabled = .Values.tls.enabled }}
-{{- else }}
-  {{- $tlsEnabled = .Values.external }}
-{{- end }}
+{{- $tlsEnabled := .Values.external }}
+{{- $tlsExplicit := dig "tls" "enabled" nil .Values }}
+{{- if ne $tlsExplicit nil }}
+  {{- $tlsEnabled = $tlsExplicit }}
+{{- end }}
🤖 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/rabbitmq/templates/certmanager.yaml` around lines 5 - 10, The
template treats a present-but-null .Values.tls.enabled as false because hasKey
alone is used; change the logic so null means "inherit" by checking for presence
AND non-null. Replace the if that sets $tlsEnabled to use: if hasKey .Values.tls
"enabled" and not (eq .Values.tls.enabled nil) then set $tlsEnabled =
.Values.tls.enabled else set $tlsEnabled = .Values.external; reference the
variables $tlsEnabled, .Values.tls.enabled and .Values.external and the hasKey
check when making the change.

54-57: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t hardcode cluster.local into the certificate SANs.

The RabbitMQ TLS contract here is svc.<cluster-domain>, not a fixed suffix. On clusters with a custom domain, the fully qualified service name will no longer match the certificate. (rabbitmq.com)

🤖 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/rabbitmq/templates/certmanager.yaml` around lines 54 - 57, The
template currently hardcodes "cluster.local" in the SANs (inside the $dnsNames
list), which breaks clusters with custom cluster domains; update the $dnsNames
construction to use a configurable cluster domain instead (e.g., use default
"cluster.local" for backward compatibility via a chart value like
.Values.clusterDomain) and build the FQDN with printf "%s.svc.%s" .Release.Name
(default .Values.clusterDomain) so the SANs reflect the actual cluster domain;
adjust any values.yaml to document .Values.clusterDomain default.
packages/apps/rabbitmq/templates/rabbitmq.yaml (1)

4-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat null as “inherit” here too.

This has the same tri-state bug as the cert-manager template: tls.enabled: null is treated as an explicit value and skips the .Values.external fallback, so external deployments can silently lose TLS wiring.

🤖 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/rabbitmq/templates/rabbitmq.yaml` around lines 4 - 9, The
template treats tls.enabled=null as an explicit value and skips the external
fallback; change the logic that computes $tlsEnabled so null is treated like
"not set" — i.e., if .Values.tls.enabled is missing or nil use .Values.external,
otherwise use .Values.tls.enabled; you can implement this by replacing the
hasKey branch with a check that .Values.tls.enabled is not nil (or by using
Helm's default helper: default .Values.external .Values.tls.enabled) to assign
$tlsEnabled.
🧹 Nitpick comments (1)
packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml (1)

25-38: ⚡ Quick win

Scope TLS-off assertions to management SSL keys, not full additionalConfig absence.

These checks can fail if unrelated RabbitMQ config is added later. Prefer asserting that management.ssl.* entries are absent instead of asserting spec.rabbitmq.additionalConfig is entirely missing.

Proposed test change
-      - notExists:
-          path: spec.rabbitmq.additionalConfig
+      - notMatchRegex:
+          path: spec.rabbitmq.additionalConfig
+          pattern: "management\\.ssl\\."

@@
-      - notExists:
-          path: spec.rabbitmq.additionalConfig
+      - notMatchRegex:
+          path: spec.rabbitmq.additionalConfig
+          pattern: "management\\.ssl\\."

Also applies to: 174-190

🤖 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/rabbitmq/tests/rabbitmq_tls_test.yaml` around lines 25 - 38,
The test currently asserts that spec.rabbitmq.additionalConfig does not exist
when tls.enabled is false; instead, change the assertions to only scope absence
to the management SSL keys (e.g., assert notExists for paths like
spec.rabbitmq.additionalConfig.management.ssl and/or
spec.rabbitmq.additionalConfig.management.ssl.*) so unrelated additionalConfig
entries won't break the test; update the two test cases (the one shown and the
one at lines 174-190) to replace the top-level notExists on
spec.rabbitmq.additionalConfig with notExists checks targeting
spec.rabbitmq.additionalConfig.management.ssl (or the specific keys under
management.ssl) while leaving other parts of additionalConfig allowed.
🤖 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/rabbitmq/templates/certmanager.yaml`:
- Around line 54-58: The DNS SAN list variable $dnsNames in certmanager.yaml
currently only includes the client service names; update it to also include the
headless nodes service SANs required by RabbitMQ Cluster Operator by adding
entries for the "-nodes" service variants (e.g., add (printf "%s-nodes"
.Release.Name), (printf "%s-nodes.%s.svc" .Release.Name .Release.Namespace) and
(printf "%s-nodes.%s.svc.cluster.local" .Release.Name .Release.Namespace) to the
list) so node-to-node TLS hostname verification succeeds.

---

Duplicate comments:
In `@packages/apps/rabbitmq/templates/certmanager.yaml`:
- Around line 5-10: The template treats a present-but-null .Values.tls.enabled
as false because hasKey alone is used; change the logic so null means "inherit"
by checking for presence AND non-null. Replace the if that sets $tlsEnabled to
use: if hasKey .Values.tls "enabled" and not (eq .Values.tls.enabled nil) then
set $tlsEnabled = .Values.tls.enabled else set $tlsEnabled = .Values.external;
reference the variables $tlsEnabled, .Values.tls.enabled and .Values.external
and the hasKey check when making the change.
- Around line 54-57: The template currently hardcodes "cluster.local" in the
SANs (inside the $dnsNames list), which breaks clusters with custom cluster
domains; update the $dnsNames construction to use a configurable cluster domain
instead (e.g., use default "cluster.local" for backward compatibility via a
chart value like .Values.clusterDomain) and build the FQDN with printf
"%s.svc.%s" .Release.Name (default .Values.clusterDomain) so the SANs reflect
the actual cluster domain; adjust any values.yaml to document
.Values.clusterDomain default.

In `@packages/apps/rabbitmq/templates/rabbitmq.yaml`:
- Around line 4-9: The template treats tls.enabled=null as an explicit value and
skips the external fallback; change the logic that computes $tlsEnabled so null
is treated like "not set" — i.e., if .Values.tls.enabled is missing or nil use
.Values.external, otherwise use .Values.tls.enabled; you can implement this by
replacing the hasKey branch with a check that .Values.tls.enabled is not nil (or
by using Helm's default helper: default .Values.external .Values.tls.enabled) to
assign $tlsEnabled.

---

Nitpick comments:
In `@packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml`:
- Around line 25-38: The test currently asserts that
spec.rabbitmq.additionalConfig does not exist when tls.enabled is false;
instead, change the assertions to only scope absence to the management SSL keys
(e.g., assert notExists for paths like
spec.rabbitmq.additionalConfig.management.ssl and/or
spec.rabbitmq.additionalConfig.management.ssl.*) so unrelated additionalConfig
entries won't break the test; update the two test cases (the one shown and the
one at lines 174-190) to replace the top-level notExists on
spec.rabbitmq.additionalConfig with notExists checks targeting
spec.rabbitmq.additionalConfig.management.ssl (or the specific keys under
management.ssl) while leaving other parts of additionalConfig allowed.
🪄 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: 1514db0b-2ffb-49d0-9cc6-8db8e053645d

📥 Commits

Reviewing files that changed from the base of the PR and between 676576e and 4f857f1.

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

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

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 — cert chain is clean (correct CA usages [cert sign, crl sign], correct leaf [digital signature, key encipherment, server auth], SAN list covers both client-facing service and inter-node headless wildcard), and the tri-state tls.enabled resolution was tightened in a3acd2d2 to handle explicit null correctly. Two blockers: when TLS lands on an externally-published instance, the LoadBalancer exposes BOTH the plaintext and TLS ports, and the chart-managed CA's ca.crt is not reachable by tenant clients who need it to verify the TLS handshake.

Business context: Add TLS for the managed RabbitMQ service — AMQPS (5671) and Management HTTPS (15671) — via a self-contained cert-manager chain, with tls.enabled falling back to external when unset. Plaintext listeners are intentionally preserved for backward compatibility per the PR description.

Blockers

B1: External LoadBalancer exposes plaintext AMQP (5672) and plaintext Management HTTP (15672) alongside TLS ports

File: packages/apps/rabbitmq/templates/rabbitmq.yaml:36-56

Issue: When external: true AND TLS is enabled (either explicitly or via the auto-on tri-state), the override.service.spec.ports block unconditionally declares all four ports — amqp, amqps, management, management-tls — on the type: LoadBalancer Service. The PR description acknowledges plaintext "coexists with the TLS ports" for backward compatibility; what's not acknowledged is that this coexistence is published over the public LoadBalancer interface, not just inside the cluster.

Evidence:

{{- if and $tlsEnabled .Values.external }}
service:
  spec:
    ports:
      - name: amqp
        port: 5672
        targetPort: 5672
      - name: amqps
        port: 5671
        targetPort: 5671
      - name: management
        port: 15672
        targetPort: 15672
      - name: management-tls
        port: 15671
        targetPort: 15671
{{- end }}

spec.tls.disableNonTLSListeners is also not set — that upstream RabbitMQ-operator flag would have closed 5672/15672 at the broker level even if the Service exposed them. With this PR, an attacker on the public network can pick the plaintext AMQP port and send PLAIN SASL credentials in cleartext, or hit the management UI over HTTP and submit cookies/sessions in cleartext.

Impact: Silent security regression for the precise cohort the TLS auto-on logic targets — externally-published instances. Operators who toggle external: true (and therefore implicitly opt in to TLS via the tri-state) reasonably expect the public-facing surface to be TLS-only; today they get TLS plus full plaintext as a bonus.

Fix — pick one (not mutually exclusive):

  • Set spec.tls.disableNonTLSListeners: true on the RabbitmqCluster when TLS is on. This closes plaintext at the broker level. Add a tls.required: false escape hatch (mirroring the mariadb pattern in #2680) for tenants in mid-migration that still need plaintext.

  • Drop the plaintext ports from the LoadBalancer override when TLS is on:

    {{- if and $tlsEnabled .Values.external }}
    service:
      spec:
        ports:
          - name: amqps
            port: 5671
            targetPort: 5671
          - name: management-tls
            port: 15671
            targetPort: 15671
    {{- end }}

    Leaves plaintext accessible only over ClusterIP (in-cluster clients can still use it during migration), TLS-only externally.

Either fix the PR description's "preserved for backward compatibility" claim by scoping it explicitly to in-cluster traffic, with TLS-only external. The current state silently widens the attack surface.

B2: Tenant clients cannot verify the TLS handshake — the chart-managed CA ca.crt is not surfaced

File: packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml (the dashboard resource map) + packages/apps/rabbitmq/templates/dashboard-resourcemap.yaml (the RBAC)

Issue: This chart issues a brand-new self-signed CA in the tenant namespace and uses it to sign the leaf serving cert. For any client to verify the AMQPS/HTTPS handshake against that CA — outside the tenant cluster's default cluster-trust roots — they need the CA's ca.crt. The dashboard resource map exposes <release>-default-user and <release>-<user>-credentials only; the CA secret (<release>-ca) and the leaf secret (<release>-tls) are not in the map. The dashboard role binds at the use access level, so even tenant admins (without raw kubectl get secret access on the namespace) hit the same gap.

Evidence: cozyrds/rabbitmq.yaml:31-37 only lists rabbitmq-{{ .name }}-default-user and user-credential secrets under secrets.include. dashboard-resourcemap.yaml RBAC matches. cert-manager produces <release>-ca (type kubernetes.io/tls, contains the CA private key + cert) and <release>-tls (type kubernetes.io/tls, contains the server private key + cert + ca.crt). Both have tls.key alongside tls.crt; Kubernetes RBAC at the Secret level cannot scope to individual keys.

Impact: An external client that wants to do verify-peer on the AMQPS handshake (the security default for production) has no path to fetch the CA cert via the supported tenant interface. Workarounds available today (cluster admin pulls the Secret out-of-band, or tenant disables peer verification) defeat the security gain of this PR.

The cleanest fix is to publish only ca.crt — not the private keys — as a separate, tenant-visible object. cert-manager's additionalOutputFormats: [{type: "CombinedPEM"}] on the leaf Certificate doesn't help here (it adds more material to the same Secret, not a separate one). Two viable patterns:

  • Post-install Helm hook Job that reads <release>-tls's ca.crt and writes it into a <release>-ca-bundle ConfigMap. Add that ConfigMap to cozyrds/rabbitmq.yaml under configMaps.include. The ConfigMap has no private key material, so the use-tier RBAC is safe.

  • Use cert-manager's trust-manager (if available cluster-side) to distribute the CA cert into a ConfigMap declaratively.

The same gap is observed (and called out) in #2680 mariadb B3 — but there it was the inverse problem (RBAC TOO permissive). Here the RBAC is correctly restrictive but the chart never publishes the public-only material. Solving B3 from #2680 and B2 here with the same ConfigMap pattern aligns the whole TLS batch.

Non-blocking follow-ups

  1. PR description acknowledges Erlang inter-node distribution (port 25672) stays plaintext. Worth tracking as an explicit follow-up issue (referenced by-number) so it doesn't drift. spec.tls.caSecretName + a separate per-node cert is the canonical upstream path; the chart's current single-cert model needs to be extended for that.

  2. certmanager.yaml:37-42: CA private key only sets rotationPolicy: Never, leaving algorithm/size at cert-manager defaults (RSA-2048). Non-uniform across the batch (mariadb uses RSA-4096 explicitly). Acceptable but worth a project-level convention.

  3. certmanager.yaml:59-68: SAN list includes *.<release>-nodes.<ns>.svc (without cluster domain). The RabbitMQ operator's inter-node communication uses rabbit@<pod>.<release>-nodes.<ns>.svc.<cluster-domain> (FQDN form), which IS covered by the wildcard on line 67. Inter-node Erlang distribution is plaintext (per follow-up #1), so today the inter-node SANs aren't exercised over TLS — they're future-proofing for when #1 lands.

Comparison to the sibling TLS PRs reviewed: B2 from #2680 (missing digital signature in usages) does NOT recur — present here on the leaf. B3 from #2680 (dashboard RBAC over TLS private keys) does NOT recur — <release>-ca and <release>-tls are correctly excluded from the dashboard role. B1 from #2684/#2685 (short-form peer hostnames not in SAN) does NOT recur for RabbitMQ either — inter-node uses FQDN by upstream chart's default and is plaintext today regardless.

@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 (LoadBalancer exposes plaintext AMQP/management ports alongside TLS ports) — fixed in de878892c. When external + TLS, the LB override now emits only amqps (5671) and management-tls (15671); plain amqp (5672) and management (15672) are dropped. In-cluster ClusterIP plain listeners are preserved for in-cluster client migration. When external is on but TLS is off, plain ports are kept (no behavior change for existing tenants).
  • B2 (no ca.crt retrieval path for tenants) — fixed in 3ebe13306. When TLS is enabled, the dashboard Role grants tenant use read on <release>-tls, and <release>-tls is added to cozyrds secrets.include. Tenants retrieve ca.crt via kubectl get secret <release>-tls -o jsonpath='{.data.ca\.crt}' | base64 -d. This accepts the same RBAC tradeoff as the rest of the series (leaf-key access at use tier); deferred until a cluster-wide ca-bundle pattern lands.

Non-blocking follow-ups:

  • FU1 (Erlang inter-node distribution on 25672 stays plaintext) — won't fix in this PR. Upstream RabbitMQ Cluster Operator currently has no API surface to enable TLS on the Erlang distribution channel (no spec.tls.erlangDistribution or equivalent); adding it requires upstream feature work. Tracked for future work once the operator supports it.
  • FU2 (RSA-2048 default CA) — addressed via series-wide ECDSA unification below.
  • FU3 (SAN list inter-node forms) — unchanged; inter-node distribution is plaintext today regardless.

Series-wide cleanups applied here too:

  • Schema cleanup (064babd46): dropped ["boolean","null"] from tls.enabled. Template uses kindIs "invalid" for tri-state null-detection.
  • Private keys unified to ECDSA P-256 for both CA and leaf in 1c41333fb. Cert chain reissue on first reconcile; existing trust anchors must be refreshed.

Ready for re-review.

@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 26, 2026

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/apps/rabbitmq/templates/rabbitmq.yaml (1)

37-54: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Fix LB “TLS-only” behavior: spec.override.service.spec.ports omission won’t prune plaintext ports

For packages/apps/rabbitmq/templates/rabbitmq.yaml, setting spec.override.service.spec.ports to only 5671/15671 via strategic merge does not delete the operator-generated Service ports for omitted entries (e.g., 5672/15672); strategic merge patches leave list items you don’t specify unchanged. This means the external LoadBalancer may still expose plaintext AMQP/management unless the operator override uses explicit delete semantics for the ports list items (e.g., $patch: delete/deleteDirective keyed by port).

🤖 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/rabbitmq/templates/rabbitmq.yaml` around lines 37 - 54, The
override using spec.override.service.spec.ports only adds ports and does not
remove operator-generated plaintext ports (5672/15672); update the
Helm/Kustomize override for the Service to explicitly remove those ports by
using delete semantics (e.g., add a $patch: delete or use the deleteDirective
keyed by port) so that only the TLS ports (5671/15671) remain exposed; target
the Service ports list (spec.override.service.spec.ports) and identify entries
to remove by their port numbers (5672 and 15672) when applying the patch.
🤖 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/rabbitmq/templates/dashboard-resourcemap.yaml`:
- Around line 20-24: The RBAC entry currently grants tenants "get" on the full
TLS Secret ({{ .Release.Name }}-tls) which exposes tls.key; instead, create a
dedicated CA-only Secret (e.g., {{ .Release.Name }}-ca) containing only ca.crt
and change the RBAC grant in dashboard-resourcemap.yaml to reference that CA
secret instead of {{ .Release.Name }}-tls; implement the extraction in
certmanager.yaml (or the certificate controller) to copy/extract ca.crt from the
primary TLS Secret into the new {{ .Release.Name }}-ca Secret at
issuance/rotation time, and update any template references that expect the CA
secret name.
- Around line 20-24: The RBAC rule in dashboard-resourcemap.yaml grants read
access to the entire TLS Secret ({{ .Release.Name }}-tls) when $tlsEnabled is
true, which exposes tls.key; instead, stop granting get/list/watch on {{
.Release.Name }}-tls and grant access only to a new minimal Secret that contains
the CA cert only (e.g., {{ .Release.Name }}-ca). Update
dashboard-resourcemap.yaml to reference {{ .Release.Name }}-ca when $tlsEnabled,
and change certificates provisioning
(packages/apps/rabbitmq/templates/certmanager.yaml) or add a small post-install
controller/hook to copy only the ca.crt from spec.secretName: {{ .Release.Name
}}-tls into the new {{ .Release.Name }}-ca Secret so tenants can read ca.crt
without access to tls.key.

---

Outside diff comments:
In `@packages/apps/rabbitmq/templates/rabbitmq.yaml`:
- Around line 37-54: The override using spec.override.service.spec.ports only
adds ports and does not remove operator-generated plaintext ports (5672/15672);
update the Helm/Kustomize override for the Service to explicitly remove those
ports by using delete semantics (e.g., add a $patch: delete or use the
deleteDirective keyed by port) so that only the TLS ports (5671/15671) remain
exposed; target the Service ports list (spec.override.service.spec.ports) and
identify entries to remove by their port numbers (5672 and 15672) when applying
the patch.
🪄 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: 09b835a4-9410-47ba-9c63-8fb32e43f349

📥 Commits

Reviewing files that changed from the base of the PR and between 12ca08b and 1c41333.

📒 Files selected for processing (10)
  • packages/apps/rabbitmq/Makefile
  • packages/apps/rabbitmq/templates/certmanager.yaml
  • packages/apps/rabbitmq/templates/dashboard-resourcemap.yaml
  • packages/apps/rabbitmq/templates/rabbitmq.yaml
  • packages/apps/rabbitmq/tests/certmanager_test.yaml
  • packages/apps/rabbitmq/tests/dashboard_resourcemap_test.yaml
  • packages/apps/rabbitmq/tests/rabbitmq_tls_test.yaml
  • packages/apps/rabbitmq/values.schema.json
  • packages/apps/rabbitmq/values.yaml
  • packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml
💤 Files with no reviewable changes (1)
  • packages/apps/rabbitmq/Makefile
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/rabbitmq/tests/dashboard_resourcemap_test.yaml

Comment thread packages/apps/rabbitmq/templates/dashboard-resourcemap.yaml Outdated

@lexfrei Aleksei Sviridkin (lexfrei) 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.

NOT LGTM — re-review on head 6d5f940e. Two security blockers. The first reopens a point I previously marked resolved: the "drop plaintext ports" approach does not actually remove them.

Business context: adds TLS (AMQPS 5671 + Management HTTPS 15671) to the managed RabbitMQ app via a self-contained cert-manager chain, with a tri-state tls.enabled that falls back to external when unset.

Blockers

B1 (reopened): omitting plaintext ports from the override does not remove them — plaintext AMQP/management stay on the public LoadBalancer
File: packages/apps/rabbitmq/templates/rabbitmq.yaml:36-55
Issue: when external + TLS, the override emits only amqps:5671 and management-tls:15671, relying on omission to keep plaintext amqp:5672 / management:15672 off the LoadBalancer. But the RabbitMQ Cluster Operator applies spec.override.service.spec.ports as a strategic merge keyed by port and independently regenerates its default ports — omitting a port from the override never deletes it.
Evidence: the PR's own test comment states this verbatim (tests/rabbitmq_tls_test.yaml:235-237): "This MERGES by port number rather than replacing the whole list; operator-generated defaults (amqp:5672, amqps:5671, management:15672) are unaffected." The notContains assertions only validate the rendered CR override block, not the final Service the operator produces, so the suite is green while plaintext stays exposed.
Impact: silent public plaintext exposure for exactly the external-published cohort the TLS auto-on targets — cleartext SASL PLAIN credentials over AMQP 5672 and cleartext management sessions over HTTP 15672 on a public IP. The PR's security goal is not met.
Fix: close plaintext at the broker with spec.tls.disableNonTLSListeners: true when TLS is on (the operator then also moves the readiness probe to amqps automatically). The current template comment explicitly avoids this to protect in-cluster clients during migration — so pair it with an opt-out (tls.allowPlaintext) for the migration window rather than leaving plaintext on a public LB. The omit-from-override approach cannot work against this operator.

B2: dashboard RBAC grants the server private key to every tenant use member
File: packages/apps/rabbitmq/templates/dashboard-resourcemap.yaml:20-24
Issue: with TLS on, the Role adds <release>-tls to resourceNames for the tenant use access level (RoleBinding at :39). The comment intends ca.crt retrieval, but <release>-tls is the cert-manager kubernetes.io/tls leaf Secret carrying tls.key — the broker's serving private key. RBAC is per-Secret, so get exposes the key, not just ca.crt.
Evidence: certmanager.yaml leaf secret (<release>-tls, type kubernetes.io/tls); Role grant at dashboard-resourcemap.yaml:23 bound to "use".
Impact: a use-level tenant can extract the broker private key, stand up an impersonating TLS terminator on 5671, MITM other clients' AMQP traffic, and decrypt captured non-PFS sessions.
Fix: expose only the public ca.crt (separate ConfigMap via trust-manager, or a ca.crt-only Secret), not the leaf Secret. Same defect as the sibling mariadb/mongodb TLS PRs — a single shared fix would close it across the series.

Non-blocking follow-ups

  1. The test comment at tests/rabbitmq_tls_test.yaml:242 ("we include amqp, amqps, management, management-tls") is stale after the override change and will need rewriting once B1 is fixed via disableNonTLSListeners.
  2. Erlang inter-node distribution (25672) stays plaintext (acknowledged in the README); inter-node -nodes SANs are already wired as future-proofing. Fine as a tracked follow-up.
  3. README "TLS scope" inherits the framing that plaintext "coexists for backward compat" without disclosing that, when external: true, that coexistence is on the public LoadBalancer (B1). Fix the framing alongside B1.

@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels May 28, 2026
The annotation block restated the README's TLS section nearly verbatim.
Only the @PARAM and @field lines reach the generated schema and README
table; the rest was a second copy that would drift against the first.

Keeps the three facts that change what a caller sets — neither field is
inferred from external, enabling TLS moves the binding port, and
disableNonTLSListeners is not external-only — and points at the README.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The dashboard Role already refuses to grant the key-bearing TLS
Secrets, but nothing in the chart told the CA-extraction controller
to publish the key-free trust anchor those Secrets feed: no
TenantProjection sentinel referenced the CA Certificate's Secret as
a source. Without that sentinel the controller has nothing to act
on, so rabbitmq-<name>.tenant-ca is never created and a tenant has
no path to the CA at all, contrary to what the README and the Role's
own comments describe.

Add templates/tenant-projection.yaml, gated on the same TLS
condition as the certificate chain and mirroring the nats and qdrant
charts: it names the CA Certificate's Secret as the projection
source. Drop the internal.cozystack.io/publish-ca-cert labels from
certmanager.yaml, since the controller resolves its source by name
through the sentinel rather than by a label on the Secret, and
correct the comments and README passage that described that label as
the actual publication mechanism.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Clearing users and vhosts and setting tls.disableNonTLSListeners in the
same edit rendered cleanly, because the existing refusal reads values and
both were already empty by then. Helm applies every object in the target
release before pruning the ones the release dropped, so the broker closed
plaintext and lost the operator-connection-uri annotation while the User,
Vhost and Permission objects still held their deletion finalizers.
messaging-topology-operator then had to reach the management API over a
certificate it verifies against the system trust pool, which cannot hold
a per-release CA, so the delete never completed and the upgrade reported
success over objects left in Terminating.

Read the topology objects actually present in the namespace instead, and
only while the flag is being introduced, so a release that already closed
its listeners keeps rendering and an object created afterwards does not
block it. Both refusals and the README now carry the two-upgrade order,
the command that confirms the namespace is clear, and the way back out.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Two comment blocks in certmanager.yaml used `#`, which the renderer does
not strip, so fourteen lines of prose about the sentinel wiring and the CA
rotation policy were copied verbatim into every Certificate the chart
applies. The rest of the file already explains itself with Go-template
comments; use those here too, and cut the parts the README carries.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…anism

Both the cozyrds comment and the bats header said the chart labels its CA
Secret for publication. Nothing labels that Secret: the chart names it in
a TenantProjection sentinel, and the CA-extraction controller stamps
internal.cozystack.io/tenant-ca on the key-free projection it writes. A
reader who went looking for the missing label on the CA Certificate would
have found nothing, and the obvious repair — a secretTemplate stamping the
label onto the CA Secret — would offer a tenant the CA private key.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…dered

cozyvalues-gen propagates the @field line and nothing under it, so the
description reaching values.schema.json, the README table and the
dashboard form said only that TLS turns on, while the continuation lines
holding the breaking part of the change stayed in values.yaml. Fold the
listener, binding-port and metrics-port moves into the @field line, and
leave the continuation with the part that is genuinely about the struct.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Every existing case with external: false also left _namespace.host unset,
so the platform host stayed out of the SAN list because no host was
supplied, not because the gate held. Replacing the gate with a constant
left the suite green. Supply a host with external: false so the gate is
the only thing deciding.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
A tenant namespace holds every RabbitMQ application the tenant runs, and
the listing enumerated all of them. A second instance with users kept the
first from ever closing its listeners, and the refusal named the other
application's objects and told the operator to delete them. The mechanism
behind the refusal does not reach that far either: a sibling's topology
objects are reconciled through their own broker, whose listeners this flag
leaves alone.

Read spec.rabbitmqClusterReference.name and keep only the objects that
name this release. The hand-written fixture gained the reference it was
missing, which is what hid the gap, and a sibling release now has a case
of its own.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The checks grepped the ResourceDefinition source for forbidden names, a
shape the nats sibling documents as broken: quoting the scalar, spelling
the name through a helper, or reaching the Secret through a label all
leave the grep green. Adding a quoted rabbitmq-{{ .name }}-tls to the
include passed every test.

Compare whole values instead, the way the converged siblings do: the
include and exclude lists as parsed YAML, and the dashboard Role's rules
and kinds against rendered output, so the spelling used in the template
cannot smuggle a name past. The Role's secrets rule is the one part that
grows from values, so a declared user gets its own case.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The file header and the block at the secrets rule both said no TLS Secret
is granted in either TLS state. The one at the rule is where a reader
editing the grant will be looking.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The paragraph closed by saying TLS moves clients before there is a
key-free way to hand them the CA. This chart ships that way, and the
section two headings down documents it. The reason the default holds is
that the move lands the instant the flag is set, before any client has
had a chance to pick the trust anchor up.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Both messages ran 700-800 characters. They surface in a HelmRelease
condition read through kubectl describe, which truncates, so the part an
operator needs was the part most likely to be cut. Keep the objects that
block, the two-upgrade order, the command that confirms the namespace is
clear and a pointer to the README section; the mechanism is already there
in full.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The README and the TLS suite called spec.tls.caSecretName the inter-node
mTLS knob and said the chart leaves it unwired. The field has nothing to
do with Erlang distribution: under it the operator writes
ssl_options.cacertfile, ssl_options.verify = verify_peer, and a cacertfile
for the management and metrics listeners, every one of them client-facing.
The cluster-operator this platform ships carries no distribution TLS under
any field, so there was no inter-node path to leave unwired either.

Say what the field does at each place it is named, and give the
client-verification section the operator field it was missing, along with
the reason verify_peer on its own does not make a client certificate
mandatory.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@github-actions github-actions Bot added the area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review label Sep 4, 2026
@lexfrei

Copy link
Copy Markdown
Contributor

Pushed 2d6a188. The one-step migration is refused at render time now: the guard reads the live RabbitmqCluster and the User, Vhost and Permission objects that reference it (filtered by rabbitmqClusterReference.name, so a neighbouring release in the same namespace cannot lock this one), and fails the flip while any of them still exist, naming the two-step order. A new suite pins it, thirteen mutations red. The topology operator here runs without --connect-using-plain-http, so the annotation is load-bearing exactly as you traced. The cozyrd comment and the bats header no longer describe a label the chart never applies; the tls.enabled description carries the port and metrics consequences; the certificate prose moved into template comments; the external SAN gate has its isolating case. Body rewritten: the controller is on main and the chart uses a sentinel. One correction on the way: caSecretName does not configure inter-node TLS on v2.9.0, it feeds the client listeners, and the README says so now. Not done: an e2e for the wedge itself, since there is no rabbitmq chainsaw suite; that is a separate change.

@IvanHunters IvanHunters left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Superseded by #2683 (review), which carries the same findings as inline comments on the lines they are about. The original text is removed rather than left as a second copy of the same review.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

NOT LGTM

The guard added here to prevent a wedged teardown enumerates three of the twelve topology kinds that wedge identically, and the runbook step meant to confirm the namespace is clear checks the same three.

Findings

  • [MAJOR] packages/apps/rabbitmq/templates/rabbitmq.yaml:39, the topology-object refusal covers 3 of the 12 kinds that share the wedging deletion path

Claim mismatches

  • "Turning TLS back off leaves the published anchor behind" is contradicted by this PR's own README.md:110, and the README matches the code: reconciler.go:1101-1103 builds a real owner reference, so deleting the sentinel garbage-collects the projection. What survives is the raw CA material, not the anchor.
  • Three paragraphs describe deltas against earlier revisions of this branch rather than against main: the spec.override.service block, the <release>-tls grant and the additionalProperties guard are not in this diff at all. Worth a line saying they answer review rounds.

Caveats

  • Not affected on upgrade, and that is rendered rather than reasoned: tls defaults to {} and nothing infers it from external, so three corners come out unchanged against merge-base.
  • Certificate renewal reload cannot be verified statically and rests on Erlang re-reading the PEM by mtime. The merged qdrant and nats charts carry identical durations, so it is a property of the series rather than of this PR.

{{- $live := lookup "rabbitmq.com/v1beta1" "RabbitmqCluster" .Release.Namespace .Release.Name | default dict }}
{{- if not (dig "spec" "tls" "disableNonTLSListeners" false $live) }}
{{- $pending := list }}
{{- range $kind := list "User" "Vhost" "Permission" }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MAJOR] the topology-object refusal covers 3 of the 12 kinds that share the wedging deletion path

The guard iterates a fixed list, and the runbook checks the same three:

39: {{-     range $kind := list "User" "Vhost" "Permission" }}
86: kubectl get users.rabbitmq.com,vhosts.rabbitmq.com,permissions.rabbitmq.com --namespace <tenant-namespace>

For chart-created objects that list is exactly right. The problem is the scope claimed beyond it: the guard's own test is titled "refuses on a hand-written Vhost the chart never declared", and README step 1 says to remove any hand-written topology object. For that scope it is short by nine. In messaging-topology-operator v1.14.2, main.go:180-360 wires Queue, Exchange, Binding, User, Vhost, Policy, OperatorPolicy, Permission, SchemaReplication, Federation, Shovel and TopicPermission through one TopologyReconciler, and controllers/topology_controller.go:64-77 is the single deletion path for all of them.

So the failure is still reachable through the documented procedure. A release with tls.enabled: true and a Queue bound to it: step 1 clears users and vhosts, step 2's command returns nothing because it does not list queues, step 3 sets the flag, the render passes, the broker closes plaintext, and the chart withdraws the operator-connection-uri annotation, so cluster_reference.go:119 falls through to useTLSForConnection = true. The operator then verifies a per-release CA against the system pool, DeleteFunc never succeeds, the finalizer never comes off, and kubectl delete namespace hangs.

Reachability is narrower than the chart-created case: no cozy:tenant:* ClusterRole grants the rabbitmq.com group, so the actor is platform-tier. That is why this is MAJOR and not CRITICAL.

Fix: extend the kind list and both kubectl get strings to the twelve kinds, and add a case seeding a Queue with rabbitmqClusterReference.name equal to the release, asserting the refusal names it. A smaller gap in the same predicate: rabbitmq.yaml:45 matches only on rabbitmqClusterReference.name, while cluster_reference.go:44 shows an object can bind through connectionSecret instead, which no kind list will catch.

If the intent is that the guard only ever covers what the chart creates, the list is right and the fix is documentation: drop the hand-written-object language from README step 1 and retitle that test.

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.

Fixed in 3c5d9f4. The guard reads a single list in templates/_topology.tpl: the twelve kinds the operator routes through its TopologyReconciler (SuperStream is the thirteenth CRD and has no finalizer path of its own, its Queue, Exchange and Binding children are in the twelve). The lookup loop, the kubectl command the refusal prints and the README runbook all read that list, and hack/check-rabbitmq-topology-kinds.bats pins it against the operator's own CRDs so a make update that adds a kind goes red. Two things fell out on the way: the runbook demanded an empty namespace while the guard ignores objects bound to another release, both now say bound to this release; and the README's open question about closing AMQP through additionalConfig is answered, statefulset.go pins the readiness probe to the amqp port unless the flag is set, so that path leaves every pod not ready.

@IvanHunters
IvanHunters dismissed their stale review September 4, 2026 17:41

Superseded by the inline review linked above.

The refusal that stops tls.disableNonTLSListeners from closing the
plaintext listeners under live topology objects iterated User, Vhost
and Permission, the three kinds the chart itself creates. All twelve
kinds messaging-topology-operator drives through TopologyReconciler
carry the same deletion.finalizers.<plural>.rabbitmq.com finalizer and
share one deletion path, so a Queue or a Shovel bound to the release
wedges identically while the render passes and the runbook step
reports the namespace clear.

Derive the list once in a template helper, read it from both the
lookup loop and the confirmation command the refusal prints, and widen
the README runbook to the same set. SuperStream stays out: its own
reconciler sets no finalizer, and the Queue, Exchange and Binding
objects it owns are already on the list.

An object that binds through connectionSecret rather than by cluster
name is still outside the guard, which the comment now says.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The list the migration guard iterates can drift two ways. A `make
update` in packages/system/rabbitmq-operator can add a CRD the chart
never learns about, and the README runbook is a hand-written copy of
the command the refusal prints, which an operator follows instead of
reading the template.

Check both against the vendored CRDs: every kind the operator declares
bar SuperStream has to be on the list, and the README command has to
name the operator's own plurals in the order the helper yields them.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The helper comment says a HelmRelease override carrying `tls: null`
reaches the chart unpruned and is accepted by values.schema.json.
Helm's coalescing drops the key instead, so the schema never sees a
null and `.Values.tls` is nil rather than an empty dict. The `dig` on
a defaulted dict is still what stops the panic; only the account of
how the value arrives was wrong.

The README says the flag forces the topology operator onto TLS
regardless of how it is configured. The connection-uri annotation
short-circuits that calculation before it is reached, so the reason
the flag wins is that the port the annotation names is the port the
flag closes, which is why the chart withdraws it.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
`var=$(a | b | c) || exit 1` takes the status of the last element
only, and the runner sets no pipefail, so an unreadable manifest
arrives as an empty string with a zero status. The comparisons that
follow still fail closed, but the guard read as protection that was
not there.

Check the values are non-empty where the pipeline hides the status,
drop the clause where it never applied, and read the operator's CRDs
once instead of once per kind.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…t question

The confirmation step told the operator to wait for an empty namespace,
while the guard it confirms deliberately ignores objects naming another
release. A tenant running two brokers in one namespace would wait for
something that never happens. Say the check is release-scoped, in the
README and in the refusal.

Closing AMQP through additionalConfig while keeping the operator on
plaintext management was recorded as an open question. It is answerable:
cluster-operator pins the readiness probe to the amqp port unless
disableNonTLSListeners is set, so that configuration leaves every pod
failing readiness. Stated instead of left open.

Also repoint one template reference at the heading that exists, and name
the runbook test after what it compares.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@lexfrei

Copy link
Copy Markdown
Contributor

Pushed 3c5d9f4, answer in the thread. The refusal now covers every kind the topology operator routes through its shared reconciler, read from one list that the printed command, the README runbook and a bats guard against the operator's CRDs share. Also corrected on the way: the claim that turning TLS off leaves the published anchor behind (the sentinel is TLS-gated and owns the projection, so GC takes it; what survives is the raw CA material because enableCertificateOwnerRef is false), and three body paragraphs that described deltas against earlier revisions rather than main. Not done: an e2e for the wedge itself, there is still no rabbitmq chainsaw suite.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

LGTM with non-blocking notes

Reviewed at 3c5d9f49e510dd6870c84a331c91ec199aaf18ac against merge-base 3a70f91423b17cafa27e59a5d40fe7c3076416bf. The default path renders byte-identically to main, every schema-valid TLS corner was rendered, and fourteen mutations of the new guards and SAN entries all turn the suite red. One MINOR below, plus the live-cluster surfaces this static review cannot reach.

Findings

  • [MINOR] packages/apps/rabbitmq/README.md:110, the private-key Secrets survive an application delete and only the disable path says so

Caveats

  • Upgrade path: rendering the merge-base and head over the external, replicas and storageClass corners produces no difference for an instance that does not set tls, which is every instance in the field today.
  • Not executed, needs a live cluster: enabling TLS on a running instance (issuance, the operator reconciling against a Secret that does not exist yet, the pod roll), the broker picking up a renewed leaf, and <release>.tenant-ca reaching tenantsecrets for this chart. All three sit behind an explicit tls.enabled: true, so no unattended upgrade reaches them. See follow-ups.
  • review-helper render-matrix came back inconclusive on every corner, so the corner set was driven by helm template over the chart's own values instead. A corner on an axis I did not name could have been missed.
  • Enabling TLS does not reduce what an external: true broker exposes. Plaintext 5672 and 15672 stay on the LoadBalancer, and tls.disableNonTLSListeners, the only switch that closes them, refuses to render alongside users or vhosts — the only way this chart declares users. A published broker with chart-managed users therefore has no setting that closes its public plaintext ports. The README says so; noted because it is the shape of the feature rather than a defect in it.
  • tests/tls_resolution_test.yaml:154 pins a Helm JSON-schema validator message verbatim. It matches on Helm 3.19.0 and 4.0.4, but the checks job pins no Helm version, so the assertion rides on whatever the runner carries.
  • Envelope warnings, unrelated to this diff: 56 CRD files declared a definition the schema pass could not use, 26 group/kind/version pairs are defined twice with differing schemas, and the hot-hunks file holds 100 of 131 matches.

Recommended follow-ups

  • A cozystack-pr-test run on a disposable dev cluster for the opt-in path: enable TLS on a running instance and watch it converge, read <release>.tenant-ca as a tenant, then exercise the two-upgrade disableNonTLSListeners order and confirm the User objects are gone rather than Terminating. The author lists this as known limitation 2; it is the right shape for a separate run.
  • The trust-anchor leg has an e2e precedent in hack/e2e-chainsaw/mongodb/chainsaw-test.yaml. Mirroring it for rabbitmq would cover the projection end to end without needing the broker's TLS path.
  • Pre-existing and outside this change: chart-declared user passwords are rendered into <release>-<user>-credentials as stringData, so they land in the stored Helm release manifest and stay there for the retained revisions. Same class as #4078, unrelated to the TLS work, but this chart is the one holding it.


### Turning TLS off again

Disabling TLS on a release that had it needs one manual step. Helm removes the `TenantProjection` sentinel along with the `Certificate` objects, and Kubernetes garbage-collects `rabbitmq-<name>.tenant-ca` with it, so the trust anchor stops being projected as soon as TLS goes off — no stale anchor is left for an endpoint that has gone back to plaintext. What Helm cannot clean up is the raw CA material: cert-manager does not delete the Secrets it produced (`enableCertificateOwnerRef` is off), so `rabbitmq-<name>-ca` and `rabbitmq-<name>-tls` stick around, unused, holding private keys nobody references any more. Delete them by hand after disabling TLS.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MINOR] the private-key Secrets survive an application delete and only the disable path says so

This section covers one of the two ways a release stops using TLS. Deleting the RabbitMQ application altogether hits the same cert-manager behaviour and is not mentioned anywhere.

The chart renders Certificate and Issuer objects and never the Secrets, so Helm's uninstall removes nothing; cert-manager ships enableCertificateOwnerRef: false (packages/system/cert-manager/charts/cert-manager/values.yaml:268, and packages/system/cert-manager/values.yaml does not override it), so it does not delete what it issued either. rabbitmq-<name>-ca, holding the CA private key, and rabbitmq-<name>-tls, holding the server private key, are left in the tenant namespace with nothing referencing them and nothing that will ever collect them. The warning below about copied trust bundles going stale makes the CA's lifetime an operator concern, which is exactly the reader who needs to know the key outlives the application.

Extend the same paragraph to the delete case: after removing a RabbitMQ that had TLS on, kubectl delete secret rabbitmq-<name>-ca rabbitmq-<name>-tls --namespace <tenant-namespace>. Worth confirming what cert-manager does when an application of the same name is created again while the old rabbitmq-<name>-ca Secret is still present and its certificate still valid, because reusing the previous CA key is the plausible outcome and it is not what "a per-release CA" reads like.

@lexfrei Aleksei Sviridkin (lexfrei) Sep 7, 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.

Agreed, the delete path was missing. The paragraph is in #4143 rather than here, since this PR merged on the approvals it had.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit d000d02 into main Sep 7, 2026
47 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the feat/tls-rabbitmq branch September 7, 2026 14:51
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 lifecycle/active Indicates that an issue or PR is actively being worked on by a contributor size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants