Skip to content

feat(kafka): add TLS support via Strimzi listener configuration - #2681

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

feat(kafka): add TLS support via Strimzi listener configuration#2681
Arsolitt (Arsolitt) merged 15 commits into
mainfrom
feat/tls-kafka

Conversation

@Arsolitt

@Arsolitt Arsolitt (Arsolitt) commented May 19, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds TLS support to the Kafka managed service via Strimzi listener configuration.

  • Introduces a tls.enabled field that controls the external listener (port 9094). When unset, defaults to the value of external (auto-on when external access is enabled). Explicit tls.enabled always wins.
  • Strimzi manages the cluster PKI automatically; the operator publishes <release>-cluster-ca-cert and <release>-clients-ca-cert for client trust setup. No cert-manager chain is rendered by this chart — the operator owns the chain end-to-end.
  • The internal TLS listener on 9093 is always on (Strimzi default); this field only governs the external listener on 9094.
  • External LoadBalancer listener is decoupled from tls.enabled: it is gated on external: true only. Previously, an explicit tls.enabled: true with external: false would force the external listener to be rendered.

Verified end-to-end on a sandbox cluster: external listener exposes a TLS endpoint signed by the Strimzi-managed cluster CA, client connections with the published trust bundle succeed.

Note: this chart does not configure listener authentication. With TLS on the external listener, clients can connect without credentials unless KafkaUser resources with tls/scram-sha-512/oauth authentication are configured outside this chart.

Release note

feat(kafka): add TLS support on the external listener via Strimzi (tri-state tls.enabled)

Summary by CodeRabbit

  • New Features

    • Tri-state TLS for the external Kafka listener: explicit on, explicit off, or inherit from the external toggle; chart values now include a top-level tls object.
  • Documentation

    • Expanded chart docs/README clarifying tls.enabled inheritance, plaintext risk when disabled with external access, and authentication guidance.
  • Tests

    • Added/updated schema, rendering, RBAC/secret-naming, helm unit, and end-to-end tests covering TLS tri-state and external-listener behavior.
  • Bug Fixes

    • RBAC and secret-name expectations updated to use the -cert suffixed secret names.
  • Chores

    • Added a Makefile test target to run helm unit tests.

Review Change Stack

@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

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: b29940b7-2b7a-4073-b580-65af1472110a

📥 Commits

Reviewing files that changed from the base of the PR and between 244f19a and 532fff8.

📒 Files selected for processing (1)
  • hack/check-kafka-rd-schema.bats

📝 Walkthrough

Walkthrough

Adds a nullable tls.enabled value to decouple external Kafka listener TLS from external, with API types and deepcopy, Helm values/schema and helper, template rendering, RBAC/resource-name updates, system schema changes, tests, and README updates.

Changes

Kafka External Listener TLS Control

Layer / File(s) Summary
API Type Contract
api/apps/v1alpha1/kafka/types.go, api/apps/v1alpha1/kafka/zz_generated.deepcopy.go
New TLS struct with nullable Enabled pointer and Tls field on ConfigSpec; deepcopy methods updated to deep-copy Tls and Enabled pointer.
Values schema, defaults & docs
packages/apps/kafka/values.schema.json, packages/apps/kafka/values.yaml, packages/apps/kafka/README.md
Add top-level tls values object (default {}) and tls.enabled (boolean) to schema and values; README documents inheritance from external and authentication guidance.
Helm helper & Kafka template
packages/apps/kafka/templates/_tls.tpl, packages/apps/kafka/templates/kafka.yaml
Add kafka.tls.enabled helper to resolve tri-state TLS; use it and $showExternal in Kafka template so external listener is rendered only when external:true and its tls is driven by tls.enabled (or inherited from external when unset).
RBAC & resource mapping
packages/apps/kafka/templates/dashboard-resourcemap.yaml
Compute $showExternal for conditional RBAC and update external bootstrap secret resourceNames to use *-clients-ca-cert and *-cluster-ca-cert.
System cozyrds schema & secrets
packages/system/kafka-rd/cozyrds/kafka.yaml
Add spec.tls.enabled to openAPISchema, include spec.tls in keysOrder, and allowlist kafka-{{ .name }}-cluster-ca-cert in secrets.
Helm tests & validation scripts
packages/apps/kafka/tests/*, hack/check-kafka-rd-schema.bats, hack/check-kafka-rd-secrets.bats, packages/apps/kafka/Makefile
Update Helm tests and add a TLS test suite covering external × tls.enabled combinations; extend RBAC tests; add Bats checks for schema typing and secret naming; add make test target.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

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

Suggested reviewers

  • kvaps
  • lllamnyp
  • androndo
  • IvanHunters
  • sircthulhu
  • myasnikovdaniil

Poem

🐰 I hopped through types and Helm today,
A tri-state toggle found its way.
Certificates named with a certly cheer,
Tests and docs now make it clear.
Hooray — the external listener chooses here.

🚥 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 clearly identifies the main feature addition: TLS support for Kafka via Strimzi listener configuration, which aligns with the primary change across all modified files.
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-kafka

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 area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files labels May 19, 2026
@Arsolitt
Arsolitt (Arsolitt) marked this pull request as ready for review May 19, 2026 13:03

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

🤖 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 `@hack/check-kafka-rd-secrets.bats`:
- Around line 8-17: Add two tests mirroring the clients-ca checks: create a test
named like 'kafka-rd cozyrds references cluster-ca-cert (Strimzi actual name)'
that greps for "cluster-ca-cert" in the $COZYRDS file, and a test named like
'kafka-rd cozyrds does not reference bare cluster-ca (wrong name)' that fails if
grep -qP "cluster-ca(?!-)" finds a match; follow the same structure and exit
behavior as the existing clients-ca tests so the suite enforces presence of
"cluster-ca-cert" and rejects bare "cluster-ca".

In `@packages/apps/kafka/Makefile`:
- Around line 6-8: The sed patch that replaces "Enabled bool
`json:\"enabled,omitempty\"`" with "Enabled *bool `json:\"enabled,omitempty\"`"
can silently no-op; after running sed (the existing sed command that targets
types.go), add a hard assertion that verifies the Go source now contains the
pointer field (look for the exact token Enabled *bool `json:"enabled,omitempty"`
in types.go) and fail the Makefile target if the check does not match; keep the
existing jq schema changes but ensure the new assertion runs immediately after
the sed step so the build fails if the Go type wasn't actually updated.

In `@packages/apps/kafka/README.md`:
- Line 11: Update the README row for the tls.enabled Helm value to reflect that
its default is unset/null (tri-state inheritance) rather than `false`; change
the default column and the wording so it clearly states the value is
"unset/null" and explains it inherits from `external` when not set, and ensure
`tls.enabled` is referenced exactly as written so the README aligns with the
chart contract.

In `@packages/apps/kafka/templates/dashboard-resourcemap.yaml`:
- Line 9: The RBAC for external-bootstrap is being enabled when tls.enabled is
true because the template sets {{$showExternal := or .Values.external (and
$tlsEnabledExplicit $tlsEnabled)}}, so update the gating logic to require
.Values.external explicitly (remove the TLS-based OR) and apply the same change
to the other occurrences noted (the blocks around lines 21-23) so that
external-bootstrap roles/subjects are only created when .Values.external is
true; search for $showExternal and replace the expression and any conditional
blocks that use it to use .Values.external directly.

In `@packages/apps/kafka/templates/kafka.yaml`:
- Around line 15-21: The template treats a present but null tls.enabled as
explicit, causing tls: null to render; fix by first capturing the raw value
(e.g. introduce $tlsEnabledRaw := index $tlsMap "enabled"), then change the
explicit check to require both key presence and non-nil (use and (hasKey $tlsMap
"enabled") (ne $tlsEnabledRaw nil)), and when that check passes assign
$tlsEnabled = $tlsEnabledRaw, otherwise fall back to the existing
.Values.external | default false; update all uses of
$tlsEnabledExplicit/$tlsEnabled to the new logic so a null value is treated as
unset.

In `@packages/apps/kafka/tests/dashboard-resourcemap_test.yaml`:
- Around line 121-134: The test "grants access to external bootstrap service
when tls.enabled=true and external=false" is asserting over-permissive behavior;
update the case to assert that external bootstrap RBAC is NOT granted when
external: false by changing the spec under "asserts" to verify the resourceNames
does not include "test-kafka-kafka-external-bootstrap" (e.g., replace the
contains check on path rules[0].resourceNames/content
test-kafka-kafka-external-bootstrap with a not-contains or absence check), and
adjust the test description to reflect the corrected expectation.
🪄 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: 07025e25-986f-4953-ba4b-1b179a09393b

📥 Commits

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

📒 Files selected for processing (14)
  • api/apps/v1alpha1/kafka/types.go
  • api/apps/v1alpha1/kafka/zz_generated.deepcopy.go
  • hack/check-kafka-rd-schema.bats
  • hack/check-kafka-rd-secrets.bats
  • packages/apps/kafka/Makefile
  • packages/apps/kafka/README.md
  • packages/apps/kafka/templates/dashboard-resourcemap.yaml
  • packages/apps/kafka/templates/kafka.yaml
  • packages/apps/kafka/tests/dashboard-resourcemap_test.yaml
  • packages/apps/kafka/tests/kafka_test.yaml
  • packages/apps/kafka/tests/kafka_tls_test.yaml
  • packages/apps/kafka/values.schema.json
  • packages/apps/kafka/values.yaml
  • packages/system/kafka-rd/cozyrds/kafka.yaml

Comment thread hack/check-kafka-rd-secrets.bats
Comment thread packages/apps/kafka/Makefile Outdated
Comment thread packages/apps/kafka/README.md Outdated
Comment thread packages/apps/kafka/templates/dashboard-resourcemap.yaml Outdated
Comment thread packages/apps/kafka/templates/kafka.yaml Outdated
Comment thread packages/apps/kafka/tests/dashboard-resourcemap_test.yaml Outdated
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the summary. You can try again by commenting /gemini summary.

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

♻️ Duplicate comments (5)
packages/apps/kafka/Makefile (1)

6-8: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a hard assertion after the sed patch for TLS.Enabled.

The sed replacement can silently no-op when generated formatting changes, which leaves the Go type as non-nullable while schema patching still succeeds. Fail fast by asserting Enabled *bool immediately after sed.

Suggested fix
-	sed -i 's/Enabled bool `json:"enabled,omitempty"`/Enabled *bool `json:"enabled,omitempty"`/' ../../../api/apps/v1alpha1/kafka/types.go
+	sed -i -E 's/Enabled[[:space:]]+bool[[:space:]]+`json:"enabled,omitempty"`/Enabled *bool `json:"enabled,omitempty"`/' ../../../api/apps/v1alpha1/kafka/types.go
+	grep -Eq 'Enabled[[:space:]]+\*bool[[:space:]]+`json:"enabled,omitempty"`' ../../../api/apps/v1alpha1/kafka/types.go || (echo "ERROR: failed to patch TLS.Enabled to *bool" && exit 1)
🤖 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/kafka/Makefile` around lines 6 - 8, Add a hard assertion
immediately after the sed patch to fail the Makefile if the Go type change
didn't apply: after running the sed that intends to change the Kafka TLS struct
field, grep the generated Go type (look for the symbol `Enabled *bool` in
`types.go`) and exit non-zero with a clear error if that exact string is not
present; ensure the check runs before proceeding to the jq schema edits so the
build fails fast when the `Enabled` field remains non-nullable.
packages/apps/kafka/templates/kafka.yaml (1)

15-21: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle tls.enabled: null as unset before rendering listener TLS.

Current explicitness checks only key presence, so enabled: null is treated as explicit instead of inheriting from external. Use a non-nil explicit check and reuse the raw value.

Suggested fix
 {{- $tlsMap := .Values.tls | default dict -}}
-{{- $tlsEnabledExplicit := hasKey $tlsMap "enabled" -}}
+{{- $tlsEnabledRaw := index $tlsMap "enabled" -}}
+{{- $tlsEnabledExplicit := and (hasKey $tlsMap "enabled") (ne $tlsEnabledRaw nil) -}}
 {{- $tlsEnabled := false -}}
 {{- if $tlsEnabledExplicit -}}
-  {{- $tlsEnabled = index $tlsMap "enabled" -}}
+  {{- $tlsEnabled = $tlsEnabledRaw -}}
 {{- else -}}
   {{- $tlsEnabled = .Values.external | default false -}}
 {{- end -}}

Also applies to: 47-47

🤖 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/kafka/templates/kafka.yaml` around lines 15 - 21, The template
treats tls.enabled: null as explicitly set; change the explicitness check to
detect non-nil values instead of just key presence: compute the raw value from
$tlsMap (index $tlsMap "enabled") and treat it as explicit only if the key
exists and the value is not nil, then assign $tlsEnabled from that raw value;
otherwise fall back to .Values.external | default false. Update the same logic
used around the later occurrence (line ~47) as well, referencing variables
$tlsMap, $tlsEnabledExplicit and $tlsEnabled.
packages/apps/kafka/templates/dashboard-resourcemap.yaml (1)

1-9: ⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Gate $showExternal solely on .Values.external to prevent over-permissive RBAC.

The logic $showExternal := or .Values.external (and $tlsEnabledExplicit $tlsEnabled) (line 9) still allows RBAC to be granted for the external-bootstrap secret when external=false but tls.enabled=true. According to the PR objectives, external listener rendering is gated solely on external: true, so RBAC should follow the same gate. When external=false, no external listener exists regardless of tls.enabled (as verified by the test in kafka_tls_test.yaml lines 88-98), making this RBAC grant unnecessary and over-permissive.

🔒 Proposed fix to align RBAC with external-only gate
-{{- $tlsMap := .Values.tls | default dict -}}
-{{- $tlsEnabledExplicit := hasKey $tlsMap "enabled" -}}
-{{- $tlsEnabled := false -}}
-{{- if $tlsEnabledExplicit -}}
-  {{- $tlsEnabled = index $tlsMap "enabled" -}}
-{{- else -}}
-  {{- $tlsEnabled = .Values.external | default false -}}
-{{- end -}}
-{{- $showExternal := or .Values.external (and $tlsEnabledExplicit $tlsEnabled) -}}
+{{- $showExternal := .Values.external | default false -}}
🤖 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/kafka/templates/dashboard-resourcemap.yaml` around lines 1 - 9,
The $showExternal gate is computed as or .Values.external (and
$tlsEnabledExplicit $tlsEnabled) which allows RBAC when tls.enabled=true even if
external=false; change the $showExternal assignment to rely solely on
.Values.external (e.g. set $showExternal := .Values.external | default false) so
RBAC and external-bootstrap rendering are only enabled when .Values.external is
true; update the template where $showExternal is defined and remove the
tls-based condition using the existing variables ($tlsEnabled,
$tlsEnabledExplicit) so the gate matches the external-only requirement.
packages/apps/kafka/tests/dashboard-resourcemap_test.yaml (1)

121-134: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Test validates over-permissive RBAC behavior that contradicts PR objectives.

This test asserts that external-bootstrap RBAC is granted when external=false and tls.enabled=true. However, per the PR objectives, the external listener (and its bootstrap secret) only exists when external: true. The comprehensive TLS test suite (kafka_tls_test.yaml lines 88-98) confirms that no external listener is created in this configuration. Therefore, granting RBAC access to a non-existent secret is over-permissive and should not be enforced by tests.

🧪 Proposed test fix to assert correct (restrictive) behavior
-  # External bootstrap service present when tls.enabled=true and external=false ($showExternal is true)
-  - it: grants access to external bootstrap service when tls.enabled=true and external=false
+  # External bootstrap service absent when external=false, regardless of tls.enabled
+  - it: does not grant access to external bootstrap service when tls.enabled=true and external=false
     release:
       name: test-kafka
       namespace: tenant-test
     set:
       external: false
       tls:
         enabled: true
     asserts:
-      - contains:
+      - notContains:
           path: rules[0].resourceNames
           content: test-kafka-kafka-external-bootstrap
         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/kafka/tests/dashboard-resourcemap_test.yaml` around lines 121 -
134, The test "grants access to external bootstrap service when tls.enabled=true
and external=false" asserts RBAC for a non-existent external bootstrap secret;
change it to assert restrictive behavior instead: replace the positive contains
assertion on rules[0].resourceNames -> "test-kafka-kafka-external-bootstrap"
with a negative check (ensure the rule does NOT contain that resource name or
assert that no rule references the external bootstrap secret) so when set:
external: false and tls.enabled: true the test verifies absence of
external-bootstrap RBAC (use the existing test case identifier and the same
documentIndex/rules[0].resourceNames references to locate and update the
assertion).
hack/check-kafka-rd-secrets.bats (1)

8-17: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add cluster CA validation tests to match the clients CA pattern.

The test suite validates clients-ca-cert naming but omits parallel checks for cluster-ca-cert, which is also referenced in the system schema (kafka.yaml line 34). Without these tests, a regression could drop the cluster-ca-cert secret or reintroduce the bare cluster-ca name without detection.

🧪 Proposed test additions for cluster CA
 `@test` "kafka-rd cozyrds references clients-ca-cert (Strimzi actual name)" {
   grep -q "clients-ca-cert" "$COZYRDS"
 }
 
+@test "kafka-rd cozyrds references cluster-ca-cert (Strimzi actual name)" {
+  grep -q "cluster-ca-cert" "$COZYRDS"
+}
+
 `@test` "kafka-rd cozyrds does not reference bare clients-ca (wrong name)" {
   if grep -qP "clients-ca(?!-)" "$COZYRDS"; then
     echo "Found bare 'clients-ca' reference (missing '-cert' suffix)" >&2
     exit 1
   fi
 }
+
+@test "kafka-rd cozyrds does not reference bare cluster-ca (wrong name)" {
+  if grep -qP "cluster-ca(?!-)" "$COZYRDS"; then
+    echo "Found bare 'cluster-ca' reference (missing '-cert' suffix)" >&2
+    exit 1
+  fi
+}
🤖 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 `@hack/check-kafka-rd-secrets.bats` around lines 8 - 17, Add two parallel BATS
tests for the cluster CA: mirror the existing tests for clients-ca-cert by
adding a positive test that greps for "cluster-ca-cert" (similar to `@test`
"kafka-rd cozyrds references clients-ca-cert") and a negative test that fails if
a bare "cluster-ca" (without "-cert") is found (parallel to the `@test` that
checks for bare "clients-ca"). Place the new tests adjacent to the existing ones
so they validate presence of "cluster-ca-cert" and reject any occurrences
matching the regex used to detect bare names (the same grep -q and grep -qP
pattern logic as the clients-ca tests).
🤖 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.

Duplicate comments:
In `@hack/check-kafka-rd-secrets.bats`:
- Around line 8-17: Add two parallel BATS tests for the cluster CA: mirror the
existing tests for clients-ca-cert by adding a positive test that greps for
"cluster-ca-cert" (similar to `@test` "kafka-rd cozyrds references
clients-ca-cert") and a negative test that fails if a bare "cluster-ca" (without
"-cert") is found (parallel to the `@test` that checks for bare "clients-ca").
Place the new tests adjacent to the existing ones so they validate presence of
"cluster-ca-cert" and reject any occurrences matching the regex used to detect
bare names (the same grep -q and grep -qP pattern logic as the clients-ca
tests).

In `@packages/apps/kafka/Makefile`:
- Around line 6-8: Add a hard assertion immediately after the sed patch to fail
the Makefile if the Go type change didn't apply: after running the sed that
intends to change the Kafka TLS struct field, grep the generated Go type (look
for the symbol `Enabled *bool` in `types.go`) and exit non-zero with a clear
error if that exact string is not present; ensure the check runs before
proceeding to the jq schema edits so the build fails fast when the `Enabled`
field remains non-nullable.

In `@packages/apps/kafka/templates/dashboard-resourcemap.yaml`:
- Around line 1-9: The $showExternal gate is computed as or .Values.external
(and $tlsEnabledExplicit $tlsEnabled) which allows RBAC when tls.enabled=true
even if external=false; change the $showExternal assignment to rely solely on
.Values.external (e.g. set $showExternal := .Values.external | default false) so
RBAC and external-bootstrap rendering are only enabled when .Values.external is
true; update the template where $showExternal is defined and remove the
tls-based condition using the existing variables ($tlsEnabled,
$tlsEnabledExplicit) so the gate matches the external-only requirement.

In `@packages/apps/kafka/templates/kafka.yaml`:
- Around line 15-21: The template treats tls.enabled: null as explicitly set;
change the explicitness check to detect non-nil values instead of just key
presence: compute the raw value from $tlsMap (index $tlsMap "enabled") and treat
it as explicit only if the key exists and the value is not nil, then assign
$tlsEnabled from that raw value; otherwise fall back to .Values.external |
default false. Update the same logic used around the later occurrence (line ~47)
as well, referencing variables $tlsMap, $tlsEnabledExplicit and $tlsEnabled.

In `@packages/apps/kafka/tests/dashboard-resourcemap_test.yaml`:
- Around line 121-134: The test "grants access to external bootstrap service
when tls.enabled=true and external=false" asserts RBAC for a non-existent
external bootstrap secret; change it to assert restrictive behavior instead:
replace the positive contains assertion on rules[0].resourceNames ->
"test-kafka-kafka-external-bootstrap" with a negative check (ensure the rule
does NOT contain that resource name or assert that no rule references the
external bootstrap secret) so when set: external: false and tls.enabled: true
the test verifies absence of external-bootstrap RBAC (use the existing test case
identifier and the same documentIndex/rules[0].resourceNames references to
locate and update the assertion).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92813d5d-8aae-4324-9bd9-a6e473dcc9c9

📥 Commits

Reviewing files that changed from the base of the PR and between c806691 and 959fc85.

📒 Files selected for processing (14)
  • api/apps/v1alpha1/kafka/types.go
  • api/apps/v1alpha1/kafka/zz_generated.deepcopy.go
  • hack/check-kafka-rd-schema.bats
  • hack/check-kafka-rd-secrets.bats
  • packages/apps/kafka/Makefile
  • packages/apps/kafka/README.md
  • packages/apps/kafka/templates/dashboard-resourcemap.yaml
  • packages/apps/kafka/templates/kafka.yaml
  • packages/apps/kafka/tests/dashboard-resourcemap_test.yaml
  • packages/apps/kafka/tests/kafka_test.yaml
  • packages/apps/kafka/tests/kafka_tls_test.yaml
  • packages/apps/kafka/values.schema.json
  • packages/apps/kafka/values.yaml
  • packages/system/kafka-rd/cozyrds/kafka.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/kafka/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.

LGTM — external Kafka listener now defaults to TLS (was plaintext); tri-state tls.enabled opt-out is well-tested; auth gap is pre-existing and explicitly documented.

Business context: pre-PR, the optional external LoadBalancer listener (port 9094) was always rendered with tls: false, exposing Kafka plaintext on a public IP whenever a user enabled external access. This PR adds TLS termination and a tri-state knob with sensible inheritance from external.

Verified:

  • Tri-state evaluation in templates/kafka.yaml:14-22 distinguishes unset from explicit-false (hasKey + ne ... nil). All six cells of the external × tls.enabled truth table are covered in tests/kafka_tls_test.yaml.
  • The external listener is gated solely on .Values.externaltls.enabled=true alone never spawns a LoadBalancer. Confirmed against kafka.yaml:44-49 + Case 5 of the TLS tests.
  • Dashboard RBAC (templates/dashboard-resourcemap.yaml) exposes only <release>-cluster-ca-cert / <release>-clients-ca-cert. Per Strimzi the -cert suffix carries the public CA only; the matching private-key Secrets (-cluster-ca, -clients-ca) are not granted. -external-bootstrap Service access is gated on external and now matches the listener gate.
  • Schema regen in packages/apps/kafka/Makefile patches *bool into both types.go and values.schema.json and verifies the result with grep -q / jq -e, so a future upstream formatting change fails the build instead of silently producing bool.
  • Internal listeners (plain 9092, tls 9093) are unaffected and pinned by tests.
  • All CodeRabbit findings on this PR were addressed and bot-confirmed.

Acknowledged limitation: this chart still does not configure listener authentication — TLS-enabled clients can connect anonymously unless KafkaUser resources with SCRAM/mTLS/OAuth are created out-of-band. The values doc and README call this out. Pre-existing surface was strictly worse (plaintext + no auth), so the auth gap is acceptable as a follow-up rather than a blocker on this PR.

@Arsolitt

Copy link
Copy Markdown
Contributor Author

Adding one series-wide cleanup (Category J — schema):

  • Dropped ["boolean","null"] from tls.enabled schema in de2bbd941. That array form silently breaks unmarshal into apiextv1.JSONSchemaProps (single-string Type field) and disables server-side defaulting. Template now uses kindIs "invalid" for tri-state null-detection. Per-PR auxiliary jq/sed patches in Makefile removed; cozyvalues-gen v1.5.0 emits *bool and plain "type": "boolean" natively.

No cert-manager changes (Strimzi-managed PKI).

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. Re-approving on the new SHA. Net change since my prior LGTM is a small cleanup pass — de2bbd941 refactor(kafka): drop null from tls.enabled schema, use kindIs invalid pattern aligns this PR with the schema convention that landed across the rest of the TLS batch (the apiextv1.JSONSchemaProps single-string Type field can't parse ["boolean","null"], so dropping the array form unblocks server-side defaulting). Strimzi-managed PKI is unchanged: -cluster-ca-cert / -clients-ca-cert exposed in dashboard RBAC are the public-only flavor (Strimzi keeps the tls.key in the -ca-suffixed siblings, never granted), so the B3 concern that's blocking the chart-side cert-manager apps doesn't apply here.

- Add tri-state tls.enabled (*bool) to Kafka API types for strict nil/true/false semantics
- Extend values.yaml and JSON schema with TLS configuration block
- Update Makefile with sed+jq patching for nullable bool schema and test target
- Update cozyrds schema to reflect nullable tls.enabled type
- Update README with Strimzi-managed PKI documentation and authentication notes

Signed-off-by: Arsolitt <arsolitt@gmail.com>
- Configure Kafka CR with TLS listener spec using Strimzi-managed PKI
- Gate external bootstrap RBAC resources on $showExternal predicate
- Use resolved $tlsEnabled variable for consistent tri-state evaluation
- Add Strimzi clients CA secret reference to dashboard resourcemap

Signed-off-by: Arsolitt <arsolitt@gmail.com>
- Add helm-unittest suite covering TLS listener config, tri-state nil/true/false cases
- Cover external=false + tls.enabled=false negative combinations
- Add dashboard resourcemap tests for CA secret inclusion
- Add bats tests for cozyrds schema nullable field and Strimzi CA secret naming

Signed-off-by: Arsolitt <arsolitt@gmail.com>
The external LoadBalancer listener (port 9094) was rendered whenever
tls.enabled was explicitly true, regardless of the external flag. This caused
Strimzi to stall waiting for a LoadBalancer IP when tls.enabled=true but
external=false.

External is now the sole gate for the listener. tls.enabled only controls
TLS on the listener when it is rendered (auto-on when external=true and
tls.enabled is unset, explicit value otherwise). Update tests to match.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
Replace double-bracket [[ ... ]] with POSIX [ ... ] in
hack/check-kafka-rd-schema.bats so it runs under dash, which is what /bin/sh
resolves to on the Ubuntu CI runners. The test passed locally because
/bin/sh resolves to bash on most developer machines.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
dash's echo interprets backslash escapes by default (unlike bash), which
mangles double-backslash sequences in the embedded JSON pattern strings and
makes jq report an invalid-escape parse error. printf '%s' passes the value
through untouched.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
Mirror the existing clients-ca-cert presence/rejection tests for
cluster-ca-cert so the suite enforces both Strimzi-managed CA secret
names and rejects bare cluster-ca references.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
Add a hard assertion after the sed substitution so the build fails
immediately if cozyvalues-gen output format changes and the patch
silently no-ops, instead of letting an incorrect non-pointer type
slip through.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The field is tri-state (null/true/false): null means inherit from
external. Showing false as the default was misleading.

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

The dashboard-resourcemap template was using an OR condition that also
enabled the external-bootstrap Role entry when tls.enabled=true with
external=false. This violates least-privilege: the external listener is
only created when external=true (matching kafka.yaml), so RBAC for it
must follow the same gate.

Simplify $showExternal to .Values.external | default false and update
the test to assert the external-bootstrap entry is absent when
external=false, regardless of tls.enabled.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
When tls.enabled is present in values but set to null, the previous
logic treated it as explicit (hasKey returned true), assigned null to
$tlsEnabled, and rendered tls: null on the external listener — an
invalid Strimzi CR field value.

Introduce $tlsEnabledRaw and guard the explicit check with an
additional ne nil condition so that null behaves like an absent key
and falls back to inheriting from .Values.external.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The README is regenerated from the @field annotation, so declaring the
field as bool produced a README default of false that disagreed with the
schema's actual ["boolean", "null"] tri-state. Switching the annotation
to *bool propagates a *bool/null entry into the generated README.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
…d pattern

cozyvalues-gen v1.5.0 natively emits *bool for @PARAM {*bool} fields,
so the post-generate sed patch for types.go and the jq patch forcing
["boolean","null"] on values.schema.json are no longer needed. Remove
both patches from the Makefile.

Regenerate values.schema.json and cozyrds/kafka.yaml so tls.enabled
carries "type":"boolean" instead of ["boolean","null"]. The single-string
type is required by apiextv1.JSONSchemaProps, which cozystack-api uses for
server-side defaulting; the array form silently disabled defaulting for
the Kafka app.

Refactor the inline tri-state TLS guard in kafka.yaml to use the canonical
kindIs "invalid" pattern and extract it into a dedicated _tls.tpl named
template (kafka.tls.enabled). Add three explicit test cases (J-1, J-2, J-3)
covering the inherit-from-external path, the explicit-false override, and
the external-gate suppression cases.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The drop-null refactor switched the embedded openAPISchema to a scalar
"boolean" type (the ["boolean","null"] array form breaks the
ApplicationDefinition CRD's own unmarshal of the embedded schema). The
rd-schema bats guard still asserted the old array form and started
failing; align it with the scalar type the chart now generates.

Signed-off-by: Arsolitt <arsolitt@gmail.com>

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 is now on by default for the external Kafka listener, with a well-tested tri-state toggle; all earlier review and bot findings are resolved and generated artifacts are in sync.

Business context: previously the optional external LoadBalancer listener (9094) was always rendered with tls: false, exposing Kafka in plaintext on a public IP whenever external access was enabled; this PR adds Strimzi-managed TLS termination plus a tri-state tls.enabled that inherits from external.

Non-blocking follow-ups

  1. _tls.tpl dereferences the nested child .Values.tls.enabled without first guarding the parent map (packages/apps/kafka/templates/_tls.tpl:15). An explicit tls: null makes helm template fail with nil pointer evaluating interface {}.enabled. Not reachable on the normal path — the Kafka CR is admitted against a schema where tls defaults to {} (not nullable), so an explicit null is replaced before rendering; it only bites when HelmRelease values are set directly. A defensive {{- $tls := .Values.tls | default dict -}} before the kindIs check would close it.
  2. tls.enabled=false + external=true still renders a public plaintext listener with no authentication. This is documented and strictly better than the pre-PR behaviour (plaintext was the only option). Listener authentication (SCRAM/mTLS/OAuth) remains a worthwhile follow-up.

is rendered at all is controlled by .Values.external in the caller.
*/ -}}
{{- define "kafka.tls.enabled" -}}
{{- if kindIs "invalid" .Values.tls.enabled -}}

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.

.Values.tls.enabled is dereferenced without first guarding the parent map. An explicit tls: null (not the same as absent/{}) makes this fail with nil pointer evaluating interface {}.enabled. CRD defaulting (tls default {}) shields the standard CR path, but a defensive {{- $tls := .Values.tls | default dict -}} before the kindIs check would make the helper robust regardless of how values arrive. Non-blocking.

@Arsolitt
Arsolitt (Arsolitt) merged commit 17b2fb7 into main May 29, 2026
14 of 15 checks passed
@Arsolitt
Arsolitt (Arsolitt) deleted the feat/tls-kafka branch May 29, 2026 10:12
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) kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants