## What this PR does Part of #2814. Part of #2811. This is the foundational ("keystone") piece of WS3: a canonical, reusable trust-anchor object that lets a tenant obtain `ca.crt` to verify a per-app TLS endpoint **without** read access to any object that also carries a private key. It unblocks the per-app TLS series (WS4/WS5) by giving every chart one agreed, key-free delivery shape instead of each chart inventing its own. ### The problem (verified against current `main`) Per-app TLS issues a per-release, self-signed CA. The only objects that hold `ca.crt` today also hold private keys: - the cert-manager CA Secret `<release>-ca` carries the CA private key (`tls.key`) — full trust-chain compromise if leaked (`packages/apps/nats/templates/certmanager.yaml`, the `isCA` Certificate); - the cert-manager leaf Secret `<release>-tls` carries the server private key (`tls.key`) plus `ca.crt` (same file, the leaf Certificate); - CNPG surfaces `ca.crt` only inside its user-credentials Secret (`packages/apps/postgres/templates/db.yaml`, the operator-managed TLS block). So any RBAC path that hands a tenant `ca.crt` by granting read on one of those Secrets also hands over a private key. On `main` today the cert-manager apps grant tenants no access to those Secrets at all, so a tenant currently cannot obtain `ca.crt` to verify the server — the gap this object closes safely. ### Design decision: key-free Opaque object delivered through `tenantsecrets`, not trust-manager I evaluated the two candidate delivery mechanisms. **Rejected — trust-manager.** A trust-manager `Bundle` reads its sources only from trust-manager's single configured "trust namespace"; its `namespaceSelector` governs only where the bundle is *distributed to*, not where it is *sourced from*. A per-release self-signed CA lives in the tenant namespace, so trust-manager cannot use it as a source, and projecting one shared CA to all tenants would defeat per-release isolation. I re-verified this against current upstream docs (cert-manager.io trust-manager) and the open per-namespace-trust-bundle request (cert-manager/trust-manager#131, which is target-side only). The prior analysis still holds. **Chosen — a `ca.crt`-only Opaque Secret surfaced through the existing tenant-secret API.** This generalizes the reference pattern from the redis work (#2729), where the operator publishes a CA-only Opaque Secret and RBAC is scoped to it instead of to the CA-private-key Secret. The new `cozy-lib.tls.caCertSecret` helper renders that object once, identically, for every chart: - `type: Opaque`, a single `ca.crt` data key, no `tls.key`/`tls.crt`; - label `internal.cozystack.io/tenantresource: "true"`; - fails closed on empty input and refuses any PEM that contains private key material. ### How the RBAC wiring works (no new roles needed) The label is the entire mechanism, and it routes through RBAC that already exists: - `pkg/registry/core/tenantsecret/rest.go` surfaces, under the virtual resource `core.cozystack.io/tenantsecrets`, exactly the namespace Secrets bearing `internal.cozystack.io/tenantresource=true` (`buildTenantSelector`, lines 199-213; label constants `pkg/apis/core/v1alpha1/tenantresource_types.go:3-4`; full `Data` copied through by `secretToTenant`, lines 84-103). - `packages/system/cozystack-basics/templates/clusterroles.yaml` grants `get/list/watch` on `core.cozystack.io/tenantsecrets` to tenant ServiceAccounts via `cozy:tenant:base` (lines 44-47) and to `use`/`admin`/`super-admin` subjects via `cozy:tenant:use:base` (lines 179-180). None of this touches raw core `secrets`, so attaching the label to a key-free object exposes the trust anchor and nothing else. - `internal/backupcontroller/credentials_projector.go:128-133` relies on the same rule in reverse — it deliberately *omits* the label so its key-bearing projection is not promoted to a `TenantSecret`. This helper is the positive counterpart: a key-free object that is safe to promote. `view` deliberately does not receive the trust anchor through this path: `cozy:tenant:view:base` grants only `core.cozystack.io/options` (lines 120-126), and `tenantsecrets` also includes credential Secrets, so granting it to a read-only role would leak passwords. The trust anchor reaches `use` and above plus tenant ServiceAccounts — the same access level at which connection credentials are already surfaced (the per-release dashboard Role binds at `use`, e.g. `packages/apps/postgres/templates/dashboard-resourcemap.yaml` via `cozy-lib.rbac.subjectsForTenantAndAccessLevel`). A chart that must show `ca.crt` at `view` level in the dashboard can additionally name the key-free Secret in its own per-release Role; that is a per-app detail, not a base-role change. ### Why a library helper and not a controller here Populating `ca.crt` stays the responsibility of whatever owns the PKI — the app operator (as the redis fork does) or a cert-manager chain resolved at the chart level. The helper is intentionally pure and value-driven so it renders deterministically and is the single shape every per-app PR converges on. A shared key-stripping projection controller is a reasonable future generalization but is out of scope for this foundational object. ### Convergence plan for the per-app TLS series (follow-up, not this PR) I did not modify any of the open per-app branches. Each should adopt the canonical object as follows: - redis #2729 — already publishes an operator-side CA-only Opaque Secret and already drops the grant on the CA-private-key Secret; converging means emitting it through the helper / carrying the `tenantresource` label so delivery matches the rest of the catalog. - nats, qdrant (merged, cert-manager) — emit the canonical object via the helper and withhold `<release>-ca` / `<release>-tls` from tenants. - mariadb #2680, opensearch #2682, rabbitmq #2683, mongodb #2692 — same: emit the canonical object, never grant a Secret that contains `tls.key`. ### Tests `helm-unittest` in the cozy-lib test chart (`make -C packages/tests/cozy-lib-tests test`) covers: the rendered object is `Opaque` with `ca.crt` and no `tls.key`/`tls.crt`; the `tenantresource` label is present; caller labels/annotations merge while the security label always wins; the helper fails closed on empty `caCert`; and it refuses a PEM that carries private key material. ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added a Helm helper to generate CA-only Kubernetes TLS trust-anchor secrets, including input validation (rejects private key material, requires a properly formatted certificate, and ensures required metadata). * **Tests** * Introduced a dedicated test suite covering successful rendering, metadata and content assertions, tenant label enforcement, label/annotation merging, and detailed failure cases for invalid inputs and incorrect arguments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Cozystack
Cozystack is a free platform and framework for building clouds.
Cozystack is a CNCF Sandbox Level Project that was originally built and sponsored by Ænix.
With Cozystack, you can transform a bunch of servers into an intelligent system with a simple REST API for spawning Kubernetes clusters, Database-as-a-Service, virtual machines, load balancers, HTTP caching services, and other services with ease.
Use Cozystack to build your own cloud or provide a cost-effective development environment.
Use-Cases
-
Using Cozystack to build a public cloud
You can use Cozystack as a backend for a public cloud -
Using Cozystack to build a private cloud
You can use Cozystack as a platform to build a private cloud powered by Infrastructure-as-Code approach -
Using Cozystack as a Kubernetes distribution
You can use Cozystack as a Kubernetes distribution for Bare Metal
Documentation
The documentation is located on the cozystack.io website.
Read the Getting Started section for a quick start.
If you encounter any difficulties, start with the troubleshooting guide and work your way through the process that we've outlined.
Versioning
Versioning adheres to the Semantic Versioning principles.
A full list of the available releases is available in the GitHub repository's Release section.
Contributions
Contributions are highly appreciated and very welcomed!
In case of bugs, please check if the issue has already been opened by checking the GitHub Issues section. If it isn't, you can open a new one. A detailed report will help us replicate it, assess it, and work on a fix.
You can express your intention to on the fix on your own. Commits are used to generate the changelog, and their author will be referenced in it.
If you have Feature Requests please use the Discussion's Feature Request section.
Community
You are welcome to join our Telegram group and come to our weekly community meetings. Add them to your Google Calendar or iCal for convenience.
License
Cozystack is licensed under Apache 2.0.
The code is provided as-is with no warranties.
Commercial Support
A list of companies providing commercial support for this project can be found on official site.
