Skip to content

feat(opensearch): add TLS support for HTTP API and Dashboards - #2682

Open
Arsolitt (Arsolitt) wants to merge 43 commits into
mainfrom
feat/tls-opensearch
Open

feat(opensearch): add TLS support for HTTP API and Dashboards#2682
Arsolitt (Arsolitt) wants to merge 43 commits into
mainfrom
feat/tls-opensearch

Conversation

@Arsolitt

@Arsolitt Arsolitt (Arsolitt) commented May 19, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds TLS for the OpenSearch HTTP API (9200) and Dashboards (5601), issued by cert-manager in the tenant namespace, and hands the CA to tenants as a key-free trust anchor.

  • tls.enabled is nullable with tri-state semantics: unset defaults to the value of external (on for externally published services, off for cluster-internal); an explicit value always wins.
  • Renders a self-contained cert-manager chain in the tenant namespace — self-signed Issuer, CA Certificate, CA Issuer, and a leaf Certificate whose SANs cover both the HTTP API and Dashboards, including the external hostname when external: true.
  • Cert-manager resources are named <release>-http-* so they cannot collide with the operator's own <release>-ca transport CA. Reusing that name made the operator read the cert-manager Secret and fail, because it expects ca.key while cert-manager writes tls.key.
  • Transport mTLS (9300) stays operator-managed (transport.generate: true, perNode: true) — the security plugin requires it and the chart cannot turn it off.

securityadmin cross-trust

Chart-managed HTTP TLS previously broke the operator's securityadmin reconciliation: the admin certificate was signed by the transport CA while the HTTP listener trusted the cert-manager CA, so the two never verified against each other and users and roles silently never applied. This is fixed by handing the HTTP CA to the operator through spec.security.tls.http.caSecret, which makes it sign the admin certificate with that same CA. No operator bump is required — the API exists in the pinned version.

That path is version-gated upstream at OpenSearch 2.0.0: below it the operator falls back to the transport CA and the mismatch returns with no error anywhere. The chart therefore refuses chart-managed HTTP TLS on version: v1 with an explicit render-time failure that names the mechanism and offers the alternatives, rather than letting it fail silently at runtime.

Trust anchor for tenants

The HTTP server's private key is no longer exposed to tenants. The CA Certificate is instead labelled so the CA-extraction controller can publish a ca.crt-only projection per release, which the ResourceDefinition selects with the engine-agnostic internal.cozystack.io/tenant-ca selector. Tenants get what they need to verify the server and nothing more.

Known limitations

These are stated rather than left to be discovered:

  1. Merge is blocked on feat(controller): engine-agnostic CA extraction for the tenant trust anchor #3299. The trust-anchor half is inert until the CA-extraction controller lands: the labels are unread and the selector matches no live Secret. Nothing breaks in the meantime — the field is simply pruned and the projection does not exist.
  2. securityadmin is fixed for trust, not yet for reachability. The operator's DNS_BASE is still cluster.local while the platform runs on a different cluster domain, so the securityadmin job waits on a name that does not resolve and never starts — independently of which CA signed the admin certificate. Closing that requires a change in the operator package, alongside the SANs of every operator-issued certificate. Tracked as follow-up.
  3. No e2e coverage for the TLS path. The chainsaw suite does not exercise external or tls. Unit coverage pins the rendered chain, the guards and the SAN set, but the end-to-end handshake is not asserted in CI.

Release note

feat(opensearch): TLS for the HTTP API and Dashboards via cert-manager, with the CA published to tenants as a key-free trust anchor

@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

OpenSearch HTTP-layer TLS is added as a tri-state tls.enabled value that defaults from external when omitted. Helm templates and a helper resolve the flag; cert-manager templates emit a self-signed Issuer, HTTP CA, CA Issuer, and combined leaf Certificate when enabled. CRD types, schema, tests, external service wiring, and docs are updated end-to-end.

Changes

OpenSearch HTTP TLS Support

Layer / File(s) Summary
CRD type contract and deepcopy support
api/apps/v1alpha1/opensearch/types.go, api/apps/v1alpha1/opensearch/zz_generated.deepcopy.go
ConfigSpec gains a new Tls TLS field, and a new exported TLS struct adds the tri-state Enabled *bool. Deepcopy methods are generated to handle the new field and its optional pointer semantics.
Helm values schema and defaults
packages/apps/opensearch/values.schema.json, packages/apps/opensearch/values.yaml
Helm chart values schema and defaults add a tls object with an enabled property that acts as a tri-state: when omitted/null, TLS follows the external setting; when explicitly set, it overrides that behavior.
TLS resolution template helper
packages/apps/opensearch/templates/_tls.tpl
New Helm helper opensearch.tls.enabled normalizes the tri-state Values.tls.enabled into a concrete boolean string, falling back to Values.external when unset.
Cert-manager certificate chain templates
packages/apps/opensearch/templates/certmanager.yaml
Conditional cert-manager resources render a 4-document chain when TLS is enabled: self-signed Issuer for bootstrapping, 10-year HTTP CA certificate with stable secret name, CA Issuer for leaf signing, and a unified 1-year leaf certificate for both OpenSearch HTTP and Dashboards HTTPS with combined SANs and ECDSA keys.
Cluster and external service TLS wiring
packages/apps/opensearch/templates/opensearch.yaml, packages/apps/opensearch/templates/external-svc.yaml
OpenSearchCluster spec.security.tls.http and spec.dashboards.tls switch between the cert-manager secret reference and operator-generated TLS based on the resolved TLS flag. External service templates require _namespace.host for external mode and use it to construct external-dns hostnames.
TLS test suite
packages/apps/opensearch/tests/tls_test.yaml
Tests validate cert-manager chain rendering across tri-state combinations, leaf certificate SANs for service DNS and optional external hosts, certificate usages, transport mTLS operator generation, and guard failures for misconfiguration.
Documentation and system schema updates
packages/apps/opensearch/README.md, hack/check-opensearch-readme.bats, packages/system/opensearch-rd/cozyrds/opensearch.yaml
README documents the tls.enabled tri-state behavior and parameters. Bats tests validate README content completeness and cozyrds YAML includes HTTP certificate secret in secrets.include. Cozyrds schema is extended with the spec.tls.enabled property and field ordering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through TLS gates,
With cert-chains dancing at tri-state,
From Helm to Kube the secrets play,
Self-signed roots light up the way,
And services sip secure bouquet. 🐰🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(opensearch): add TLS support for HTTP API and Dashboards' clearly and specifically summarizes the main change: adding TLS support for OpenSearch's HTTP API and Dashboards, which is the primary focus of all changes across multiple 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tls-opensearch

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

Copy link
Copy Markdown
Contributor

Summary of Changes

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

This pull request introduces TLS support for the OpenSearch HTTP API and Dashboards, enabling secure communication via cert-manager. It implements a flexible tri-state configuration for TLS, allowing users to explicitly enable or disable it, or rely on automatic detection based on external access settings. The changes ensure that transport mTLS remains managed by the operator while providing a robust, self-signed certificate chain for the HTTP layer, complete with necessary SANs and validation logic.

Highlights

  • TLS Support: Added TLS support for the OpenSearch HTTP API and Dashboards using cert-manager, including a tri-state tls.enabled configuration.
  • Cert-Manager Integration: Implemented a self-contained cert-manager chain (Issuer, CA Certificate, CA Issuer, and leaf Certificate) with SAN policies covering internal and external DNS names.
  • Configuration Safety: Added a name-length guard to prevent Kubernetes object name collisions and failures when release names exceed 47 characters.
  • Testing: Introduced comprehensive BATS tests for README structure and Helm chart tests to verify TLS configuration states.
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/opensearch/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.

@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 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 HTTP-layer TLS configuration for OpenSearch and Dashboards using cert-manager. It implements a tri-state logic for the tls.enabled field (auto-on when external access is enabled), adds corresponding cert-manager templates, and includes comprehensive unit and static tests. Feedback was provided regarding the placement of the TLS configuration section in values.yaml to prevent splitting the "Common parameters" table in the generated documentation.

Comment thread packages/apps/opensearch/values.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: 2

🧹 Nitpick comments (1)
packages/apps/opensearch/tests/tls_test.yaml (1)

29-249: ⚡ Quick win

Add a regression test for the 47-char release-name guard.

This suite validates TLS behavior well, but it does not currently lock in the new fail-fast contract for overly long release names. Adding one failedTemplate case here would prevent regressions.

Proposed test addition
 tests:
+  - it: fails when release name exceeds the cert-manager naming limit
+    release:
+      name: os-test-name-that-is-definitely-longer-than-forty-seven
+      namespace: tenant-test
+    set:
+      tls:
+        enabled: true
+    asserts:
+      - failedTemplate: {}
+
   - it: "(a) renders no resources when external false and tls.enabled unset"
     asserts:
       - hasDocuments:
           count: 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/apps/opensearch/tests/tls_test.yaml` around lines 29 - 249, Add a
new failedTemplate test case to this tls_test.yaml suite that sets a release
name longer than 47 characters (e.g. a 48-char string) and asserts a
failedTemplate with an errorMessage indicating the release-name-length guard
(for example: "release name must be at most 47 characters" or the existing guard
message). Place it alongside the other TLS cases (near the top-level tests
block) and use the same structure as other cases (set: release: name:
"<48-char-string>" and asserts: - failedTemplate: errorMessage: "<expected
message>"). Ensure the assertion uses the existing failedTemplate symbol so the
CI will catch regressions to the 47-char guard.
🤖 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-opensearch-readme.bats`:
- Around line 23-28: The test "topologySpreadPolicy is not in TLS configuration
section" uses `awk ... | grep -qv "topologySpreadPolicy"` which incorrectly
succeeds if any line differs; change the assertion to explicitly ensure the TLS
section does not contain the string by replacing the negative grep logic with an
explicit absence check (e.g., use the pipeline output piped into a command that
fails when "topologySpreadPolicy" is found, such as negating `grep -q
"topologySpreadPolicy"` or failing when `grep -q` matches). Update the command
in that test (the awk pipeline followed by the grep check) so it reliably fails
if `topologySpreadPolicy` appears in the TLS section.

In `@packages/apps/opensearch/README.md`:
- Around line 27-32: The README contains a duplicate heading "### Common
parameters" (first occurrence and a second before the table with
`topologySpreadPolicy` and `version`) which triggers MD024; rename the second
heading to a distinct title such as "### Common parameters (additional)" or "###
Additional parameters" so it no longer duplicates the earlier "### Common
parameters" heading, keeping the table and surrounding content unchanged.

---

Nitpick comments:
In `@packages/apps/opensearch/tests/tls_test.yaml`:
- Around line 29-249: Add a new failedTemplate test case to this tls_test.yaml
suite that sets a release name longer than 47 characters (e.g. a 48-char string)
and asserts a failedTemplate with an errorMessage indicating the
release-name-length guard (for example: "release name must be at most 47
characters" or the existing guard message). Place it alongside the other TLS
cases (near the top-level tests block) and use the same structure as other cases
(set: release: name: "<48-char-string>" and asserts: - failedTemplate:
errorMessage: "<expected message>"). Ensure the assertion uses the existing
failedTemplate symbol so the CI will catch regressions to the 47-char guard.
🪄 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: 07f954cc-c19f-420d-a077-ef3cab5e1100

📥 Commits

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

📒 Files selected for processing (13)
  • api/apps/v1alpha1/opensearch/types.go
  • api/apps/v1alpha1/opensearch/zz_generated.deepcopy.go
  • hack/check-opensearch-readme.bats
  • packages/apps/opensearch/Makefile
  • packages/apps/opensearch/README.md
  • packages/apps/opensearch/templates/_tls.tpl
  • packages/apps/opensearch/templates/certmanager.yaml
  • packages/apps/opensearch/templates/external-svc.yaml
  • packages/apps/opensearch/templates/opensearch.yaml
  • packages/apps/opensearch/tests/tls_test.yaml
  • packages/apps/opensearch/values.schema.json
  • packages/apps/opensearch/values.yaml
  • packages/system/opensearch-rd/cozyrds/opensearch.yaml

Comment thread hack/check-opensearch-readme.bats
Comment thread packages/apps/opensearch/README.md 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 — external: true silently auto-enables a chart-managed HTTP TLS mode that the chart itself documents as breaking the operator's securityadmin job, so security-config reconciliation (users/roles/audit) stops applying on external deployments; this is documented, not fixed or gated.

Business context: add cert-manager-backed TLS for the OpenSearch HTTP API (9200) and Dashboards (5601) in the tenant namespace, leaving operator-managed transport mTLS (9300) untouched.

Blockers

B1: external: true auto-enables an HTTP TLS mode that breaks securityadmin reconciliation
File: packages/apps/opensearch/templates/_tls.tpl:13, packages/apps/opensearch/templates/opensearch.yaml:33-38
Issue: when tls.enabled is unset, the helper turns chart-managed HTTP TLS on whenever external: true. This switches the HTTP listener from the operator-generated cert to a cert-manager leaf signed by <release>-http-ca. The operator's securityadmin job connects to the HTTP listener with an admin cert and a CA bundle rooted in the transport CA (/certs/ca.crt), which does not include <release>-http-ca. The -icl/-nhnv flags relax cluster-name and hostname checks only, not CA-chain verification, so the handshake fails, the job exits non-zero, and <release>-security-config (users/roles/audit/password policies) is never applied or refreshed.
Evidence: opensearch.yaml:35 sets the HTTP secret to <release>-http-cert when TLS is on; certmanager.yaml:51-77 issues that leaf from <release>-http-ca, distinct from the operator transport CA <release>-ca. The chart's own KNOWN ISSUE / KNOWN LIMITATION comments (certmanager.yaml:18-23, values.yaml:101-110) acknowledge that securityadmin "cannot verify the HTTP TLS chain". The latest commit (a6ee81b) adds a workaround (tls.enabled: false) to the docs but does not change the auto-on default.
Impact: an existing external OpenSearch deployment silently loses security-config reconciliation upon upgrade, with no error surfaced to the operator the platform targets; auto-on via external makes a documented-broken mode the default for external deployments.
Fix (pick one): (a) do not auto-enable chart-managed HTTP TLS from external — require an explicit tls.enabled: true opt-in, keeping operator-managed certs as the external default until the cross-trust gap is resolved; (b) overlay <release>-http-ca's ca.crt into the admin job's trust bundle; or (c) sign the HTTP cert from the operator transport CA.

Non-blocking follow-ups

  1. The cert-manager leaf secret opensearch-<name>-http-cert (including the server private key tls.key) is surfaced to the tenant via the cozyrds secrets.include list (packages/system/opensearch-rd/cozyrds/opensearch.yaml:35-36). Namespace-scoped, not a cross-tenant escalation — but clients only need ca.crt/the public cert to trust the endpoint; exposing the private key is unnecessary. Compare kafka-rd which exposes a CA-only secret. Prefer a CA/public bundle.
  2. The CA cert usages include key encipherment (packages/apps/opensearch/templates/certmanager.yaml:57-60), a no-op for a CA; the conventional profile is [signing, cert sign, crl sign]. Cosmetic.

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

Copy link
Copy Markdown
Contributor Author

Addressed your review.

Blockers:

  • B1 (securityadmin cross-trust gap) — documented as a known limitation in 30d25a616. Per the analysis, securityadmin's -icl -nhnv flags don't relax the CA chain check, and the upstream operator currently exposes no API to provide a merged CA trust bundle. Without that, this gap can't be closed from the chart side. The doc block in values.yaml next to the tls.enabled field flags this so operators understand the constraint before enabling chart-managed HTTP TLS. To revisit when upstream supports a merged trust bundle field.

Non-blocking follow-ups:

  • FU1 (tls.enabled: false falls through to operator-generated self-signed) — true; the off-branch produces operator-managed TLS, not plaintext. Not addressed in this PR; behavior unchanged.
  • FU2 (key encipherment unusual on CA) — CA usages unchanged in this pass. CA now uses ECDSA P-256 (see series-wide note below) and the unused bit is harmless; can be removed in a follow-up.
  • FU3, FU4 — informational, no change.

Series-wide cleanups applied here too:

  • Schema cleanup (43d7801e2): 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 ab33611ea. Cert chain reissue on first reconcile after upgrade; existing tenant trust anchors must be refreshed.

Ready for re-review.

@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/system/opensearch-rd/cozyrds/opensearch.yaml (1)

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

Add nested keysOrder entry for tls.enabled.

The keysOrder array follows a pattern where parent paths are followed by their important nested properties (e.g., ["spec", "images"]["spec", "images", "opensearch"], ["spec", "dashboards"]["spec", "dashboards", "enabled"]). The tls object contains only one property, enabled, which should be included in keysOrder for consistent dashboard UI field ordering.

📋 Proposed keysOrder fix
-    keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "topologySpreadPolicy"], ["spec", "version"], ["spec", "tls"], ["spec", "images"], ["spec", "images", "opensearch"], ["spec", "nodeRoles"], ["spec", "nodeRoles", "master"], ["spec", "nodeRoles", "data"], ["spec", "nodeRoles", "ingest"], ["spec", "nodeRoles", "ml"], ["spec", "users"], ["spec", "dashboards"], ["spec", "dashboards", "enabled"], ["spec", "dashboards", "replicas"], ["spec", "dashboards", "resources"], ["spec", "dashboards", "resourcesPreset"]]
+    keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "topologySpreadPolicy"], ["spec", "version"], ["spec", "tls"], ["spec", "tls", "enabled"], ["spec", "images"], ["spec", "images", "opensearch"], ["spec", "nodeRoles"], ["spec", "nodeRoles", "master"], ["spec", "nodeRoles", "data"], ["spec", "nodeRoles", "ingest"], ["spec", "nodeRoles", "ml"], ["spec", "users"], ["spec", "dashboards"], ["spec", "dashboards", "enabled"], ["spec", "dashboards", "replicas"], ["spec", "dashboards", "resources"], ["spec", "dashboards", "resourcesPreset"]]
🤖 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/system/opensearch-rd/cozyrds/opensearch.yaml` at line 29, The
keysOrder array is missing a nested entry for the tls.enabled property, causing
inconsistent UI ordering; update the keysOrder array (the keysOrder declaration)
to include ["spec","tls","enabled"] immediately after ["spec","tls"] so the tls
object’s enabled field is explicitly ordered like the other nested properties
(e.g., follow the pattern used for ["spec","images"] ->
["spec","images","opensearch"] and ["spec","dashboards"] ->
["spec","dashboards","enabled"]).
🤖 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/opensearch/values.schema.json`:
- Around line 146-147: The description for tls.enabled in values.schema.json
incorrectly mentions "null" as a tri-state value while the schema enforces
"type": "boolean"; update the source annotation in values.yaml for the
tls.enabled field to say "unset/omitted" (not "null") and then regenerate the
JSON schema using the generator (cozyvalues-gen / run make generate) so
values.schema.json is rebuilt from values.yaml rather than editing
values.schema.json manually.

In `@packages/apps/opensearch/values.yaml`:
- Around line 101-109: The workaround text is misleading about operator-managed
HTTP TLS: clarify that when using external: true (external deployment), leaving
tls.enabled unset enables chart-managed TLS, so to use operator-managed HTTP TLS
in external mode users must explicitly set tls.enabled: false; update the
paragraph to replace the suggestion "keep tls.enabled unset (operator-managed
mode)" with guidance "set tls.enabled: false for operator-managed HTTP TLS when
external: true" and mention the involved keys tls.enabled and external to make
the required configuration change explicit.

---

Outside diff comments:
In `@packages/system/opensearch-rd/cozyrds/opensearch.yaml`:
- Line 29: The keysOrder array is missing a nested entry for the tls.enabled
property, causing inconsistent UI ordering; update the keysOrder array (the
keysOrder declaration) to include ["spec","tls","enabled"] immediately after
["spec","tls"] so the tls object’s enabled field is explicitly ordered like the
other nested properties (e.g., follow the pattern used for ["spec","images"] ->
["spec","images","opensearch"] and ["spec","dashboards"] ->
["spec","dashboards","enabled"]).
🪄 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: 0240e1db-9017-41db-a1eb-b8364cbff38e

📥 Commits

Reviewing files that changed from the base of the PR and between af76d5f and ab33611.

📒 Files selected for processing (5)
  • packages/apps/opensearch/templates/certmanager.yaml
  • packages/apps/opensearch/tests/tls_test.yaml
  • packages/apps/opensearch/values.schema.json
  • packages/apps/opensearch/values.yaml
  • packages/system/opensearch-rd/cozyrds/opensearch.yaml

Comment thread packages/apps/opensearch/values.schema.json Outdated
Comment thread packages/apps/opensearch/values.yaml Outdated

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. My prior concern about the securityadmin cross-trust limitation is now explicitly documented:

  • 30d25a616 docs(opensearch): document securityadmin cross-trust limitation in values.yaml — the chart's leaf cert is signed by the chart's own CA, but the OpenSearch operator runs securityadmin against the HTTP listener using the transport CA that the operator manages separately. The -nhnv flag passed by the operator's securityadmin job skips hostname verification but still validates the chain, so a chart CA ≠ transport CA means the bootstrap apply may fail until the operator's transport CA is overlaid into the chart's trust. This is now called out at the values doc level so operators know what they're signing up for.
  • af76d5f19 test(opensearch): add failedTemplate case for the 47-char release-name guard + 15f97fc61 fix(hack): correct negative grep assertion in opensearch README bats test — the chart-side guard against names too long for the operator's StatefulSet-named-as-resource-name convention is now positive- and negative-tested.
  • ab33611ea refactor(opensearch): switch CA and leaf private keys to ECDSA P-256 + 43d7801e2 refactor(opensearch): drop null from tls.enabled schema, use kindIs invalid pattern — same modernization as the rest of the batch.

Leaf usages [server auth, signing, key encipherment]signing is cert-manager's alias for digital signature (pkg/api/util/usages.go maps both to KeyUsageDigitalSignature). No client auth because the chart cert serves only the HTTP listener; transport-side mTLS is operator-owned with the separate transport CA. dashboard RBAC exposes only <release>-credentials, no TLS Secret leak. Clean.

*/}}
{{- define "opensearch.tls.enabled" -}}
{{- if kindIs "invalid" .Values.tls.enabled -}}
{{- .Values.external | default false | toString -}}

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.

Auto-enabling chart-managed HTTP TLS from external: true makes a mode the chart documents as breaking securityadmin (cross-trust gap between transport CA and <release>-http-ca) the default for external deployments. Security-config reconciliation (users/roles/audit) silently stops applying — the latest commit documents a workaround (tls.enabled: false) but doesn't change the default. Gate this behind an explicit tls.enabled: true opt-in, or resolve the CA-bundle overlap, before auto-on is safe.

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.

The cross-trust gap is closed rather than gated. The CR now passes the chart CA to the operator as spec.security.tls.http.caSecret: <release>-http-ca, so the operator signs the securityadmin admin certificate with that same CA and anchors the HTTP listener on it — both sides end up on one anchor instead of two, and the security config applies. No operator bump needed; the field exists in the pinned 2.8.0.

One real constraint came out of reading the operator source: adminCAName() only honours http.caSecret at OpenSearch >= 2.0.0 and silently falls back to the transport CA below it. So on version: v1 the old mismatch would come back with no error anywhere. The chart refuses to render chart-managed HTTP TLS there — explicit tls.enabled: true fails with the mechanism named, auto-on degrades to operator-managed instead of breaking an existing external release on upgrade.

Your two follow-ups are also fixed: the tenant no longer receives the server private key (the ResourceDefinition selects a key-free ca.crt projection by label instead of listing the leaf Secret), and the CA usages are down to [signing, cert sign].

…otate

Passing http.caSecret is what lets securityadmin verify the HTTP endpoint, but
it also changes how the operator mounts the material: with a CA secret set it
switches from mounting the whole secret directory to three individual mounts,
each carrying SubPath. The kubelet never refreshes a subPath-mounted secret
file, and nothing restarts the pods when the secret changes, so a renewal
reaches the secret and stops there.

A one-year leaf with a thirty-day renewBefore therefore promised a rotation
that could not happen: cert-manager would reissue while the pods kept serving
the old certificate until it expired outright, taking HTTPS on 9200 down and
breaking securityadmin roughly a year after install. Give the leaf the same
ten-year lifetime as the CA that signs it, drop the renewBefore that cannot be
honoured, and say plainly that rotating it means deleting the secret and
restarting the pods.

Also document what the certificate means for a client: it is issued by a
per-release private CA, delivered as the key-free <release>.tenant-ca secret,
and the external names it covers are derived from the tenant host — which is
what external-dns publishes, and not the in-cluster domain older releases used.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The trust anchor reaches the tenant through two different labels on two
different objects: publish-ca-cert marks the key-bearing source the extraction
controller reads, and tenant-ca marks the key-free projection it writes, which
is what the ApplicationDefinition selects. Nothing enforced that separation.
Because selection is purely by label, renaming one key on the CA Certificate's
secretTemplate would have made the CA private key tenant-readable with every
test in the repo still green — a one-line edit past a boundary the design
calls deliberate.

Pin it from both sides: the CA Certificate must not carry the projection's
label, and the ApplicationDefinition must not select by the source's.

Also give _namespace a default before dereferencing it. The existing check
claimed to fail clearly when the tenant host is missing, but only covered
_namespace present and empty; with the key absent entirely the render died on
a raw nil pointer before the message could be emitted.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Three corrections, all where the previous wording would have misled someone
making a security decision.

The external access note claimed the published DNS names are the names the
certificate carries SANs for. That is true only under chart-managed TLS. With
TLS operator-managed — turned off explicitly, or fallen back to below
OpenSearch 2.0.0 — the operator issues for in-cluster names only, so a client
reaching the published name cannot verify it at all. Say which mode covers
which names.

Turning chart-managed TLS off does not withdraw the trust anchor. Helm removes
the certificates but cert-manager keeps their Secrets, so the CA keeps
matching the publish label and the tenant keeps receiving it, while the
endpoint has already moved back to an operator-issued certificate under a
different CA. Nothing the chart renders can prevent that — it does not own the
Secret — so name the manual cleanup instead of leaving it to be discovered.

The nil guard on tls was commented as handling an explicit null. Helm coalesces
that back to the default in values.yaml, so nil never reaches the template and
no test can exercise it; the comment now says it is defensive only.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
… bad usage

Enabling chart-managed TLS replaces the certificate the operator was issuing,
so any name the operator covered and the chart does not stops verifying the
moment TLS goes on. The leaf was missing three: <release>.<namespace>,
<release>-dashboards.<namespace>, and the <release>-discovery service. The
two-label form is the one that matters most — with ndots:5 and the default
search list it is what a client in another namespace actually resolves, and
therefore what its TLS verification runs against. This was reachable without
anyone opting in: with external access on and the switch unset, the tri-state
turns TLS on by itself, so an upgrade alone would have narrowed the coverage
of a working release.

Also drop key encipherment from both certificates. RFC 5480 section 3 forbids
it for ECDSA keys, which perform no key transport, and both keys here are
ECDSA P-256 — it was left over from when they were RSA.

The tenant-exposure check now asserts the shape of secrets.include rather than
grepping for one spelling of a name. It is the only thing standing between the
CA private key and the tenant, and the grep passed a leak written any other
way; every entry must now be either the credentials Secret or the key-free
tenant-ca projection.

Correct the external-dns note while here: external-dns runs upsert-only, so
re-applying publishes the new name but never withdraws the old record.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
… needed

Two things a user has to know that the tri-state documentation did not say.

An existing external release has the switch unset and runs operator-managed
TLS today, so it moves to chart-managed TLS the first time it reconciles after
this ships. That rolls the cluster and re-anchors it on a new CA — anything
pinned to the operator's CA has to be repointed. The choice is deliberate and
matches what a new release gets, but it must not be discovered by watching a
data cluster restart, so say it, and pin the resolution in a test of its own
rather than leaving it to incidental coverage.

The manual restart was framed as something rotation needs. It is wider than
that: the certificate is mounted by subPath and never refreshed in place, so
changing external access, the tenant host or the cluster domain also reissues
it while the pods keep serving the old one, with no error to notice. Say that
under both parameters, since external is the one a user actually touches.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The check that no key-bearing Secret reaches the tenant iterated over whatever
the selector returned, and a loop over nothing is a green test. Restructure
spec.secrets.include, or run where yq is not installed, and it reported ok
while checking nothing at all — on the one assertion standing between the CA
private key and the tenant. Require yq up front, and assert the entry count
before judging the entries, so the list being what we think it is becomes part
of the claim rather than an assumption underneath it.

Also correct what the release-name guard says it protects. Certificate, Issuer
and Secret names are DNS subdomains bounded at 253, which nothing here comes
near; the real limits are the 63-char DNS label on the SANs, where
<release>-dashboards binds at 52, and the 64-char X.509 common name, which
binds at 56. The 47-char guard sits under all of them and is unchanged — only
the reasoning was wrong, and someone relaxing it later would have started from
a premise that is not true. Pin both sides of the boundary now, not just the
rejected one.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
grep exits non-zero when it counts nothing, so under set -e the count assertion
killed the test before its own message could say what was expected. The check
still failed, which is what matters, but it failed as a bare grep trace instead
of naming the invariant it was defending.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…oint

One combined leaf covered both services, so the Dashboards pod was mounted a
private key whose SANs included every OpenSearch API name — the in-cluster
ladder and, with external access on, the public hostname. Dashboards is the
more exposed of the two and the more likely to be compromised, and anything
that took it could then present a valid certificate for the API endpoint. The
ten-year lifetime the subPath mount forces makes that a ten-year window.

The operator never did this: it issues <cluster>-http-cert and
<cluster>-dashboards-cert separately, so collapsing them was a widening this
chart introduced. Issue one certificate per service instead, both from the
same CA so a client trusting the published anchor still verifies both, each
carrying only its own names. The Dashboards certificate renders last and only
when Dashboards is enabled, so the other four keep their positions.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Splitting the certificate left four statements describing the design it
replaced, one of them with an operational consequence.

The manual procedures named only the API certificate. Changing external access,
the tenant host or the cluster domain reissues both, so an operator following
them literally restored the API endpoint and left Dashboards serving a
certificate that no longer matched its own name — the silent failure the
paragraph exists to prevent. It now covers both sides, and says the Dashboards
deployment restarts separately, since it is not an OpenSearch pod. The
disable-path cleanup likewise left the Dashboards Secret behind.

The template header still described one leaf covering both services, directly
contradicting the explanation further down the same file, and the external
access note still spoke of a single certificate carrying both names.

The Dashboards lifetime comment justified itself by the subPath mount, which is
the one thing that does not apply there: the operator mounts a provided
Dashboards certificate as a whole volume, so the kubelet does refresh it. The
ten years are right for a different reason — Dashboards reads the files once at
startup and nothing restarts it when they change.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…ing has

The API leaf is pinned three ways: it may not take a name the operator
generates into, it may not be offered to the CA-extraction controller as a CA
source, and it may not carry the other service's names. The dashboards
certificate inherits every one of those hazards and had only positive
assertions, so all three failures passed the suite green — including undoing
the split itself by putting the API names back on the key the more exposed pod
holds.

Mirror the three, and the ECDSA usages assertion with them, so each fails
against the corresponding regression instead of documenting the safe shape and
checking none of it.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…ly what it must not

The guards added for the dashboards certificate were all prohibitive, so it
ended up well defended against being the wrong thing and unchecked on being
the right one. Pointing its issuerRef at the bootstrap self-signed Issuer
instead of the shared CA passed the whole suite: cert-manager still issues,
Dashboards still serves HTTPS, the release still goes Ready, and the anchor
the platform publishes silently stops verifying that endpoint. So did
switching the key to RSA, reintroducing the short lifetime that expires in
place, and pointing it at the API certificate's Secret so two Certificates
race over one object while the CR names only one of them.

Mirror the structural assertion the API leaf already has — kind, secret name,
issuer, key algorithm, size and rotation policy — and extend the lifetime pin.
That also establishes that the document really is the dashboards certificate,
without which the prohibitive guards would pass against whatever landed at
that position if the order ever moved.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…tificate

Neither leaf pinned isCA, and no other assertion can stand in for it. certSign
is derived from that field rather than from spec.usages, so flipping a leaf to
isCA: true renders an unchanged usages list, satisfies the exact-length and
no-certSign pins, and still issues a working intermediate CA — one whose
private key lives in the pod and chains to the anchor tenants are handed. That
is a wider hole than the key-sharing the per-service split closes: it signs for
any name, not just the sibling service's.

issuerRef was pinned by name but not by kind, and on the dashboards leaf only.
A reference that resolves to a different object of the same name still issues,
still serves, and still goes Ready while anchoring on something the published
trust anchor does not cover — the same silent failure already argued for the
dashboards leaf, which applies just as well to the more exposed API endpoint
and to the CA itself. Pin kind and group everywhere the name is pinned.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The comparison was described as running against the resolved image tag. It runs
against the resolved version string — the same value that goes into
spec.general.version, which is the field the operator branches on. That
distinction is the reason the floor is correct: an images.opensearch override
changes the running image without moving spec.general.version, so it cannot
put the operator on one side of 2.0.0 while the chart believes the other.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The external Dashboards Service selected opster.io/opensearch-cluster together
with app.kubernetes.io/component. The operator labels Dashboards pods with
opensearch.cluster.dashboards and nothing else — app.kubernetes.io appears
nowhere in it — so the selector matched no pod and the LoadBalancer stood up
with no endpoints and no error to show for it.

That was survivable while nothing pointed at it. It is not now: the certificate
carries a SAN for the Dashboards hostname and the parameter documentation
describes it as a reachable external endpoint, so the chart promises something
the Service cannot deliver. Select the label that exists, and pin it against
the two that do not.

While here, correct what the SAN comment claims about parity with the operator.
The fully-qualified rung is deliberately different: the operator builds it from
its own DNS_BASE, left at the upstream cluster.local default, while the chart
uses the cluster domain the platform runs on. The chart's name is the one that
resolves — the difference is a fix, not a gap.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Listing the anchor in the ApplicationDefinition was only half the wiring. That
entry feeds the lineage webhook, which labels the Secret as a tenant resource
for the registry; it is not an access grant. Direct read comes from the chart's
own Role, and that still named only the credentials Secret — so a tenant with
use access got 403 on the very anchor this change exists to hand them. kafka
carries both legs for its CA Secrets; this brings opensearch in line.

Only the key-free projection is granted. The cert-manager CA and both leaves
hold a private key and must never appear in that list, so the new test asserts
the grant is exactly two names and that none of the three key-bearing Secrets
is among them.

The bats case named itself after exposing the CA to the tenant while asserting
only the label; it now says which half it checks and points at the other.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
… empty

Requiring the tenant host turned a supported platform state into a failed
release. A cluster with no root host leaves that value empty all the way down
the tenant tree, and Helm's required rejects the empty string, so external:
true stopped rendering — not just the external-dns annotation but the whole
chart, taking the OpenSearchCluster CR, the credentials Secret and the RBAC
with it. A running release would have gone not-Ready on its next reconcile,
and it fired with chart-managed TLS off too, reaching users who had opted out
of this feature entirely. The same configuration renders on main.

Degrade instead. The LoadBalancers still get addresses and still serve; the
external-dns hostname is simply not published when there is no name to publish,
and the certificate keeps its in-cluster SANs and omits the external ones for
the same reason. Both are asserted, including that the cluster itself still
renders, which is the part whose loss actually hurt.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The empty-host case rendered with dashboards disabled, so it only ever reached
the API certificate. Breaking the guard on the dashboards leaf alone left the
suite fully green while that certificate emitted a trailing-dot SAN — on the
more externally exposed of the two services.

Enable dashboards in that case and assert the fifth document the same way as
the fourth. Breaking either guard alone now fails.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…uire

The guard rejected any release name over 47 characters. The platform's own cap
is 53, so names between the two are reachable, render today, and stopped
rendering here — including on the auto-on path, where a release that never
asked for chart-managed TLS would have stopped templating entirely and taken
its HelmRelease NotReady.

Nothing was violated at those lengths. The binding constraint is the 63-char
DNS label on the SANs, and which SAN binds depends on configuration: with
Dashboards enabled it is <release>-dashboards at 52, otherwise
<release>-discovery at 53. The old message even printed the evidence, blaming
a dashboards SAN that is not emitted when Dashboards is off.

Compute the limit from the longest label actually built, and name that label in
the message. The only rejected case left is one where the certificate genuinely
could not be issued. Both boundaries are pinned from both sides, and the
long-named auto-on release is pinned as still rendering.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The bound was derived from certificate SAN labels alone, which missed the
longest name the chart produces: the <release>-dashboards-external Service, at
20 characters of suffix. Service names are DNS-1035 labels capped at 63, unlike
the Certificate and Secret names the reasoning was based on, which are
subdomains capped at 253 and never bind. So the real limit with external access
and Dashboards both on is 43, and relaxing the guard to 52 made a configuration
the API server rejects reachable — the Service is refused and the release fails
with nothing in the chart to explain why.

The bound also has nothing to do with TLS. That Service renders on external
access alone, so a guard living only in the certificate template could not see
the case at all.

Move the check into a helper that picks the longest suffix the current values
actually build — the external Dashboards Service, else the Dashboards name,
else the discovery SAN, else the external Service — and invoke it from both
templates, so it applies with chart-managed TLS off as well. Each boundary is
pinned from both sides, and the message names the object that binds.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The guard ran from the certificate and external Service templates, so a release
with Dashboards enabled but external access and chart-managed TLS both off
reached no invocation. The operator still creates a <release>-dashboards
Service for it — a DNS-1035 label bounded at 63 — so a 53-character release
rendered clean and then failed at the API server with no chart-level error.
That is the same shape as the external Service gap closed previously, on the
axis that was left behind.

Invoke the guard from the cluster template, which renders unconditionally and
therefore covers every remaining path. The helper is idempotent, so the paths
that already checked are unaffected.

Also drop the limits table left behind in the certificate template. It still
described a 52/53 bound with no mention of the external Service, contradicting
the helper that now owns those numbers — and a stale second copy of that
reasoning is exactly what produced the original mistake.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@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 Jul 19, 2026
@lexfrei Aleksei Sviridkin (lexfrei) added the do-not-merge/hold Indicates that a PR should not merge because someone has issued /hold label Jul 19, 2026
index on an absent map is a hard render error rather than a nil result, so
the default that follows it never applied and the whole chart failed to
render instead of the one value being read. The platform always injects
_cluster, which is why this only showed up rendering the chart alone.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…rong

The RBAC comment pointed at kafka as carrying both a registry entry and a
read grant. Kafka grants its two CA secrets by name and has no label
selector at all, so a reader following that pointer found a counter-example
rather than the pattern being described.

The name table called -discovery a TLS-only SAN label. The operator creates
that Service on every path, so the name exists with TLS off too. Record
which bounds Helm's own 53-character release-name limit leaves reachable
while the table is being corrected.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Two suites in the TLS file carried no name, so both reported as a blank
PASS line and a failure in either gave no clue which one broke.

The readme check named for asserting the parameter appears outside the TLS
section only asserted that it appears at all; the section half is the
preceding test. Name it for what it does — it is the guard that stops that
preceding test passing on a README that dropped the row entirely.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…tlives the pin

The comment justified the matched lifetime entirely by the subPath mount
shape, which is a property of the pinned operator rather than of OpenSearch:
upstream has since moved those branches back to whole-directory mounts, so a
routine vendored-chart bump would quietly retire the stated rationale while
the conclusion stayed correct.

State the version-independent reason first — certificates are read once at
startup and no pod restarts on change — and mark the mount-shape half as
pinned-version detail.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei

Copy link
Copy Markdown
Contributor

Closing out the remaining feedback. Four fixes pushed; three findings did not survive checking.

Correction to an earlier claim in this thread. It was stated that the gap could not be closed from the chart side because "the upstream operator currently exposes no API to provide a merged CA trust bundle". That is wrong, and it is what kept this open longer than it needed to be. spec.security.tls.http.caSecret is exactly that API and exists in the pinned 2.8.0: adminCAName() returns Http.CaSecret.Name for clusters at 2.0.0 and above, and providedCaCert() reads a cert-manager Secret through the cert-manager.io/issuer-kind annotation, remapping tls.crt/tls.key to ca.crt/ca.key. Passing it makes the operator sign the admin certificate with the chart's CA and anchor the HTTP listener on it, so there is one anchor rather than two. No merged bundle is needed and no operator bump either. The version gate is real, though, and that is the part worth keeping from the original concern: below 2.0.0 the operator falls back to the transport CA and the mismatch returns with nothing logged, so the chart refuses to render chart-managed HTTP TLS there instead of letting it fail silently at runtime.

Fixed:

  • The cluster-domain read used index on _cluster unguarded. That is a hard render error when the map is absent, not a miss returning nil, so the | default "cozy.local" after it never ran and the whole chart failed rather than the one value. Every existing suite injects _cluster, which is why nothing caught it; there is now a suite that deliberately does not.
  • The RBAC comment cited kafka as carrying both a registry entry and a read grant. Kafka grants its two CA secrets by name and has no label selector at all, so anyone following that pointer found a counter-example.
  • The name-guard table described -discovery as a TLS-only SAN label; the operator creates that Service on every path. Also recorded which bounds Helm's own 53-character release-name limit actually leaves reachable, since two of the four cannot fire.
  • Two test suites had no suite: key and reported as blank PASS lines, and the readme check named for asserting a parameter appears outside the TLS section only asserted that it appears at all.

Not changed, with reasons:

  • Nested ["spec","tls","enabled"] in keysOrder — this would make opensearch the odd one out. kafka, nats, qdrant and postgres all expose tls.enabled and none of them carry the nested entry; the convention for this key across the series is the flat form already there.
  • A regression test for the 47-character release-name guard — the suite has these, and the number is no longer 47: the bound depends on which suffix the configuration actually produces, so the cases pin 43 and 52 with the exact failure message, plus the accepting cases one character under each.
  • Renaming the Go field Tls to TLS — house convention, not an oversight. postgresql, nats, kafka and qdrant all declare Tls TLS; changing only this one trades a style nit for real inconsistency.

Two limitations in the PR body stand as written and are not addressed here: the operator's DNS_BASE is still cluster.local while the platform runs a different cluster domain, which is a separate cross-package fix, and there is no e2e covering the TLS path.

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 do-not-merge/hold Indicates that a PR should not merge because someone has issued /hold 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.

2 participants