Repository navigation
docs(external-database-exposure): make the plaintext-exposure rule per engine - #91
Conversation
…r engine The Security section had one admission rule for every engine, written before redis had TLS, and it only fits the postgres chart's tri-state. For postgres it closes nothing: CloudNativePG keeps ssl on and its default pg_hba accepts plaintext whatever tls.enabled says. Redis TLS is opt-in and fixed at creation, so the implemented rule requires TLS when an instance is created external, refuses exposure after creation, and refuses TLS changes while exposed. Record 0002 keeps the alternatives that lost. Also correct the Postgres SNI mechanism. A client is unroutable when it sends a plaintext SSLRequest before the handshake, which a passthrough listener never reads; the server version does not decide that. A direct-negotiation client against a pre-17 server does not fall back: it is routed and the server closes the connection. The certificate hooks section now names the redis and mariadb SAN hooks that arrived with their TLS support instead of calling them open pull requests. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID:
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. Comment |
<!-- Thank you for making a contribution! Here are some tips for you: - Use Conventional Commits for the PR title: `type(scope): description` - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore - Scopes are not an exhaustive list — pick the most specific scope for the change and extend the list when a genuinely new area appears. Examples: - System components: dashboard, platform, operator, cilium, kube-ovn, linstor, fluxcd, cluster-api - Managed apps: postgres, mariadb, redis, kafka, clickhouse, virtual-machine, kubernetes - Development and maintenance: api, hack, tests, ci, docs, maintenance - Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or add a `BREAKING CHANGE:` footer - If it's a work in progress, consider creating this PR as a draft. - Don't hesistate to ask for opinion and review in the community chats, even if it's still a draft. - Add the label `kind/backport` if it's a bugfix that needs to be backported to a previous version. --> ## What this PR does An admission policy now refuses a Redis release that would expose a plaintext endpoint outside the cluster. Redis never turns TLS on by itself, so `external: true` without `tls.enabled: true` is exactly that case. The error names the release and a remedy that works. The rule has three parts, because TLS is fixed when a Redis instance is created: the RedisFailover schema refuses a change, while the Redis API accepts one and the HelmRelease then fails. A spec's `tls.enabled` is therefore a request, not proof of what the instance runs. - On create, `external: true` needs `tls.enabled: true`. - On update, turning `external` on is refused whatever `tls.enabled` says. - While `external` stays on, `tls.enabled` cannot change. Before the first reconcile nothing else stops TLS being dropped from a release admitted as external with TLS. Withdrawing `external` in the same edit lifts the freeze, and the callers trusted by `cozystack-tenant-host-policy` are exempt, so an instance whose TLS was flipped earlier can still be unblocked. Edits to a release that was already external pass, so releases exposed in plaintext before this policy keep working. The guarantee covers the Redis app. A generic publisher such as `tcp-balancer` can still forward to a plaintext Redis, by design. This diverges from the Security section of the `external-database-exposure` proposal, which refuses only an explicit `tls.enabled: false` and covers every engine. That rule was written before redis had TLS and matches only postgres. Postgres is left out here on purpose: CloudNativePG keeps `ssl=on` and its default pg_hba accepts plaintext from any client that asks, whatever the chart's `tls.enabled` says, so an admission rule on that field closes nothing. That gap is #4619. The proposal update and a decision record are cozystack/community#91. The policy follows `cozystack-tenant-host-policy`: a ValidatingAdmissionPolicy in `cozystack-basics`, enforced inside `cozystack-api` for the aggregated kind. Its decisions are tested in Go by compiling the shipped template with the apiserver's own CEL compiler and running Redis objects through it, including the bypass sequences found in review. A structural chart test can't show what the policy admits. The redis README, the `external` field description and the threat model describe the new rule. Related: #4622 (valkey `external` is always plaintext), #4624 (the redis `tls.enabled` description implies a live switch). ### Screenshots <!-- REQUIRED for UI changes: attach screenshots or screen recordings demonstrating the visual impact of your changes. PRs with UI changes without screenshots will not be merged. --> ### Downstream repositories <!-- Cozystack is upstream for repositories that are NOT kept in sync with it automatically, and nothing in CI compares the two sides, so a change here can break them silently. Which change forces what, and which file to touch, is the trigger map in docs/agents/contributing.md under "Downstream Repositories". Walk it before you tick anything. IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM TO FILL IN: 1. Walk the trigger map against the actual diff of this PR, file by file. Do not decide from the PR title or from your memory of what you changed. 2. For every repository you tick, open the follow-up PR there and link it on the same line. A ticked box with no link claims work that does not exist, which is worse than an empty box. Search that repository for an open PR or issue covering it first, and link that instead of filing a duplicate. 3. When the follow-up is out of scope, or needs a decision that is not yours to make, open an issue in that repository instead, link that, and say so in the PR body. Do not push a speculative PR into another repository just to fill a line here. 4. Do not tick "No downstream repository is affected" to make the checklist go away. If you are unsure, leave every box empty and say so in the PR body, so a human decides. --> - [ ] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: I walked the trigger map against the diff and nothing in it fires, but I left the boxes empty because cozystack/community is reached anyway: the `external-database-exposure` proposal still states the old admission rule. Its update and a decision record are cozystack/community#91. ### Release note <!-- Write a release note: - Explain what has changed internally and for users. - Start with the same `type(scope):` prefix as in the PR title - Follow the guidelines at https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md. --> ```release-note feat(redis)!: a Redis release can be exposed outside the cluster only when it is created with `tls.enabled: true`. Creating one with `external: true` and no TLS, turning `external` on for an existing instance, or changing `tls.enabled` while `external` is on is refused at admission. Instances already external without TLS keep working and stay editable. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Redis admission checks now require TLS when external access is enabled at instance creation. They block enabling external access on an existing instance and changing TLS while external access remains enabled. Disabling external access in the same update allows TLS changes. * Existing instances that already had external access without TLS before the policy was introduced remain editable. * **Documentation** * Updated Redis configuration and security guidance to explain these restrictions and TLS migration behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
IvanHunters
left a comment
There was a problem hiding this comment.
Phase 0: Acceptance
✓ docs-only change on plaintext-exposure rule per engine
✓ companion to cozystack/cozystack#4639 (Redis admission policy)
✓ 115 lines, 2 files: README.md + decision record
Phase 2b: Factual Accuracy
✓ executed
Per-engine rule verified against #4639 implementation:
- VAP targets
resources: ["redises"]only - Three rule conditions: CREATE (external+tls), UPDATE (cannot turn external on), WHILE EXTERNAL (cannot change tls)
- Postgres, MariaDB, tcp-balancer excluded by design (documented correctly)
Phase 3c: Novel Hypotheses
✓ executed (0 hypotheses from novelty pass; auto_quick=true, docs-only)
Phase 5b: Render & Template Check
✓ N/A (docs-only, no templates)
Phase 5c: Config Schema & Values
✓ N/A (docs-only)
Phase 5d: Cross-Document Consistency
✓ executed
All three docs (README, decision record, threat-model update) consistently describe:
- Per-engine targeting (Redis only)
- Rule conditions and visibility
- Exclusion rationales
- Code references to #4639 files
Phase 7: Prior-Round Reconciliation
✓ N/A (first round)
Phase 8b: Mutation & Coverage
✓ N/A (docs-only, no code to mutate)
Verdict: APPROVE
Rationale: Documentation accurately describes the per-engine plaintext-exposure rule implementation and maintains consistent terminology across three documents. All factual claims verified against cozystack/cozystack#4639 (Redis admission policy). No gaps, no contradictions, no broken links.
Reviewed against: cozystack/cozystack#4639 per-engine rule for redis.enabled enforcement
Findings
None.
The admission rule in the Security section of
external-database-exposuredoes not hold for the engines it was written for, so this changes it to a per-engine rule and records why in decision 0002.The old rule refused an explicit
tls.enabled: falsewithexternal: truefor every engine. It was written before redis had TLS and only fits postgres's tri-state. For postgres it closes nothing: CloudNativePG keeps TLS offered and accepts plaintext from a client that asks, whatevertls.enabledsays (cozystack/cozystack#4619). Redis TLS, added in cozystack/cozystack#2729, is opt-in and fixed at creation, and the API accepts a change the operator refuses. So the rule implemented in cozystack/cozystack#4639 covers redis only: exposing it needs TLS at creation, an update cannot turnexternalon, andtls.enabledis frozen whileexternalstays on.The record keeps the alternatives that lost, with the test case in #4639 that pins each bypass. The same change corrects the Postgres SNI edge cases. A client without direct TLS negotiation does not lack SNI, it sends a plaintext protocol message first, which a passthrough listener never reads.
This should merge after cozystack/cozystack#4639, since the record states what that PR implements.
Before review
decisions/directory, or says below why none is needed.DCO
git commit --signoff).