mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-10 06:24:09 +09:00
3d2dd4271a2ce5567a37494f3d127a52f74b5708
2043
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b43f00d2bb |
feat(mariadb): publish a key-free CA projection to the tenant
The tenant needs the CA certificate to verify TLS connections, but the cert-manager Secrets that back the chain are key-bearing: <name>-ca-tls holds the CA private key and <name>-tls holds the server private key. The dashboard Role granted the tenant read access to both whenever TLS was on, which handed out signing material where only a trust anchor was required. Drop both from the tenant grant and mark the CA Secret as the source of a key-free projection instead. The chart owns the cert-manager chain here, so it labels the CA Secret directly, inside the existing TLS gate: with TLS disabled the chain is not rendered and nothing is marked. Only the CA carries the labels, never the leaf. The selector added to the ApplicationDefinition is deliberately specific. An empty selector would compile to a match-everything label selector and project every Secret in the namespace to the tenant. The source Secret and the projection are matched by two different labels on purpose: the source stays key-bearing and internal, while the published copy is key-free. Selecting the source directly from the consumer would defeat the split and leak the CA private key. Note that the labels and the selector are inert until the controller that produces the projection exists: nothing reads the labels yet, and the selector matches no Secret. Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
3a70f91423 |
fix(vm-disk): fail on storageClass/source edit of existing disk (#3969)
## What this PR does Fixes #2985. Editing a `VMDisk`'s `storageClass` or `source` after its DataVolume already exists was silently ignored: `packages/apps/vm-disk/templates/dv.yaml` looks up the existing DataVolume and, when found, reused its `spec` verbatim (because `DataVolume.spec` is immutable in CDI). The edit therefore rendered as a no-op — the typed CR accepted it and the HelmRelease reported success, while the DataVolume/PVC kept the original spec and the source of truth silently diverged from the cluster. The template now builds the desired `source` into a shared `$desiredSource` variable (used both for rendering and comparison) and, when a DataVolume already exists, calls `fail()` with an explicit message if the requested `storageClass` or `source` differs from the immutable existing spec — mirroring the explicit size handling already done in `pvc-resize-hook.yaml`. Storage growth is unaffected and still flows through the resize hook. `source` is also marked `@immutable` in `values.yaml`, so the generated schema now carries the same `x-kubernetes-validations` transition rule (`self == oldSelf`) that `storageClass` already had; `types.go`, `values.schema.json` and the generated `vm-disk-rd` CRD are regenerated to match. ### Verification - `helm unittest` covers both the immutability guard (via a mocked existing DataVolume through `kubernetesProvider`, since `lookup` needs a cluster) and initial-creation source rendering for every source type — 12/12 pass. - Verified live on a dev cluster with `helm install --dry-run=server` against a real DataVolume: an unchanged edit renders, a `storageClass` change fails, a `source` change fails, and a storage grow still renders the resize hook without tripping the guard. ### Scope note This PR only makes the drift visible and marks `source` immutable. Two deeper platform findings sit outside its scope: (1) #2657 (pre-existing) — the `apps.cozystack.io` aggregated apiserver does not evaluate the `x-kubernetes-validations` CEL rules on update, so `@immutable` is a no-op at admission for every Application kind, which is exactly why the chart-level `fail()` — not the CEL rule — is the substantive guard here; (2) #3971 — that same apiserver does not honour `--dry-run=server` and persists the change (surfaced while testing this fix). ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff file by file. The diff touches only `packages/apps/vm-disk/` (template, `values.yaml`, regenerated `values.schema.json`, tests), the regenerated `packages/system/vm-disk-rd/cozyrds/vm-disk.yaml`, and `api/apps/v1alpha1/vmdisk/types.go`. No package was added, renamed or removed; no `values.schema.json` field was added, renamed or removed (only a validation rule on the existing `source` field); no default changed; `README.md` is unchanged; the `cozyrds` file was regenerated, not renamed. The terraform provider models fields, defaults and enums but not CEL immutability, so it is unaffected. No downstream repository is reached by this change. - [x] 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: ### Release note ```release-note fix(vm-disk): editing storageClass or source on an existing VMDisk now fails with a clear message instead of being silently ignored; delete and recreate the disk to change these immutable fields. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for blank, HTTP, image, disk, and upload VM disk sources. * VM disk sources and storage classes are now protected from unintended changes, while storage expansion remains supported. * Existing disks preserve their configured source and storage settings. * **Bug Fixes** * Invalid or conflicting source configurations are rejected. * Prevented silent no-op updates and incorrect immutability errors for existing disks. * **Documentation** * Documented source and storage class immutability, including the need to recreate disks when changing either setting. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
86fe8e5a7e |
feat(valkey): add managed Valkey service (#3380)
## What this PR does Adds a **managed Valkey** service to the catalog, alongside managed Redis. > **Stacked on #3406** (redis-operator migration to freshworks-oss). This PR targets that branch and depends on it; review/merge #3406 first. ### Why Redis relicensed at 8.x to a source-available family (AGPLv3 / SSPL / RSALv2), and the managed Redis app currently ships `v8: 8.4.0` with no BSD-licensed option ≤ 7.2. That is a licensing risk for CNCF Incubation. [Valkey](https://valkey.io) is the BSD-3-Clause fork of Redis 7.2.4, governed by the Linux Foundation, and a drop-in replacement for Redis. This is the same move Argo CD made — [`cncf/foundation#750`](https://github.com/cncf/foundation/issues/750) was closed with "no need for exception" after they migrated to Valkey. ### How it works Valkey runs on the same operator as managed Redis. #3406 migrates that operator from the archived `spotahome/redis-operator` to the actively-maintained **freshworks-oss/redis-operator** fork, which adds a native `spec.engine: Valkey` switch. With it, the operator runs `valkey-server` / `valkey-cli` natively (in the launch command and in its liveness / readiness / shutdown probes), so the chart uses the official `valkey/valkey` image directly — **no Redis-compatibility shim image**. The app mirrors managed Redis: same shape/values (`replicas`, `resources`/`resourcesPreset`, `size`, `storageClass`, `external`, `version`, `authEnabled`), Sentinel-based HA, metrics exporter, `WorkloadMonitor`s, dashboard resource map, and `ApplicationDefinition` (`kind: Valkey`). Versions offered: `v8 → 8.1.8` (default) and `v7 → 7.2.13` (the BSD-3-Clause Redis 7.2 line, most conservative drop-in). ### How it was verified - `helm template` renders cleanly for `v8`, `v7`, and `external: true`; the `RedisFailover` carries `spec.engine: Valkey` and `image: valkey/valkey:<version>`. `helm lint` shows only the pre-existing icon-URL / `cozy-lib` warnings that managed Redis emits too. - `helm template` of `valkey-rd` renders a valid `ApplicationDefinition` (`kind: Valkey`, `plural: valkeys`, `category: PaaS`). - Generated artifacts (`values.schema.json`, `README.md`, `types.go`, `zz_generated.deepcopy.go`, the ApplicationDefinition `openAPISchema`) reproduce with the pinned `cozyvalues-gen` v1.6.0 and `controller-gen` v0.16.4 — root `make generate` and per-app generate both produce no drift; `go build ./valkey/...` passes. - A live-cluster e2e was not run from this environment; the Chainsaw suite `hack/e2e-chainsaw/valkey/` (mirroring the redis suite) is included and `hack/select-e2e.sh` maps the `valkey-application` source to it, so it runs in CI once the freshworks operator image is built. ### Notes - The Valkey logo (`packages/apps/valkey/logos/valkey.svg`) is a placeholder in Valkey's brand colors, pending the official brand asset. - A Grafana `db/valkey` dashboard was intentionally left out of this initial PoC; it can follow. ### Screenshots N/A — no UI changes. ### Downstream repositories This adds a new managed-app kind, which likely warrants follow-ups in `cozystack/website` (user-facing docs for the new service) and possibly `cozystack/terraform-provider-cozystack`. No follow-up PRs are opened yet — leaving these for a maintainer to decide, per the template guidance. - [ ] 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: ### Release note ```release-note feat(valkey): add managed Valkey service — a BSD-3-Clause, Linux Foundation-governed alternative to managed Redis, running on the freshworks-oss redis-operator via native spec.engine: Valkey (versions 8.1.8 and 7.2.13) ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added managed Valkey service support with high availability and configurable replicas. * Supports Valkey versions 7 and 8, persistent storage, resource presets, authentication, and optional external access. * Added monitoring metrics and dashboard integration. * Added platform package integration and tenant administrator access. * **Documentation** * Added Valkey configuration guidance and storage-class immutability documentation. * **Tests** * Added end-to-end coverage for deployment readiness, storage, and replicas. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
127cc2ce9a |
fix(ingress-nginx): re-pin to the segfault-free v1.11.5 rebuild
The v1.11.5 tag in cozystack/ingress-nginx-with-protobuf-exporter now points at the merge of its #7, which builds lua-protobuf with -fno-strict-aliasing. The digest pinned here was the build before that, whose pb.so makes nginx's cache manager and cache loader children segfault continuously: tens per second, indefinitely. The workers are unaffected, so the pod stays Ready with no restarts and nothing surfaces until the master itself segfaults, which is how this reached main unnoticed. Verified against the published image rather than a local build. The one-line repro, luajit -e 'require "protoc"', exits 0 where it exited 139, and a 25 second nginx run on a generated ingress-nginx config produces no signal 11 where the pinned build produced 105. The exporter is re-pinned alongside it because one tag builds both images, so the rebuild published a new exporter digest too. Its source did not change. release-1.5 and release-1.6 pin v1.11.2 and were never affected. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> |
||
|
|
dc2e427262 |
docs(etcd): note that affinity is not applied to existing members (#3964)
## What this PR does Documentation follow-up to #3962. Both reviewers (CodeRabbit and @androndo/@IvanHunters) noted that the new `affinity` value is not applied retroactively: the etcd-operator sets `spec.affinity` only when it creates or replaces a member Pod and never reschedules running members. On a cluster that already has co-located members (the exact failure #3962 targets) the default soft rule — or a newly set hard rule — takes effect only as each member is replaced. This adds that caveat to the `affinity` parameter documentation in `values.yaml` and regenerates `values.schema.json`, `README.md`, and the embedded `openAPISchema` in `etcd-rd`. No template or behaviour change; `make test` passes (20/20). The other inline note (the soft podAntiAffinity default overlaps the soft `topologySpreadConstraints`) was explicitly flagged as "fine to keep as-is", so no code change is made for it. > Note: this PR is stacked on `fix/etcd-soft-antiaffinity` (#3962) and will retarget to `main` once that merges. ### Screenshots n/a — no UI changes. ### Downstream repositories Doc-only change to an app chart (regenerated artifacts). No package added/renamed/removed, `kind`/`plural` unchanged, no installer values/assets/variants touched. No downstream follow-up required. - [x] No downstream repository is affected by this change ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified that etcd affinity rules apply when member Pods are created or replaced. * Noted that running members are not rescheduled retroactively; the rule takes effect as each member is replaced. * Updated the guidance consistently across configuration documentation and schema descriptions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
ec0785fe2a |
fix(redis-operator): use REDISCLI_AUTH for valkey-cli auth in generated scripts
The freshworks-oss operator authenticates the CLI in its generated readiness and shutdown scripts by exporting the engine's auth env var before invoking it. For the Valkey engine it used VALKEYCLI_AUTH, but valkey-cli honours only REDISCLI_AUTH (its --help documents that name and no VALKEYCLI_AUTH). With auth enabled the readiness probe's valkey-cli call therefore returned NOAUTH, the role never resolved, and the data pods never became Ready. Patch valkeyEngine.CLIAuthEnvName to REDISCLI_AUTH so auth-enabled Valkey instances pass readiness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Andrey Kolkov <androndo@gmail.com> |
||
|
|
c94a1da5bb |
feat(mongodb): add TLS support via PSMDB operator-managed certificates (#2692)
## What this PR does Enables operator-managed TLS for MongoDB and stops handing tenants any Secret that carries private key material. - Percona Server for MongoDB serves TLS unconditionally (`mode: preferTLS`), so this PR drops the misleading `tls.enabled` toggle rather than pretending the chart controls it. - The tenant no longer receives the operator's CA Secret (`<release>-ca-cert`, which holds `ca.key`) or the leaf (`<release>-ssl`). Both were previously reachable, and either one is a private key in tenant hands, the CA one is a full trust-chain compromise. - The chart declares a `TenantProjection` sentinel naming its CA source Secret (`<release>-ssl`, the operator-managed leaf, which carries `ca.crt`), and the ResourceDefinition selects the resulting key-free `<release>.tenant-ca` projection by the engine-agnostic `internal.cozystack.io/tenant-ca` label. The CA-extraction controller reads the sentinel and publishes the projection holding only `ca.crt`. The tenant gets what it needs to verify the server and nothing else. ### Why the include also carries an exclude list The trust-anchor selector matches by label with no name constraint, so any Secret in the namespace that acquired that label would be promoted with its data copied verbatim. The enumerable dangerous set is therefore excluded explicitly: the CA, both leaves, the rotation snapshots (`-ssl-old`, `-ssl-internal-old`, same server keys as the leaves, and unlike them they carry no owner reference, so an interrupted rotation can leave them behind after the release is gone), the system-user passwords, the at-rest encryption key, the keyfile, the object-storage credentials, and both forms of the operator's internal Secret. That backstop is bounded, and the comment says so: matching is by exact name with no globs, so a Secret a future operator version introduces, or a transient object an issuer creates mid-renewal, stays in scope of the label-only include. The per-user Secrets cannot be enumerated at all, since their names carry the username. The primary defence remains that the controller stamps only its own projection; this closes the enumerable set, not the whole surface. ### Release note ```release-note feat(mongodb): stop exposing TLS private key material to tenants and publish the CA as a key-free trust anchor ``` |
||
|
|
6fdb5e0840 |
docs(etcd): note that affinity is not applied to existing members
The operator applies spec.affinity only when it creates or replaces a member Pod; it does not reschedule running members. Document that on the `affinity` value so operators do not expect the default (or a newly set rule) to redistribute already co-located members until each is replaced. Addresses the non-blocking review notes on #3962. Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
edd767ea0f |
feat(nats): publish ca.crt-only trust anchor to tenants (#3340)
## What this PR does Converges nats onto the key-free CA trust-anchor contract. A TLS-enabled nats release renders a `TenantProjection` sentinel naming the chart's cert-manager CA Secret as its source, and the nats ResourceDefinition selects the resulting `<release>.tenant-ca` object by label. The projection is built by the CA extraction controller in `internal/controller/cacert`, which parses the source certificate and re-encodes it into a single `ca.crt` key, so a tenant that enables TLS gets something to verify the server against without ever getting a private key. No key-bearing Secret is granted to a tenant, by name or by label. `packages/apps/nats/templates/certmanager.yaml` is not touched. An earlier draft of this change stamped publish labels onto the CA `Certificate`'s `secretTemplate`; the controller that shipped consumes a sentinel object instead, so the chart now declares its source rather than marking it. The functional change is one new template and one `matchLabels` entry in the ResourceDefinition. The sentinel is gated on the chart's effective TLS state, which is tri-state: left unset it follows `external`. That gate is the single divergence from the postgres implementation of the same contract, and it exists because the nats CA is only issued while TLS resolves true. An ungated sentinel would sit `Ready=False/SourceNotFound` forever on every plaintext release. The CA extraction controller is already on main, so this is live on merge rather than dormant until a follow-up lands. The `do-not-merge/hold` label predates that and no longer guards anything. ## Verified against a running cluster The premise this rests on is that cert-manager populates `ca.crt` on the Secret of a `selfSigned`, `isCA: true` Certificate. Nothing in this repository witnesses it: `hack/e2e-chainsaw/` has no nats suite, and nats appears in no e2e workflow, so every unit test here encodes the same assumption the chart encodes and the two cannot catch each other being wrong. I closed it against a live cluster instead, running cert-manager v1.20.2, the version this repo ships, on two independent nats releases: - `nats-<name>-ca`, the chart's CA Secret, holds `ca.crt`, `tls.crt` and `tls.key`. The declared source key exists. - `nats-<name>.tenant-ca`, the projection, holds exactly one key, `ca.crt`. - The projection's SHA-256 certificate fingerprint equals the source's, so it carries that CA and not some other certificate. - The projection carries `internal.cozystack.io/tenant-ca: "true"` and `internal.cozystack.io/tenantresource: "true"`. The second is stamped by the lineage webhook, computed from the ResourceDefinition selector this PR adds, so its presence means the whole chain held. - `kubectl get tenantsecret nats-<name>-ca` returns NotFound. The key-bearing Secret is not served through the tenant API. What is still missing is an automated regression witness for that premise. Adding one means a new chainsaw suite for nats, which has no e2e coverage of any kind today, and that is a larger change than this one. ## What the tests pin 61 helm-unittest cases and 3 bats cases. The sentinel's absence of `ownerReferences` (an owner would short-circuit the lineage walk before it reaches the Flux label the attribution depends on), the length of `spec.projections` as well as its single entry (the controller refuses a multi-entry sentinel, so a second entry is a silent loss of the anchor rather than a leak), both implied legs of the tri-state TLS gate, and `notExists` on either visibility label for both key-bearing Certificates. The bats guard covers the two grant surfaces nothing else asserts on. The ResourceDefinition is rendered, by the `nats-rd` chart inlining `cozyrds/*`, but that chart has neither a tests directory nor the `test` target `hack/helm-unit-tests.sh` looks for, and no `packages/system/*-rd` package does. The dashboard Role is rendered by a chart that has both, and no suite under `packages/apps/nats/tests/` asserts on it. Both are compared whole rather than filtered down to the entries that mention a Secret, because every such filter admits the spelling it did not anticipate. A name whitelist passes an added label selector, and `controller.cert-manager.io/fao` is stamped on both key-bearing Secrets, so a selector on it is a one-line grant of the CA private key. On the Role side, a rule with no `resourceNames` names no Secret, a rule with `resources: ["*"]` names no resource, and a ClusterRole is not a Role, while all three grant read access to the CA key. Comparing the rendered output whole needs no filter and leaves no spelling to find. ## Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff, file by file. No trigger fires, and no follow-up PR is owed. `packages/apps/nats/templates/tenant-projection.yaml` is a new template and `packages/apps/nats/tests/` holds chart tests. No package is added, renamed or removed; `values.yaml` and `values.schema.json` are untouched, so neither the hand-written Terraform schema, its version enums, nor its restated defaults have anything to track. `kind`, `plural` and `release.prefix` are unchanged, and no output Secret or Service is renamed, so the provider's concatenated name lookups still resolve. `packages/system/nats-rd/cozyrds/nats.yaml` is edited in place rather than renamed, so the ccp external-app skill's dependency-contract anchor still resolves. The provider offers no typed access to the new projection, which is the position it already holds for every Secret it does not model, and not a break. `packages/apps/nats/README.md` is covered by the one automated path that does exist: `nats` is present in the website `Makefile` `APPS` list, so the docs bot regenerates the managed-apps reference page from this `README.md` on the next stable tag. This is the silent-skip failure mode the trigger map warns about, so I checked the list rather than assuming; an app missing from it gets no page at all, which is what happened to `opensearch`. `hack/check-nats-rd-secrets.bats` is a new file, not a move or a rename, and it changes no make target's contract: `Makefile` globs `hack/*.bats` into `BATS_UNIT_FILES` automatically. The ccp skills anchor on `hack/package.mk` and `hack/common-envs.mk`, neither of which is touched. - [x] 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: Part of #2814. ### Release note ```release-note feat(nats): expose the CA certificate to tenants as a key-free `<release>.tenant-ca` Secret ``` |
||
|
|
65e700486b |
feat(qdrant): publish ca.crt-only trust anchor to tenants (#3341)
## What this PR does Converges qdrant onto the key-free CA trust-anchor contract (#2814), the same way #3340 does it for nats. The chart declares a `TenantProjection` sentinel naming its cert-manager CA Secret. The CA-extraction controller reads that declaration and publishes a key-free copy, `ca.crt` and nothing else, as `<release>.tenant-ca`. The qdrant ResourceDefinition selects that projection by label so it shows up in the tenant's secret list. No key-bearing Secret gains a tenant-visible label. The sentinel is gated on the chart's TLS condition. Engines whose operator mints a CA unconditionally do not need that, but qdrant only has a CA while TLS is on, and an ungated sentinel would sit `Ready=False`/`SourceNotFound` on every plaintext release. This was held while the CA-extraction controller was still open. That controller landed in #3407, so the sentinel is honoured as soon as this merges and the hold is gone. Tests pin the invariants that otherwise fail silently. The sentinel carries no `ownerReferences`, so the lineage walk falls through to the Flux release label. Neither key-bearing Certificate stamps a tenant-visible label on its Secret. The two objects that decide which Secrets a tenant may actually read, the dashboard Role with its binding and the ResourceDefinition, are pinned whole rather than checked for known-bad names: a rule granting secrets with no `resourceNames` is unrestricted rather than narrow, and a label selector can reach a key-bearing Secret while every name in the file stays correct, so neither adds a name for a probe to notice. Part of #2814. ### Follow-ups What this PR does not do, and where each belongs instead. - nats converges the same contract in #3340 and has no `dashboard-resourcemap_test.yaml`, so the dashboard Role pin belongs there too. Tracked in #3701 along with postgres, which already carries the label selector with no guard behind it. - The qdrant e2e fixture runs plaintext (`hack/e2e-chainsaw/qdrant/qdrant.yaml` sets `external: false`), so nothing in CI walks the path from the sentinel to `tenantsecrets`. postgres has a `verify-tenant-ca-projection` step to copy. - `hack/check-qdrant-rd-secrets.bats` is engine-agnostic apart from two literals, the chart path and the expected Secret set. The third copy of it is the point to parameterise over `packages/system/*-rd/cozyrds/` instead of copying again. ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff, file by file. Nothing needs a manual follow-up, but not for the reason a values-only walk would give: the website reference page for an app is regenerated from that package's `README.md`, and this PR does change it. `qdrant` is in the app list hardcoded in the website `Makefile`, so the docs bot picks the new section up on the next stable tag and there is nothing to open by hand. The Terraform provider's schema is hand-written against `values.schema.json`, which is untouched, and the provider offers no typed access to the new projection, the same position it holds for every other Secret it does not model. No package is added, renamed or removed, and the `cozyrds` file is edited in place rather than renamed, so the ccp dependency-contract anchor still resolves. User-facing documentation for consuming the trust anchor ships with the chart README, matching #3340. - [x] 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: ### Release note ```release-note feat(qdrant): expose the CA certificate to tenants as a key-free `<release>.tenant-ca` Secret ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Tenant environments can securely access the Qdrant TLS CA certificate without receiving the private key. * TLS CA access is automatically enabled when TLS or external access is configured. * **Documentation** * Expanded TLS guidance explains certificate validation and provides commands for retrieving the tenant CA certificate. * **Tests** * Added coverage for TLS projections, secret visibility, dashboard resources, and secure fail-closed secret exposure. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
29d2955241 |
fix(mongodb): declare the trust anchor with a TenantProjection
A tenant verifying a MongoDB endpoint needs the CA certificate, and every Secret that holds one also holds a private key: the operator's <release>-ca-cert carries the CA key, the leaf <release>-ssl the server key. Granting either would hand over the key along with the anchor. Render a TenantProjection naming the source, so the CA-extraction controller can publish the key-free <release>.tenant-ca, and select that projection by label from the managed-service definition. The sentinel carries no ownerReferences: it is resolved back to its release through the label Flux stamps, and an owner reference short-circuits that walk and silently costs the tenant its anchor. The sentinel is ungated. The operator issues the certificate chain whatever the chart asks for, so the source exists for every release and there is no plaintext leg to skip. The source is <release>-ssl rather than <release>-ca-cert. It is the name this chart itself sets in spec.secrets.ssl, so the reference cannot drift from the CR; the operator writes it on both its cert-manager path and the self-signed fallback it uses when cert-manager is absent, where no <release>-ca-cert exists at all; and it holds the merged old and new CA through a rotation, where <release>-ca-cert holds only the new one and would stop verifying pods still serving the previous leaf. Withhold the key-bearing Secrets from the tenant explicitly as well. The label selector matches on no name, so a single mis-stamp would promote a private key; the exclude list is evaluated first and closes the enumerable set. Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
27f71bfbe9 |
fix(mongodb): deliver the TLS trust anchor without private key material
The tenant-facing paths handed over both Secrets the PSMDB operator creates for TLS. <release>-ca-cert holds the CA private key, so read access is a full trust-chain compromise; <release>-ssl holds the server private key, so read access allows impersonation. A client verifying the endpoint needs ca.crt and nothing else, so neither object belongs on a tenant-facing path. Declare the trust anchor by name instead. The operator creates its CA Secret itself and offers no way to label it, so the ApplicationDefinition names it and the CA-extraction controller lifts ca.crt alone into the key-free projection. The include selector matches that projection by label, and the dashboard Role drops both key-bearing names without gaining the projection by name: a name grant conveys whatever occupies the name, while the label path conveys only what the platform vouched for. Witness both delivery paths against a running cluster, since the two that matter are separate. The Role is asserted as an exact grant with bounded rule counts, because a rule with no resourceNames — or one granting every resource — would otherwise pass an absence check while conveying every Secret in the namespace. The label path is asserted as a negative on all three key-bearing Secrets. A bats check pins the declaration's names and keys, which nothing else covers: the field is pruned at admission until the controller ships, so drift there would surface as an empty trust anchor with nothing red. Drop the mongodb-rd unit test. That package has no test target, so the file never ran, and what it asserted was the leak. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
7f8f32b1ba |
fix(mongodb): always enable operator-managed TLS, drop tls.enabled field
Address review feedback from lexfrei (B3): the tri-state tls.enabled field could not actually disable TLS. The PSMDB operator defaults spec.tls to preferTLS whenever it is unset, so tls.enabled: false rendered no spec.tls while the operator still served TLS — a misleading no-op. Remove the field and unconditionally render spec.secrets.ssl and spec.tls.mode: preferTLS to match operator behaviour. splitHorizons SAN injection now keys off external alone. Also drop the Makefile jq patch that re-injected the tls schema property. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Arsolitt <arsolitt@gmail.com> |
||
|
|
e9f276195f |
test(mongodb): cover TLS modes, splitHorizons, and RBAC
Signed-off-by: Arsolitt <arsolitt@gmail.com> Assisted-By: Claude <noreply@anthropic.com> |
||
|
|
f6816e1f44 |
feat(mongodb): add operator-managed TLS with external hostname SANs
Signed-off-by: Arsolitt <arsolitt@gmail.com> Assisted-By: Claude <noreply@anthropic.com> |
||
|
|
4e6313b608 |
feat(valkey): register Valkey application in the platform
Wire the managed Valkey app into the platform: add the valkey-rd package carrying the Valkey ApplicationDefinition, the valkey-application PackageSource (reusing the existing redis-operator), enable it in the paas bundle, and grant tenant roles access to the valkeys resource. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
06624bacd0 |
fix(vm-disk): fail on storageClass/source edit of existing disk
Editing a VMDisk's storageClass or source after its DataVolume already
exists was silently ignored: dv.yaml reused the existing DataVolume.spec
verbatim (DataVolume.spec is immutable in CDI), so the edit rendered as a
no-op while the HelmRelease reported success and the CR diverged from the
cluster.
When a DataVolume already exists, fail with a clear message if the
requested storageClass or source differs from what the disk was created
with. Both compare the operator's input recorded in an annotation at
creation (vm-disk.cozystack.io/storage-class and .../source), written only
at creation and preserved verbatim afterwards — never overwritten with an
unapplied edit. Recording the input rather than reading the live object
matters on both axes: the image-clone PVC name carries a chart-version
prefix (vm-image- was renamed to vm-default-images-, commit
|
||
|
|
6d9444056d |
chore(keycloak): bump KMS proxy to 0.2.2
Pin the released image by digest so package rendering is reproducible. Assisted-By: GPT-5 <noreply@openai.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> |
||
|
|
b9a95a0c4a |
fix(postgres): let in-place restore re-bootstrap instead of wedging (#3959)
## What this PR does An in-place `RestoreJob` (one whose `targetApplicationRef` is omitted, so the app is restored back into itself) re-bootstraps the Postgres app under its own name. On stock v1.6.2 it never comes back up — the cluster is deleted and then hangs in `Setting up primary` with `*-full-recovery-*` pods crash-looping, or the HelmRelease upgrade fails outright. There are two independent causes, both in the same restore patch, and both surface only in place because a to-copy restore targets a different app. **1. Archive-vs-recovery `serverName` collision.** The driver set the restored cluster's WAL-archive `serverName` equal to the recovery source (`bootstrap.serverName = bootstrap.oldName = sourceServerName`), while the chart pinned the archiver `serverName` to the release name. In place those coincide, so the cluster archived onto the source's own WAL prefix and `barman-cloud-check-wal-archive` refused to start with `Expected empty archive`. Fixed by giving the restored cluster a fresh `bootstrap.newServerName`, keyed by the `RestoreJob` UID (unique per restore, stable across reconciles); the chart archives there while `externalClusters` still recovers from `bootstrap.serverName`. This is what manual `CNPG` strategies hit (their `serverName` defaults to the cluster name, including the shipped `examples/backups/postgres/10-cnpg-strategy.yaml`); the `cozy-default` flow dodged it because its `serverName` is `<namespace>-<app>`. **2. `useSystemBucket` guard.** The restore populates explicit S3 coordinates (`destinationPath` / `s3CredentialsSecret` / `endpointCA`) but left `backup.useSystemBucket=true`, so the chart's `bootstrap.enabled is incompatible with useSystemBucket` guard failed the HelmRelease upgrade and the cluster never re-rendered. This is what the default `cozy-default` (system-bucket) flow hits on an in-place restore. Fixed by clearing `useSystemBucket` in the restore patch, since the app is now on the explicit-creds path. Verified live on a v1.6.2 cluster: with `useSystemBucket` cleared, the same in-place restore re-bootstraps to a healthy cluster and the sentinel row survives. Both are exercised only by an in-place restore; the postgres backup e2e drove only to-copy, so neither surfaced in CI. A step 50 in-place restore now guards cause 1 (manual-strategy flow); cause 2 is covered by a Go unit assertion (a system-bucket in-place e2e needs the platform bucket wired in CI and is left as a follow-up). This is a bug in released v1.6.2 (chart and driver are byte-identical on `main`), so it is a backport candidate. ### Screenshots Not applicable (no UI changes). ### Downstream repositories The only externally visible schema surface is one new field, `spec.bootstrap.newServerName`, in the postgres `values.schema.json` / CRD; it is set by the CNPG backup driver on restore and never authored by tenants, exactly like the existing `bootstrap.serverName` / `bootstrap.oldName`. (The `useSystemBucket` clearing is internal to the Go driver — `backup.useSystemBucket` already exists in the schema.) The chart `README.md` is regenerated from `values.yaml` automatically by the website docs bot on release, and `terraform-provider-cozystack` models user-authored spec (its guard tracks top-level fields, not driver-set nested ones), so neither needs a change in practice. Leaving every box unticked per the trigger-map guidance so a maintainer confirms rather than acting on a speculative follow-up. - [ ] 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: ### Release note ```release-note fix(postgres): in-place restore no longer wedges — the restored cluster archives WAL under a fresh serverName distinct from the recovery source, and a system-bucket app is switched to explicit credentials so the bootstrap/useSystemBucket guard no longer fails its HelmRelease. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added support for specifying a distinct WAL archive destination when restoring PostgreSQL clusters. - In-place restores now automatically use a fresh archive location while continuing to recover from the original source. - Added configuration and schema support for the optional `bootstrap.newServerName` setting. - **Bug Fixes** - Prevented archive conflicts that could cause in-place restores to fail. - **Documentation** - Documented the new restore archive configuration and behavior. - **Tests** - Added coverage for unique restore destinations and in-place restore validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c8e4d97df9 |
feat(ingress-nginx): bump to 1.11.5 and restrict the admission webhook (#3798)
## What this PR does Bumps ingress-nginx to chart 4.11.5 / controller v1.11.5, and closes the admission webhook to everything except the API server. ### Version bump The shipped `v1.11.2` controller is a cozystack rebuild that **already carries a backport** of the CVE-2025-1974 fix (v0.28.2, March 2025), so this is not a fix for a live vulnerability. The problem is that it reports itself as a plain `v1.11.2 build git-d3bb2b4`: the binary is built from the upstream 1.11.2 tag with the patch applied on top, which does not change the version ldflags, and the image labels are inherited from the `registry.k8s.io` base layer. A patched build is therefore indistinguishable from an unpatched one by anything except its digest, and anything scanning by version string flags it. This bump removes that ambiguity. `packages/system/ingress-nginx` feeds three consumers — the platform ingress, tenant Ingress applications (`extra/ingress`), and the ingress addon of tenant Kubernetes clusters — so one bump covers all three. The protobuf-exporter sidecar moves in lockstep, since its tag tracks the upstream controller version. `CHART_VERSION` is now pinned in the package Makefile. `helm pull` carried no `--version`, so `make update` would have fetched whatever is latest upstream (4.13.9 today) against a controller image that only exists for selected versions. ### Admission webhook policy The webhook is only ever called by the API server, but it was reachable from every pod in the cluster, in any namespace — avoidable attack surface independent of any specific CVE. The new `CiliumNetworkPolicy` is deny-only and disables default-deny. A Cilium rule carrying an ingress section otherwise puts the selected endpoints behind a default-deny, which here would mean enumerating every port the controller serves (80, 443, metrics, the exporter, tcp/udp services) and breaking traffic on the first one missed. Subtracting one port is the safer construction. Two callers are deliberately not denied: * **host-network sources**, which are not Cilium endpoints — this covers a management cluster, where the API server runs on the host network; * **the konnectivity agent**, which is not optional. On a Kamaji-hosted tenant cluster the API server lives outside the cluster and reaches in-cluster webhooks through the konnectivity tunnel (Kamaji defines only the `cluster` egress selection), so the call arrives from the agent pod in `kube-system`, not from the API server address. A `fromEntities: [kube-apiserver]` allow would not match it, and every Ingress create/update in every tenant cluster would start failing. The policy is namespaced rather than clusterwide on purpose: the same package is installed into several namespaces of one cluster, so a cluster-scoped object with a fixed name would collide between releases. It can be disabled with `admissionWebhookPolicy.enabled`. ### Verification Tested on a live Kamaji tenant cluster (k8s 1.35.6, Cilium 1.19), with the addon enabled for the test and disabled afterwards: | | probe from a pod in `default` | Ingress create | |---|---|---| | without policy | `open` | works | | with policy | `Connection timed out` | works | Also confirmed on the management cluster that `kube-apiserver` runs as static pods on the host network, so the host-network carve-out holds there. The package previously ran no unit tests at all; a `test` target is added with 7 assertions covering the policy. It is scoped with `--with-subchart=false` because the vendored chart carries upstream's own suites, which assert upstream defaults and fail under our values. The full `hack/helm-unit-tests.sh` sweep is green. The controller and exporter images are rebuilt by cozystack/ingress-nginx-with-protobuf-exporter#5, which vendors the `11843.diff` patch instead of fetching it from a still-open upstream PR at build time. ### Screenshots Not applicable — no UI changes. ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff, which touches `packages/system/ingress-nginx/` and one comment line in `hack/promote-retag.sh`: * **website** — no package added, renamed or removed under `apps/`/`extra/`; no platform component added or removed; no change to `packages/core/platform/values.yaml`, variants, bundles or release assets. The `package Makefiles` trigger was considered because of `CHART_VERSION` and the new `test` target: `CHART_VERSION` does not change the documented contract of `make update`, and `test: helm unittest` is an existing pattern in the repo (`packages/system/cilium-networkpolicy/Makefile`), not new tooling. * **terraform-provider-cozystack** — every trigger is about `packages/apps/`, `values.schema.json`, version enums or `ApplicationDefinition`; none apply to a system package. * **ansible-cozystack** — no change to installer values, platform keys the role sets, variants, waited-on objects or node prerequisites. * **ccp** — `hack/package.mk` and `hack/common-envs.mk` untouched; nothing under `hack/` moved or renamed, and the edit to `hack/promote-retag.sh` is a comment with no behaviour change; layout and namespaces unchanged. * **external-apps-example**, **talm**, **cozyhr**, **cozy-proxy**, **cozystack-telemetry-server**, **examples** — no trigger matches. - [x] 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: ### Release note ```release-note feat(ingress-nginx): update to chart 4.11.5 with controller v1.11.5, and restrict the admission webhook so it is reachable only by the API server. The previously shipped v1.11.2 image already contained a backport of the CVE-2025-1974 fix; it reported itself as a stock v1.11.2 build, which this update makes unambiguous. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added optional network protection for the admission webhook with restricted access rules. * Added PrometheusRule support and improved ServiceMonitor configuration. * Webhook service ports are now configurable. * **Updates** * Upgraded ingress-nginx and controller images to version 4.11.5. * Improved default-backend availability and topology-spread settings. * Added Pod Security Admission guidance and refreshed configuration documentation. * **Bug Fixes** * Corrected webhook service rendering and default-backend disruption budget behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
02c6942adc |
fix(kubeovn-webhook): restrict the webhook port to the API server (#3955)
## What this PR does Closes the exposure behind GHSA-g883-q79m-8225 by constraining who can reach the kube-ovn webhook's serving port. The `/mutate-pods` handler returns namespace-derived annotations to any caller that reaches it, and until now nothing stopped an arbitrary in-cluster pod from being that caller. ### Why the client-certificate path does not close it cozystack/cozystack#2698 added `--client-ca-file`, deliberately off by default, as observation mode. Turning it on requires the API server to **present** a certificate, which it does only when its `AdmissionConfiguration` names a `kubeConfigFile` for the webhook plugins. Checked against a running Cozystack control plane rather than assumed: the `AdmissionConfiguration` there carries a `PodSecurity` plugin block and nothing else, so no client certificate is offered to admission webhooks at all. Flipping to `RequireAndVerifyClientCert` on such a cluster would reject the API server itself, and with `failurePolicy: Fail` that stops pod creation cluster-wide. This matters for how the remaining work was scoped: the plan had been to observe issuer logs for a day and then pin a CA. No amount of observation changes the outcome, because the absence is a property of the control plane's configuration, not of the traffic. Making `--client-ca-file` usable is a separate, larger change to how the platform configures the API server, and it is not a prerequisite for closing the exposure. ### What the policy does Denies pod traffic and world traffic to the serving port, and needs no carve-out for the legitimate caller: the API server runs on the host network, so it is not a Cilium endpoint and `fromEndpoints` never matches it. Unlike the ingress-nginx admission webhook this component exists only on the management cluster, so there is no konnectivity-tunnelled caller to exempt either — and consequently no forgeable pod label anywhere in the selector. It is a `CiliumNetworkPolicy` rather than a core `NetworkPolicy`, and that distinction is the reason it works at all: `packages/system/kubeovn/values.yaml` sets `ENABLE_NP: false`, so kube-ovn programs no ACLs and a core NetworkPolicy would be created and never enforced. The values comment that claimed NetworkPolicy was simply not an option is corrected — it was true only of the core kind. `webhookPolicy.enabled` lets a cluster whose networking variant ships no Cilium turn the object off. Deny-only with default-deny explicitly disabled, matching the ingress-nginx admission policy: putting these pods behind a default-deny would mean enumerating every port they serve and breaking traffic on the first one forgotten. ### Verification Eight helm-unittest cases cover the port, the all-namespaces span, the absence of any label-based exemption, the world deny, the disabled default-deny, the Cilium kind, the selector and the off switch. The rendered object was additionally accepted by a live API server via `kubectl apply --dry-run=server`, so the CRD validates it as written. Not verified: that a denied pod actually fails to reach the port at runtime. That needs a live negative test against the policy, which is the same gap the ingress-nginx admission policy carries. ### Screenshots Not applicable — no UI change. ### Downstream repositories Walked the trigger map against the diff. Nothing is reached: the change is one chart template, its values and its tests under `packages/system/`, with no package added or removed under `packages/apps/` or `packages/extra/`, no `packages/core/platform/values.yaml` key, no platform variant or bundle change, nothing under `hack/`, and no node prerequisite. - [x] 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: ### Release note ```release-note fix(kubeovn-webhook): the kube-ovn webhook's serving port is now closed to in-cluster pods and external traffic by a CiliumNetworkPolicy. It previously answered any in-cluster pod with namespace-derived annotations (GHSA-g883-q79m-8225). Disable with webhookPolicy.enabled=false on clusters whose networking variant ships no Cilium. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a Cilium network policy to protect the namespace-annotation webhook. * The policy is enabled by default and restricts access to the webhook port, blocking unauthorized in-cluster and external traffic. * Standardized the webhook port across its deployment, service, and network policy configuration. * **Bug Fixes** * Improved webhook network isolation when standard Kubernetes NetworkPolicy enforcement is unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a7cef35d88 |
fix(kubeovn-webhook): address review on the webhook network policy
Derive the serving port from a single helper so the container port, the
Service targetPort and the policy cannot drift apart. With default-deny
disabled a disagreement failed open: the policy would deny a port nothing
serves while the real one stayed reachable, removing the control without
breaking anything visible. The knob that allowed the drift is gone and a
test asserts the three agree.
Move the rationale into template comments. Helm strips only {{/* */}}, so
the block was being copied verbatim into every rendered manifest. Record
in it what cilium monitor showed on a live kube-ovn cluster: the API
server reaches the webhook from the node InternalIP with identity host or
kube-apiserver, never from the join subnet and never as world.
Drop the egress default-deny toggle, which does nothing without egress
rules, and note that the policy removes in-cluster pods and external
traffic rather than every caller.
Assisted-By: Claude
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
|
||
|
|
9dd2841c74 |
feat(clickhouse): add configurable version parameter (#3476)
## What this PR does The ClickHouse server and Keeper images were hardcoded to `24.9.2.42` in the chart templates, so the chart exposed no top-level `version` values key. The Cozystack API returns a managed database's engine version by passing the HelmRelease's `spec.values.version` straight through into the Application object, so ClickHouse instances carried no `spec.version` in the API — unlike postgres, mariadb, mongodb, opensearch, rabbitmq and redis, which all ship a `version` parameter. This closes the ClickHouse half of the gap tracked in #1246. This adds the same version-selection scaffolding the other managed-database charts already use: - `files/versions.yaml` maps a `major.minor` key to the full image tag, shared by the ClickHouse server and Keeper images (both are pinned to one version). - `templates/_versions.tpl` (`clickhouse.versionMap`) resolves the tag and fails the render on an unsupported version. - `values.yaml` gains a `version` enum (`v25.8`, `v25.3`, `v24.9`), consumed by `templates/clickhouse.yaml` and `templates/chkeeper.yaml`. - `hack/update-versions.sh` + `make update` refresh the map from Docker Hub, keeping only tags published for *both* the server and Keeper images and preserving the current default so a regeneration never bumps the major of existing deployments. The default stays `v24.9` → `24.9.2.42`, so existing installations render byte-for-byte identically and are never upgraded to a new major implicitly. The generated `values.schema.json`, `README.md`, the Go API type and the `clickhouse-rd` `openAPISchema` are regenerated with `cozyvalues-gen` v1.6.0. A new `tests/version_test.yaml` helm-unittest suite covers the server/Keeper mapping for each version and the schema-level rejection of an unsupported value. ### Downstream repositories The diff changes `packages/apps/clickhouse/values.schema.json` by adding a new `version` field with an `enum`. Per the trigger map, this reaches `terraform-provider-cozystack`, whose ClickHouse resource is hand-written and would need a matching `version` attribute with a `stringvalidator.OneOf` list and its expand/flatten pair. There is no existing PR or issue there for it yet. This is left for a maintainer decision rather than a speculative cross-repo PR, so the box below is intentionally left unticked and flagged here. No other downstream repository is reached (the website reference page is regenerated from `README.md` by the release docs bot; ClickHouse is already in its app list). - [ ] 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: needs a `version` attribute on the ClickHouse resource (maintainer decision — see note above) - [ ] [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: ### Release note ```release-note feat(clickhouse): add a configurable `version` parameter (v25.8, v25.3, v24.9; default v24.9) so the deployed ClickHouse version is selectable and returned through the API. Existing installations are unchanged. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a configurable ClickHouse version setting for both server and Keeper images, defaulting to `v24.9`. * Supported versions are `v25.8`, `v25.3`, and `v24.9`, with compatible image patches selected automatically. * **Documentation** * Documented the version setting, default, and supported values. * **Validation** * Unsupported versions are rejected with an error listing allowed options. * **Tests** * Added coverage for version mapping, defaults, invalid values, and update behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
94614c021a |
fix(etcd): spread members across nodes with default soft pod anti-affinity (#3962)
## What this PR does The `etcd` chart hardcoded only a **soft** `topologySpreadConstraints` (`whenUnsatisfiable: ScheduleAnyway`) and never rendered `spec.affinity`, even though the `EtcdCluster` CRD supports it. On small clusters the scheduler therefore co-locates multiple members on one node; losing that node takes out quorum (or the whole cluster). We hit this in production: a single node reboot left several 3-member etcd clusters with 2–3 members on the downed node, and one tenant's control-plane etcd lost quorum until the node came back. This renders `EtcdCluster.spec.affinity` from a new `affinity` value: - **Default (value left empty):** the chart applies a **soft** (preferred) `podAntiAffinity` on `kubernetes.io/hostname`, so members spread across nodes on a best-effort basis. Soft on purpose — a hard rule would leave a member `Pending` when replicas exceed schedulable nodes, or when node-local storage pins a member to a node, so it is safe to default on. - **Override:** set `affinity` to any rule — e.g. a hard `requiredDuringSchedulingIgnoredDuringExecution` podAntiAffinity — to forbid co-location outright on clusters with enough nodes and non-node-pinned storage. A supplied value replaces the default cleanly. The existing `topologySpreadConstraints` block (and its `_cluster.scheduling.globalAppTopologySpreadConstraints` override) is unchanged. Generated artifacts (`values.schema.json`, `README.md`, and the embedded `openAPISchema` in `etcd-rd`) were regenerated with `make generate`; `make test` passes (18/18). ### Screenshots n/a — no UI changes. ### Downstream repositories Walked the trigger map against the diff: this only adds one values key to an app chart. No package is added/renamed/removed, `kind`/`plural` are unchanged, no installer values or release assets or variants change, and `README.md` is regenerated here (the website bot regenerates reference pages from it on release). No manual downstream follow-up is required. - [x] No downstream repository is affected by this change ### Release note ```release-note fix(etcd): etcd clusters now spread their members across nodes by default via a soft podAntiAffinity, and expose an `affinity` value so operators can set a hard rule; previously a single node could host a quorum of members and take out the cluster when it failed. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added configurable pod affinity rules for etcd deployments. * By default, etcd members use soft anti-affinity to spread across hosts without blocking scheduling. * Custom affinity settings can override the default behavior. * Exposed affinity configuration in the etcd application schema and dashboard. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
6bb946fa5b |
fix(postgres-operator): bump CloudNativePG to 1.30.0 for the Cluster scale-subresource selector (#3951)
## What this PR does Bumps the vendored `cloudnative-pg` chart from 0.27.1 to 0.29.0 (operator + CRDs from 1.28.1-patched-to-1.28.2 up to 1.30.0) and drops the hand-maintained `patches/cloudnative-pg-1.28.2.patch` entirely — 0.29.0 is a published chart, so the version-alignment patch is no longer needed. **Why this is required.** Database read-replica autoscaling (community proposal `design-proposals/database-horizontal-autoscaling`) drives the CNPG `Cluster` scale subresource with a stock HPA (via KEDA). That only works when the subresource serves `status.selector`, which [cloudnative-pg#8996](https://github.com/cloudnative-pg/cloudnative-pg/pull/8996) added and shipped in CNPG 1.28.4 / 1.29.2 / 1.30.0. cozystack shipped 1.28.2, whose scale subresource has no selector, so the HPA fails with `InvalidSelector`. **Why 1.30.0 and not 1.28.4.** The floor is ≥ 1.28.4, but the upstream chart repo publishes no chart for 1.28.4 (the 1.28 line stops at chart 0.27.1 / appVersion 1.28.1), so 1.28.4 could only be reached by hand-patching the 0.27.1 chart's operator image and CRDs up to the backport. A live PoC on a dev cluster proved that path is broken: the 1.28.4 operator running under the 0.27.1 (1.28.1) chart templates crash-loops on a missing RBAC grant it needs but the older chart does not provide — `endpointslices.discovery.k8s.io is forbidden: ... cannot list resource "endpointslices"` — so `status.selector` is never populated and the HPA still cannot scale. 1.30.0 (chart 0.29.0) is the only published chart that carries the selector together with matching RBAC/CRDs/templates, and the 1.28 line is EOL (2026-06-30), so 1.30.0 also keeps us on a supported release. cozystack uses the barman-cloud plugin (not native barman, removed in 1.31) and no CNPG Poolers, so the 1.30 deprecations and the known pooler bug do not apply. **Validation on a dev cluster (CNPG 1.30.0).** Operator rolls out clean (no crashloop, the `endpointslices` RBAC error is gone); all existing `Cluster` objects return to Ready; `/scale` now returns a populated `status.selector` (`cnpg.io/cluster=<name>,cnpg.io/podRole=instance`); an HPA pointed at a `Cluster` no longer reports `InvalidSelector` (`AbleToScale=True, SucceededGetScale`); psql connectivity and streaming replication verified. The version-guard unittest is updated to 1.30.0 and now also asserts the clusters CRD scale subresource carries `labelSelectorPath: .status.selector`. ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff, file by file. The change touches only the vendored `cloudnative-pg` operator chart (CRDs, RBAC, values), the package `Makefile`, and the version-guard test. The `packages/apps/postgres` app and its `version` enums are unchanged, `packages/core/platform/values.yaml` is unchanged, and the `clusters.postgresql.cnpg.io` CRD is not one the terraform provider types by hand (it travels as opaque JSON). No downstream repository restates anything this diff changes. - [x] No downstream repository is affected by this change ### Release note ```release-note fix(postgres-operator): bump CloudNativePG to 1.30.0 so the Cluster scale subresource serves status.selector, enabling HPA-driven read-replica autoscaling (previously the HPA failed with InvalidSelector on 1.28.2) ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Upgraded the CloudNativePG operator and Helm chart to 1.30.0/0.29.0. * Added expanded image catalogs, pooler settings, pod selectors, scheduling options, monitoring TLS, and LZ4 backup compression. * Improved permissions for database roles, endpoint slices, and lease monitoring. * **Bug Fixes** * Strengthened configuration validation and removed the deprecated workload reference option. * Updated compatibility requirements to Kubernetes 1.29 or newer. * **Documentation** * Expanded installation, configuration, verification, upgrade, and uninstall guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
f5b957ee27 |
feat(kubernetes)!: manage worker pools as KubernetesNodes, adopt on upgrade (Phase 2, breaking) (#3315)
## What this PR does Phase 2 of the Kubernetes-app split ([cozystack/community#8](https://github.com/cozystack/community/pull/8)) — the **breaking** half. Stacked on #3314 (review that first). Removes worker node pools from the `kubernetes` chart (they moved to `kubernetes-nodes` in #3314) and adopts existing pools on upgrade: - `kubernetes` chart is now **control-plane only**: drops the pool `range` block, the `talos-reconcile` Job + RBAC (deleted; moved to `kubernetes-nodes`), the preserve-old-KMT block, the `kubernetes.nodeGroups` helper, and the per-group dashboard RBAC. - **`spec.nodeGroups` is removed from the `Kubernetes` CR** (BREAKING). Worker pools are now separate `KubernetesNodes` resources. - **Migration 54** (bumps `targetVersion` to 55): pre-upgrade hook that, for every `kubernetes-<cluster>` release, creates one `kubernetes-nodes-<cluster>-<pool>` HelmRelease and re-annotates the existing worker objects onto it with `helm.sh/resource-policy=keep`, so the now control-plane-only parent upgrade does not prune running workers. The default `md0` (implicit when `nodeGroups` was empty) is materialised explicitly. Idempotent. Genuine infrastructure/read failures fail closed (`exit 1`, retried on the next upgrade); hostile stored shapes (a pool name over Helm's 53-char release-name cap, an RFC-1123-invalid or whitespace/slash map key, a non-object `nodeGroups`) are warned, pinned, and skipped rather than deadlocking the entire fleet's upgrade. - The `ingressNginx` addon no longer auto-provisions a node (the implicit `md0` default is gone). If enabled without a pool carrying `roles: [ingress-nginx]`, the controller DaemonSet simply schedules nothing (degraded, not failed) — the requirement is documented in the chart NOTES.txt rather than enforced at render time. Because the child chart renders the pool objects **byte-identically** to the old parent render (golden parity in #3314; the KubevirtMachineTemplate content-hash name is preserved), adoption does not churn live worker VMs. ### Migration number `54` is contiguous with main (latest is `53`) and `targetVersion` is `55`. The `52`/#3201 collision noted in earlier revisions has been resolved: this branch was rebased and merged past it, so the numbering is contiguous and `run-migrations.sh` has no gap to hard-fail on. ### Not yet done (blocking draft → ready) - [ ] **dev e2e upgrade validation** (`cozystack-pr-test`): upgrade a custom-`nodeGroups` cluster and a default-`md0` cluster; assert zero worker-VM churn (KMT hash, Machine uid/creationTimestamp stable) and that the adopted child HRs reconcile. - [ ] downstream trigger-map walk (website docs for the `nodeGroups` → `KubernetesNodes` change; terraform-provider for the removed API field). ### Downstream repositories Deliberately unticked (draft) — the breaking API change affects docs and the terraform provider; follow-ups to be filed before ready. - [ ] 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: ```release-note Worker node pools of a managed Kubernetes cluster are now managed as separate `KubernetesNodes` resources. `spec.nodeGroups` on the `Kubernetes` CR is removed; existing pools are adopted automatically on upgrade. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Worker pools are now separate `KubernetesNodes` resources with their own lifecycle. * Added a render-time compatibility guard to prevent worker pools from running ahead of the parent control-plane minor version. * Improved upgrade/uninstall behavior to better preserve already-adopted worker objects. * **Breaking Changes** * Removed per-pool `nodeGroups` and worker node health settings from the `Kubernetes` chart/CR; pool configuration now lives in `KubernetesNodes`. * Ingress requires at least one `KubernetesNodes` pool with `roles: [ingress-nginx]`. * `cluster` and `storageClass` are immutable after creation; pool `version` may lag but must not be ahead of the parent minor. * **Documentation** * Updated READMEs, notes, and schemas to reflect the `KubernetesNodes` split and the immutability/version rules. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c83a22e077 |
fix(etcd): spread members across nodes with default soft pod anti-affinity
The etcd chart only set a soft topologySpreadConstraints and never rendered spec.affinity, so the scheduler could place several members of a 3-node EtcdCluster on the same node; losing that node then took out quorum (observed in production after a single node reboot). Render EtcdCluster.spec.affinity from a new `affinity` value. When it is left empty the chart applies a soft (preferred) podAntiAffinity on kubernetes.io/hostname, so members are spread on a best-effort basis without ever leaving a member Pending on small or node-pinned-storage clusters. Operators can override the value — e.g. a hard requiredDuringScheduling rule — where node count and storage allow. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
e23fceffe7 |
docs(postgres-operator): distinguish chart version from appVersion in comment
Reword the values.yaml comment so it does not call appVersion 1.30.0 a "published chart": the published chart version is 0.29.0 and it carries appVersion 1.30.0. Keeping the two distinct stops a maintainer from running `helm pull --version 1.30.0`. Addresses CodeRabbit review feedback on #3951. Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
091d8ce143 |
fix(ingress-nginx): re-pin both images to the rebuilt v1.11.5
The controller digest this pinned was a build dated 2025-03-26, whose
nginx.tmpl calls buildHTTP3Listener — a function no v1.11.5 binary
defines. Go binds template functions at parse time, so the
{{ if $cfg.UseHTTP3 }} guard around it does not help and the controller
crash-loops on a default config, serving no webhook at all.
The tag pointed at that image because the controller had not been built
since March: the build in the fork failed on a luarocks step, and the
cause was outside the repository. luarocks resolves a rock name through
the luarocks.org index, and that index has grown past LuaJIT's hard
limit of 65536 constants per function, so no client can load it and
every lookup returns "No results matching query were found". Neither a
luarocks bump nor GNU wget fixes that; installing from a rockspec URL,
which skips the index, does. Fixed in
cozystack/ingress-nginx-with-protobuf-exporter#6 and the v1.11.5 tag
re-pointed at it.
Verified in the rebuilt image: zero references to buildHTTP3Listener,
and both lua-protobuf and lua-iconv present — the rocks this fork exists
to add.
The exporter is re-pinned in the same commit rather than separately. It
had drifted a build ahead of its tag before this rebuild, and refreshing
one half alone would leave the PR looking updated while the controller
was still the March build.
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
|
||
|
|
bf38655ab5 |
docs(computeplane): nodeGroups allowlist drops unlisted fields, not pass-through
The nodeGroups docs still described the old denylist behaviour: "unlisted NodeGroup fields pass through to the wrapped chart unvalidated". After the switch to an allowlist (pick) both halves are wrong: fields go to the kubernetes-nodes pool chart (not the wrapped kubernetes chart), and anything the pick does not name is dropped, not passed through. A tenant that sets e.g. talos.version on a nodeGroup gets no error and no effect (accepted-but-inert). Correct the source @param and the hand-written README intro, regenerate schema/README/cozyrd, and refresh the stale "omit" wording in the splice test comment to say allowlist. Signed-off-by: IvanHunters <xorokhotnikov@gmail.com> |
||
|
|
5bcdb8cfb8 |
fix(postgres): let in-place restore re-bootstrap instead of wedging
An in-place RestoreJob (targetApplicationRef omitted) re-bootstraps the
Postgres app under its own name. On stock v1.6.2 it never comes back up,
for two independent reasons in the same restore patch - both surface only
in place, because a to-copy restore targets a different app.
1. Archive-vs-recovery serverName collision. The driver set the restored
cluster's WAL-archive serverName equal to the recovery source
(bootstrap.serverName = bootstrap.oldName = sourceServerName) while the
chart pinned the archiver serverName to the release name. In place those
coincide, so the cluster archived onto the source's own WAL prefix and
barman-cloud-check-wal-archive refused to start ("Expected empty
archive"). Fixed by giving the restored cluster a fresh
bootstrap.newServerName, keyed by the RestoreJob UID (unique per
restore, stable across reconciles); the chart archives there while
externalClusters still recovers from bootstrap.serverName. This is what
the manual CNPG strategies hit (serverName defaults to the cluster
name); the cozy-default flow dodged it (serverName is <ns>-<app>).
2. useSystemBucket guard. The restore populates explicit S3 coordinates
(destinationPath / s3CredentialsSecret / endpointCA) but left
backup.useSystemBucket=true, so the chart's "bootstrap.enabled is
incompatible with useSystemBucket" guard failed the HelmRelease upgrade
and the cluster never re-rendered. This is what the cozy-default
(system-bucket) flow hits on an in-place restore. Fixed by clearing
useSystemBucket in the restore patch, since the app is now on the
explicit-creds path. The driver's app view (postgresapp.Backup) gains
the field, without omitempty so a false survives the merge patch.
Both are exercised only by an in-place restore; the postgres backup e2e
drove only to-copy, so neither surfaced in CI. A step 50 in-place restore
now guards reason 1 (manual-strategy flow); reason 2 is covered by a Go
unit assertion (a system-bucket in-place e2e needs the platform bucket in
CI and is left as a follow-up).
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
517e33f67b |
fix(keycloak): pin the Vault leg to the configured CA and roll on rotation
SSL_CERT_FILE alone does not narrow trust. crypto/x509 replaces only its file list and then scans SSL_CERT_DIR unconditionally, and the pinned proxy image ships /etc/ssl/certs/ca-certificates.crt, so the connection guarding the KEK trusted the private CA plus every public root — a mis-issued certificate for the Vault hostname was still accepted. Set SSL_CERT_DIR to the mount as well, which collapses the pool to the CA the operator configured. An unvalidated caBundle failed open on top of that: a non-PEM value is dropped silently by AppendCertsFromPEM, leaving the proxy on the image's roots with no error at all. Reject it at render time, the way backend and auth already are. Rotating an inline caBundle had no effect either. The pod template did not change, so no new pods, and crypto/x509 memoizes the pool per process — the upgrade reported success while the old CA stayed in use until some later restart. Derive a checksum/vault-ca annotation from the bundle so the rollout happens with the change. Also add vault.caSecretKey: the projected key was hardcoded to ca.crt, and a bundle stored under another name (a cert-manager Certificate from an ACME issuer emits no ca.crt at all) wedged the pod at mount time with no way to correct it from values. Drop the claim that the database leg verifies against its own CA pool: the chart never sets KKP_BACKEND_CA_FILE, so that leg is plaintext today, as the note 30 lines above already says. The conclusion holds — it never consults the system trust store — but for the other reason. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru> |
||
|
|
98ef5413f4 |
fix(keycloak): keep the Vault CA options inert on the static KMS backend
The volume and mount that consume the Vault CA sit at pod level, outside the vault-transit env block, while $vaultCASecret was seeded from caSecretName regardless of the backend. With kms.backend=static and caSecretName set, the Deployment mounted a Secret the chart never renders, so the proxy — the single choke point for all Keycloak DB traffic — would sit in ContainerCreating. Gate both CA keys, and the mutual-exclusion check between them, on vault-transit, so the whole vault.* subtree behaves the same way under static: ignored. Project only ca.crt from a pre-seeded Secret, which keeps a cert-manager tls.key out of the container and turns a bundle stored under another key into a mount failure rather than an opaque TLS error later. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru> |
||
|
|
3531553176 |
feat(keycloak): let the KMS proxy trust a private Vault CA
The proxy could only reach a Vault whose certificate chains to a publicly trusted root, so a Vault fronted by an internal load balancer with a self-signed certificate was unreachable over HTTPS. Allow the CA to be supplied inline (caBundle, the chart renders the Secret) or as a pre-seeded Secret (caSecretName), mounted and exposed as SSL_CERT_FILE. That redirects only the system trust store, which nothing but the Vault client consults — the database leg verifies against its own CA pool. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru> |
||
|
|
30af0e1f60 |
docs(postgres-operator): update values comment for the native 1.30.0 bump
The explanatory comment in values.yaml still described the removed patches/cloudnative-pg-1.28.2.patch mechanism and the old 0.27.1/1.28.1 versions, and pointed at an "edit the patch" workflow that no longer exists. Rewrite it to describe the native 0.29.0/1.30.0 chart bump (operator and CRDs aligned by construction, no patch step) while preserving the still-valid guidance: do not pin image.tag, keep the operator and CRDs in lockstep, since a mismatched pin prunes CRD status fields and fails backups cluster-wide. Addresses review feedback on #3951 (IvanHunters, CodeRabbit). Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
34e801cb9e |
fix(computeplane): stop tenant pool values leaking cluster-level keys and overflowing release names
The per-pool child HelmRelease spliced the tenant nodeGroup dict verbatim (toYaml) after setting cluster/clusterReleaseName, and the NodeGroup schema has no additionalProperties:false. A tenant could therefore (a) set a duplicate cluster/clusterReleaseName key that wins the YAML merge and re-parents the pool onto a tenant-controlled cluster, or (b) pass talos/ version/images through a shape-only knob and pick the worker OS image, installer and kubelet version. omit those five keys at the splice so only the pool's own shape flows through (tolerated extras like maxUnhealthy still do). Separately, each pool renders a 38-char fixed release-name prefix against Helm's 53-char cap, so a nodeGroup key over 15 chars created a HelmRelease the apiserver accepts but Flux never reconciles, with no other signal. Guard the length at render time, next to the ingress-role guard, mirroring the migration overflow branch and the aggregated API's validateNameLength. Also refresh the generated values doc: the module now materialises its own default md0 and pool storageClass falls back to the kubernetes-nodes chart default, not the wrapped kubernetes app. Signed-off-by: IvanHunters <xorokhotnikov@gmail.com> |
||
|
|
cdc34d6e34 |
fix(kubeovn-webhook): restrict the webhook port to the API server
The /mutate-pods handler returns namespace-derived annotations to any caller that reaches it (GHSA-g883-q79m-8225), and until now nothing stopped an arbitrary in-cluster pod from being that caller. Client-certificate verification, added in #2698, cannot close this on its own. Enabling --client-ca-file requires the API server to *present* a certificate, which it does only when its AdmissionConfiguration names a kubeConfigFile for the webhook plugins. Checked on a running Cozystack control plane: the AdmissionConfiguration carries a PodSecurity block and nothing else, so no certificate is offered at all. Flipping to RequireAndVerifyClientCert there would reject the API server itself, and with failurePolicy: Fail that stops pod creation cluster-wide. No amount of log observation changes that — it is a property of the control plane's configuration, not of the traffic. So the network is where the caller can actually be constrained. The policy denies pod and world traffic to the serving port and needs no carve-out: the API server runs on the host network, so it is not a Cilium endpoint and fromEndpoints never matches it. Unlike the ingress-nginx admission webhook this component exists only on the management cluster, so there is no konnectivity-tunnelled caller to exempt either, and no forgeable pod label anywhere in the selector. It is a CiliumNetworkPolicy rather than a core NetworkPolicy, and that distinction is why it works at all: kube-ovn runs with ENABLE_NP: false and programs no ACLs, so a core NetworkPolicy would be created and never enforced. The values comment claiming NetworkPolicy was simply not an option is corrected — it was true only of the core kind. Deny-only with default-deny disabled, matching the ingress-nginx admission policy: putting these pods behind a default-deny would mean enumerating every port they serve and breaking traffic on the first one forgotten. Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
4689b2b844 |
feat(kubeovn-webhook): add client-certificate verification, off by default (#2698)
## What this PR does Adds client-certificate verification to the kube-ovn webhook, and turns it on only when an operator supplies a CA bundle. `--client-ca-file` is new and empty by default. With it empty the server sets `tls.RequestClientCert`: it asks callers for a certificate, records the issuer of whatever arrives, and serves the request either way. Set the flag and the server switches to `tls.RequireAndVerifyClientCert`, so a caller without a certificate signed by that CA is refused at the TLS handshake. **This is step one of two, and on its own it does not close [GHSA-g883-q79m-8225](https://github.com/cozystack/cozystack/security/advisories/GHSA-g883-q79m-8225).** With the default empty value nothing is rejected — the webhook still answers an unauthenticated caller, and the only change is that the caller's issuer now appears in the logs. That is deliberate: switching straight to `RequireAndVerifyClientCert` would break every cluster whose API server sends no client certificate, and there is no way to know from here which those are. The logs are how we find out. Step two, in a follow-up: read a day of issuer logs on a Talos cluster and on `isp-full-generic`, confirm the API server presents a certificate we can pin, then set the CA bundle so verification becomes mandatory. The advisory should be published as fixed only after that lands — not with this PR. An earlier revision also carried a NetworkPolicy restricting ingress to `kube-system`. It was dropped, and the title has been corrected to match; only the TLS path is left here. ### Screenshots N/A — no user-facing or UI change. ### Downstream repositories - [x] 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: Walked the trigger map against the diff: no row matches. The change touches one system package and the Go image it builds — no app package, no `values.schema.json`, no `ApplicationDefinition`, no platform variant, no node prerequisite. ### Release note ```release-note feat(kubeovn-webhook): the webhook now accepts --client-ca-file. Left empty (the default) it requests and logs client certificates without requiring them; set to a CA bundle it rejects callers that do not present a certificate signed by that CA. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added optional client-certificate verification for the pod mutation webhook through configurable CA settings. * Added separate rate limits for requests with and without client certificates. * Added remote-address logging and clearer verification status reporting. * **Security** * Requests continue to be forwarded while certificate observations are recorded when verification is disabled. * Serving-certificate updates remain supported; client CA changes require a webhook restart. * **Documentation** * Documented client CA configuration and current certificate rotation limitations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
3c78c0be56 |
feat(clickhouse): add configurable version parameter
Add a `version` value (v25.8, v25.3, v24.9; default v24.9) to the ClickHouse chart, backed by files/versions.yaml and resolved by templates/_versions.tpl for both the ClickHouse server and Keeper images, so the Cozystack API returns the engine version (spec.version) like the other managed-DB charts. Default v24.9 -> 24.9.2.42 keeps existing installations byte-for-byte identical. The maintainer-run hack/update-versions.sh generator intersects the server/keeper Docker Hub tags byte-collated (LC_ALL=C) so comm keeps tags across the X.9 -> X.10 boundary; freezes the default major to the tag it already ships (read from versions.yaml, so the freeze follows the default); errors on a dropped default or an unresolvable major; writes atomically; and runs on stock macOS bash 3.2 / BSD. Covered by hack/clickhouse-update-versions_test.bats (10 cases) and the tests/version_test.yaml helm-unittest suite. Refs: #1246 Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
b20e8ee91a |
fix(postgres-operator): bump CloudNativePG to 1.30.0 for the Cluster scale-subresource selector
Bump the vendored cloudnative-pg chart 0.27.1 -> 0.29.0 (operator + CRDs 1.28.1-patched-to-1.28.2 -> 1.30.0) and drop the hand-maintained cloudnative-pg-1.28.2.patch entirely — 0.29.0 is a published chart, so no version-alignment patch is needed. Why this is required -------------------- Database read-replica autoscaling (community proposal design-proposals/database-horizontal-autoscaling) drives the CNPG Cluster scale subresource with a stock HPA (via KEDA). That only works when the subresource serves status.selector, which cloudnative-pg#8996 added and shipped in CNPG 1.28.4 / 1.29.2 / 1.30.0. cozystack shipped 1.28.2, whose scale subresource has no selector, so the HPA fails with InvalidSelector. Why 1.30.0 and not 1.28.4 ------------------------- The floor is >= 1.28.4, but the upstream chart repo publishes no chart for 1.28.4 (the 1.28 line stops at chart 0.27.1 / appVersion 1.28.1), so 1.28.4 could only be reached by hand-patching the 0.27.1 chart's operator image and CRDs up to the backport. A live PoC on a dev cluster proved that path is broken: the 1.28.4 operator running under the 0.27.1 (1.28.1) chart templates crash-loops on a missing RBAC grant it needs but the older chart does not provide — endpointslices.discovery.k8s.io is forbidden: ... cannot list resource "endpointslices" — so status.selector is never populated and the HPA still cannot scale. The hand-patch would have to port the operator's RBAC (and any other template delta) from 1.28.1 to 1.28.4 by hand, with no published chart to diff against. 1.30.0 (chart 0.29.0) is the only published chart that carries the selector together with matching RBAC/CRDs/templates, and the 1.28 line is EOL (2026-06-30), so 1.30.0 also keeps us on a supported release. cozystack uses the barman-cloud plugin (not native barman, removed in 1.31) and no CNPG Poolers, so the 1.30 deprecations and the known pooler bug do not apply. Update the version-guard unittest to 1.30.0 and assert the clusters CRD scale subresource carries labelSelectorPath: .status.selector. Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io> |
||
|
|
724354ffe1 |
fix(console): address the review findings on the Admin portal rework
Four of the five findings share one root cause: a node in the tenant
tree can attach to its nearest *visible* ancestor when its real parent
is inaccessible, and per-row actions then derived their target from
that bridged ancestor.
The Edit action on a bridged tenant row pointed at a Tenant CR that
does not exist. The CR lives in the real parent's namespace, so the
target now comes from realParentNamespace — and Edit is offered only
when that namespace is readable, because otherwise there is no CR the
user could open and the button could only ever dead-end. The test that
asserted two Edit buttons had frozen the broken target as expected
behaviour; it now names the rows it expects, and the Edit button gained
a title so that assertion means something.
An inherited module chip is rendered only when the providing tenant is
NOT visible, yet it was a live button: clicking it denied the instance
GET and switched the active tenant to a namespace outside the visible
set, which the selector then bounced to an arbitrary fallback. It is
now an inert badge that states where the module comes from.
InfoRedirect's missing-definition branch hardcoded /console, throwing an
admin out of the portal its own docstring promises to preserve.
The sidebar's alsoMatch used a bare startsWith, so /admin/apps would
light up for /admin/apps-v2. It now matches whole path segments.
And SecretsTab let a rejected clipboard write escape as an unhandled
rejection, leaving the button silent — a denied permission or an
insecure context was indistinguishable from a no-op. It now reports the
failure.
Declined: CodeRabbit asked to split `import { useK8sList, type K8sResource }`
into a separate `import type`. The inline modifier is valid under
verbatimModuleSyntax and is what 26 other imports in this console use,
so the change would make the file the odd one out.
Reported-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
|
||
|
|
8554c036de |
fix(ingress-nginx): confine the konnectivity carve-out to kube-system
The admission-webhook deny exempted any endpoint carrying k8s-app: konnectivity-agent, in any namespace. Because the rule disables default-deny, falling out of the deny is the entire access decision — so any pod anywhere could reach the webhook port by labelling itself, which is precisely the access the policy exists to remove. k8s-app is an ordinary pod label a workload sets on itself. Nothing in the platform constrains pod labels by admission, and Cilium keeps k8s-app identity-relevant (its label filter ships no default whitelist), so the forged label really does change the endpoint's identity rather than being filtered out. The namespace key is derived by Cilium from the pod's actual namespace and cannot be set by the workload. Splitting the deny in two keeps the carve-out and closes the hole: one selector for everything outside kube-system with no label condition at all, one for kube-system carrying the existing NotIn. Only the agent in its own namespace is exempt. Kamaji at the pinned 26.3.6-edge puts nothing but k8s-app: konnectivity-agent on the agent pod template (internal/resources/konnectivity/agent.go), so the label remains the only available discriminator and the namespace confinement is what makes it safe to key on. Tests cover both halves: the outside-kube-system selector must carry no label condition, and the kube-system selector must keep exactly the namespace and label pair. Reported-by: Daniil Myasnikov <myasnikovdaniil2001@gmail.com> Assisted-By: Claude Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
bfd4ea0643 |
feat(dashboard): land on the Console instead of the Marketplace
Opening the dashboard without a path sent the user to the catalog, and the header gave Marketplace the primary emphasis. The Console is the better entry point: it shows what the tenant already runs, which is what someone opening the dashboard almost always came for. The Marketplace stays one click away for when the intent is to add something new. The landing path becomes a named constant beside the portal helper, so the redirect and the tab emphasis cannot drift apart, and the library's own default tab set is reordered to match rather than contradict its only consumer. Assisted-By: Claude Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
cfd5444f16 |
fix(dashboard): let a masked secret be copied and confirm the copy
Copying a secret key required revealing it on screen first, and the button gave no sign that anything reached the clipboard. The value the list already carries is enough to copy, so the button works while the key stays masked, and it briefly turns into a check mark to confirm. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
e75500682c |
fix(dashboard): tie navigation to where a page was opened from
Detail pages offered a fixed tab set, so Info showed Workloads, Services and Ingresses tabs it never fills; tabs now appear only for resource groups the instance actually owns. The probes reuse the tabs' own list refs and label selector, so they share one cache entry and one watch. Back and the sidebar followed the plural instead of the entry point: a module page sent the user to a one-item list and lit nothing in the sidebar. Module pages now return to Modules and keep it highlighted, while Info returns to Tenants and highlights that, matching the trees they are reached from. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
1279652b56 |
feat(dashboard): list External IPs across every accessible tenant
The page showed LoadBalancer services of the selected tenant only, so an operator had to switch tenants one by one to find an address. It now lists them for every visible TenantNamespace in a single table with a Namespace column. A namespace whose services the user cannot list contributes no rows instead of failing the page. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
21b8a0e2f0 |
feat(dashboard): rebuild Modules as a tenant tree of module chips
The Modules page listed the selected tenant's modules as Enabled or Disabled cards, so a module a parent tenant provides looked disabled and clicking it led to a Tenant CR that does not exist in the child's namespace — an endless spinner. It now covers every tenant the user can access, as the same hierarchy tree the Tenants page draws, with each tenant's modules as chips carrying the module icon, the namespace running it and its readiness. Availability comes from the `namespace.cozystack.io/<module>` label on each TenantNamespace, which names the providing namespace: the tenant itself for a local module, an ancestor for an inherited one. An inherited chip appears only when that ancestor is out of the user's reach, since otherwise its own chip already shows the module. Clicking any chip opens the module in whichever tenant runs it. Tenants whose whole subtree has no modules collapse into a "+ N tenants" branch, and the Info module is left out entirely as every tenant carries it. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
7a48d455ac |
feat(dashboard): show the tenant hierarchy on the Tenants page
The flat table becomes a tree built from TenantNamespaces, whose `tenant.cozystack.io/<ancestor-ns>` labels carry the full ancestry. A tenant whose parent is not accessible attaches to its nearest visible ancestor instead of disappearing, and rows are labelled relative to that parent, so a sub-tenant reads as `crpjxhwm` rather than `whmcs-crpjxhwm`. Subtrees collapse from the row itself. Each row also carries its own actions: Info opens that tenant's Info page, and Create Tenant orders a sub-tenant at any level of the tree. Editing a tenant switches to the namespace holding its Tenant CR — the parent's, or its own for the hierarchy root. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
ef73a0e47c |
feat(dashboard): move Administration into the Admin portal
Administration (Tenants, Modules, External IPs, per-tenant Info) leaves the Console sidebar for Admin, which is now always visible: those pages need no special permission. The two cluster-wide operator areas keep their own gates, so Capacity and Backup Classes still appear only for users who can open them. Resource routes become portal-aware through a shared base-path helper, so a detail, edit or order page opened from Admin stays under /admin instead of jumping back to /console. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |
||
|
|
5178e169b1 |
feat(dashboard): drop the Marketplace landing page
Open the grouped application list right away instead of the "Choose a product to deploy" page and its Show-all-apps button. Category links keep filtering to a single category. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> |