Skip to content

feat(cozystack-basics): pin TenantProjection writes to helm-controller - #3410

Closed
Aleksei Sviridkin (lexfrei) wants to merge 2 commits into
mainfrom
feat/tenant-projection-writer-policy
Closed

feat(cozystack-basics): pin TenantProjection writes to helm-controller#3410
Aleksei Sviridkin (lexfrei) wants to merge 2 commits into
mainfrom
feat/tenant-projection-writer-policy

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds a ValidatingAdmissionPolicy pinning the writer of a TenantProjection to helm-controller's ServiceAccount, as defence in depth. The primary boundary is RBAC: no tenant role grants any verb on internal.cozystack.io, so this guards against platform-tier or widened-role writers, not tenants. Ships the cacert chainsaw suite that drives the deny and allow paths against a live apiserver.

Depends on #3407: the TenantProjection CRD it references ships there, and the chainsaw suite needs it at e2e runtime. Merge after #3407.

Split out from #3299.

feat(cozystack-basics): a ValidatingAdmissionPolicy pins TenantProjection writes to helm-controller as defence in depth.

Summary by CodeRabbit

  • New Features

    • Added admission controls that restrict creation and updates of tenant projections to the designated Flux service account.
    • Added end-to-end coverage for admission enforcement and CA certificate projection, including key exclusion and readiness checks.
    • Added automatic selection and installation support for the cacert test suite.
  • Bug Fixes

    • Added safeguards to prevent forged tenant projection requests and ensure the policy is only rendered on supported clusters.
  • Tests

    • Added unit, integration, and selection tests covering policy rendering, writer identity, and suite mapping.

Add a ValidatingAdmissionPolicy that restricts create, update and delete
of internal.cozystack.io TenantProjection objects to the shared flux
helm-controller ServiceAccount, as defence in depth behind the RBAC
boundary that already denies tenants any verb on the group. A chart
renders the sentinel through Flux, so no other writer has a legitimate
reason to touch it. A bats check pins the policy to the flux namespace
the platform installs helm-controller into.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add a chainsaw suite that exercises the CA-extraction feature end to end,
and wire it into the e2e and install selection scripts so a change to the
writer policy or the extraction controller runs it. The suite depends on
the TenantProjection CRD, so it is meaningful only once that resource is
installed.

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

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@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 Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 0c7a2138-a0cb-4158-ab23-4a11f47cc9d0

📥 Commits

Reviewing files that changed from the base of the PR and between aef9e20 and 20afe95.

📒 Files selected for processing (10)
  • hack/check-tenant-projection-writer-pin.bats
  • hack/e2e-chainsaw/README.md
  • hack/e2e-chainsaw/cacert/chainsaw-test.yaml
  • hack/select-e2e.sh
  • hack/select-e2e_test.bats
  • hack/select-install.sh
  • hack/select-install_test.bats
  • packages/system/cozystack-basics/templates/tenant-projection-writer-policy.yaml
  • packages/system/cozystack-basics/tests/tenant-projection-writer-policy-capability-gate_test.yaml
  • packages/system/cozystack-basics/tests/tenant-projection-writer-policy_test.yaml

📝 Walkthrough

Walkthrough

Changes

Tenant projection writer control

Layer / File(s) Summary
Admission policy and Helm validation
packages/system/cozystack-basics/templates/tenant-projection-writer-policy.yaml, packages/system/cozystack-basics/tests/tenant-projection-writer-policy*.yaml
Adds a capability-gated, fail-closed admission policy and binding that permits TenantProjection creation and updates only from the Flux ServiceAccount, with Helm-unittest coverage.
Admission and controller E2E scenarios
hack/e2e-chainsaw/cacert/*, hack/e2e-chainsaw/README.md
Adds Chainsaw coverage for forged-writer denial, Flux authorization, CA projection, key exclusion, ownership, labels, fingerprints, and readiness.
cacert suite selection and installation
hack/select-e2e.sh, hack/select-e2e_test.bats, hack/select-install.sh, hack/select-install_test.bats
Routes cozystack-basics policy changes to cacert and maps the suite to its required package sources, with selection tests.
Flux writer pin consistency checks
hack/check-tenant-projection-writer-pin.bats
Validates the pinned writer format, ServiceAccount name, and namespace against the Flux shard configuration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: area/testing

Suggested reviewers: androndo

🚥 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 matches the main change: pinning TenantProjection writes to the helm-controller ServiceAccount.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ 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/tenant-projection-writer-policy

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.

@dosubot dosubot Bot added area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/testing Issues or PRs related to testing (e2e, bats, unit tests) labels Jul 21, 2026

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I do not think this is needed. Multiple resources in Cozystack are exclusively platform managed, such as corev1 resources, but we don't enumerate their controllers in VAPs. I think the cost of the cluster admin's astonishment when he can't create a resource for whatever reason (maybe he's debugging something) is greater than the extra security benefit from this additional check.

@lexfrei

Copy link
Copy Markdown
Contributor Author

The primary boundary is RBAC and it already shipped in #3407: no tenant role has any verb on internal.cozystack.io, so a tenant cannot create a sentinel. This VAP is only defence in depth on top. The reason I thought it was worth it is that a TenantProjection is not an ordinary platform resource. It is a declaration that says "project this Secret's ca.crt to the tenant", so the threat it pins out is a platform-tier actor with a widened Secret or CRD grant who should not be minting trust anchors.

Your corev1 analogy holds though, and the astonishment cost is real. With CREATE and UPDATE pinned to flux, an admin debugging cannot hand-apply a sentinel. DELETE is ungated so a stuck one can still be removed, just not created.

The marginal-security-versus-astonishment call is yours. If you are not convinced, I will drop this PR. The controller's e2e is covered by the postgres chainsaw in #3407, so dropping the policy is a clean removal, not a loss of coverage.

@lexfrei

Copy link
Copy Markdown
Contributor Author

Closing, you convinced me. RBAC is the boundary and it already shipped in #3407: no tenant role has any verb on internal.cozystack.io. Pinning by username duplicates that in a more brittle way, and impersonating the flux service account bypasses it in one flag anyway, so it does not stop the platform-tier actor it was meant to stop. The controller's e2e is covered by the postgres chainsaw in #3407, so nothing loses coverage.

myasnikovdaniil added a commit that referenced this pull request Aug 17, 2026
…3463)

## What this PR does

Two documentation defects that both landed with the CA trust-anchor work
in #3407.

The Postgres README told a tenant to fetch `<release>.tenant-ca`. The
release name carries the `postgres-` prefix the application definition
applies, so the object is `postgres-<name>.tenant-ca`, and the postgres
chainsaw test shows exactly that for a resource named `test`. A tenant
following the README got `NotFound`. I confirmed the real name on a live
cluster while converting another engine onto the same contract.

The second is a comment in the controller and one in its RBAC chart,
both promising a companion `ValidatingAdmissionPolicy` that pins
sentinel writes to helm-controller. No such policy ships. It was
proposed in #3410 and rejected: authorizing by
`request.userInfo.username` duplicates RBAC in a more brittle form, and
the RBAC boundary already holds because no tenant role has any verb on
`internal.cozystack.io`. The comments now say that instead of pointing
at a guard that does not exist.

### Downstream repositories

Documentation only, no API or chart behaviour changes.
`cozystack/website` regenerates the Postgres reference page from this
README on the next stable tag, so no manual follow-up is needed there.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:

### Release note

```release-note
docs(postgres): the tenant CA trust anchor is `postgres-<name>.tenant-ca`, not `<release>.tenant-ca`.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated PostgreSQL TLS setup instructions with the correct tenant CA
Secret name and retrieval command.
* Clarified that tenant-accessible Secrets contain only the CA
certificate, while the original Secret contains the private key.
* Clarified CA sentinel access controls, including tenant isolation and
helm-controller-managed behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/testing Issues or PRs related to testing (e2e, bats, unit tests) area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants