Commit Graph
5128 Commits
Author SHA1 Message Date
Myasnikov Daniil d1ad59509d feat(flux): regenerate embedded management-cluster manifests for Flux 2.8.0
Re-render internal/fluxinstall/manifests/fluxcd.yaml via
`make update` in packages/core/flux-aio (timoni bundle build against
oci://ghcr.io/stefanprodan/modules/flux-aio at the latest tag) so
the cozystack-operator binary embeds Flux 2.8.0 instead of 2.7.3.
Auto-syncs the helm-controller image into fluxcd-tenants.yaml.

Embedded versions now match the chart bump in the prior commit:
  flux:                   v2.7.3 -> v2.8.0
  helm-controller:        v1.4.3 -> v1.5.0   (enables healthCheckExprs)
  source-controller:      v1.7.3 -> v1.8.0
  kustomize-controller:   v1.7.2 -> v1.8.0
  notification-controller:v1.7.4 -> v1.8.0
  source-watcher:         v2.0.2 -> v2.1.0

Verified by rebuilding cmd/cozystack-operator and running `strings`
on the binary -- the expected v1.5.0 / v1.8.0 / v2.1.0 image strings
are now embedded in place of the v1.4.x / v1.7.x ones.

Structure preserved:
- single AIO Deployment named "flux" in namespace cozy-fluxcd
- all 5 controllers + source-watcher as containers in one pod
- custom tolerations from flux-aio.cue intact
- Namespace pod-security label still "privileged" via the
  Makefile yq post-processing step

Net diff is -3664 lines (mostly CRD schema simplification in 2.8).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-28 12:43:49 +05:00
Myasnikov Daniil 9aa8710ef2 feat(fluxcd): bump flux-operator/instance to v0.48.0, web UI opt-in
Bump flux-operator and flux-instance vendored charts from v0.33.0 to
v0.48.0, moving the Flux distribution from 2.7.x to 2.8.x. This brings
helm-controller v1.5.x, which exposes spec.install.healthCheckExprs on
HelmRelease — a prerequisite for adding real readiness gating to
dependency-target packages (postgres, mongodb, kafka, etc.).

The new flux-operator chart defaults web.enabled=true; override it to
false in packages/system/fluxcd-operator/values.yaml so the Flux Status
Web UI stays opt-in. Operators wanting it can set web.enabled=true and
configure web.config / web.configSecretName / web.ingress per the
upstream chart values.

Patches reapplied:
- kubernetesEnvs.diff: matched cleanly (offset shift only)
- networkPolicy.diff: extended to also restrict port 9080 (web UI port);
  hunk count updated 20 -> 22

Pre-flight: no references to the v0.39-removed --disable-wait-interruption
flag anywhere in packages/ or internal/.

Verified locally:
- helm template renders both packages without errors
- web.enabled=false (default): no web Deployment/Service/NetworkPolicy
- web.enabled=true: renders web NetworkPolicy on port 9080
- CiliumClusterwideNetworkPolicy lists 8080/8081/9080
- FluxInstance distribution.version: "2.8.x", all 7 components present
- Cozystack kustomize patches (concurrent, requeue, storage-adv-addr,
  events-addr) intact

Notable upstream changes between v0.33 and v0.48:
- Flux 2.8.x with HelmRelease inventory format change (auto-migrated)
- Reliability fixes for stuck FluxInstance/ResourceSet on health-check
  cancellation (v0.39, v0.46)
- New CLI: flux-operator migrate, diff, patch instance, distro mirror
- ResourceSetInputProvider: GitLab Environments, GitHub App, Gitea/Forgejo
- CVE-2026-23990 (Web UI OIDC impersonation bypass) — N/A while web is off

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-28 12:43:49 +05:00
Myasnikov Daniil 22187e069d ci(release): repair orphaned draft tag_name on retag
When the git tag was deleted and re-created between draft creation
and merge, GitHub orphaned the draft release: tag_name became
"untagged-<hash>" while `name` was preserved. The finalize step
looked up the draft by tag_name and threw `Draft release for <tag>
not found`, blocking the release.

Fall back to matching by `name` only when the candidate is the
GitHub-orphaned form (tag_name matches /^untagged-/), repair the
draft's tag_name via updateRelease, then publish. Hard-fail is
preserved when neither lookup matches.

Same failure recurred on the v1.4.1 finalize run; this commit
prevents recurrence but does not auto-recover the stuck v1.4.2
draft.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-28 12:00:54 +05:00
myasnikovdaniil 8afb99b282 docs(release): add changelog for v1.4.2 (#2759)
This PR adds the changelog for release `v1.4.2`.

✅ Changelog has been automatically generated in
`docs/changelogs/v1.4.2.md`.
2026-05-28 11:35:15 +05:00
cozystack-ci[bot] 6270c728eb docs: add changelog for v1.4.2
Signed-off-by: cozystack-ci[bot] <274107086+cozystack-ci[bot]@users.noreply.github.com>
2026-05-28 01:52:14 +00:00
myasnikovdaniil 74de393e1a test(e2e): ouroboros fold + CSI/NFS ordering + OIDC keycloakInternalUrl (#2728)
## What this PR does

Bundles four independent e2e improvements lifted from #2619:

1. **`test(e2e): fold ouroboros assertions into kubernetes-latest`** —
moves the standalone `hack/e2e-apps/ouroboros.bats` (which provisioned
its own full Kamaji control plane, ~25m) into the existing
`kubernetes-latest` run as an optional addon. The assertions cover
ouroboros HR Ready, controller pod Running, `Ingress → coredns-custom`
rewrite-line injection, and end-to-end DNS resolution from inside the
tenant. Reuses the existing tenant cluster instead of building a second
one.

2. **`test(e2e): wait for tenant CSI HR Ready before NFS test`** —
explicit `kubectl wait hr/kubernetes-${test_name}-csi
--for=condition=ready` ahead of the NFS pod. Otherwise the NFS pod
schedules while the `kubevirt-csi-node` DaemonSet is still rolling out,
eats ~1m on `FailedAttachVolume` retries, and trips the pod-Succeeded
wait.

3. **`test(e2e): bump NFS pod wait to 10m to fit slow-path chain`** —
was 5m. With dependencies properly serialized (per #2), 5m was tight on
slow runners; 10m gives the chain genuine room without masking real
bugs.

4. **`test(e2e): set keycloakInternalUrl when enabling OIDC`** — the
dashboard gatekeeper (oauth2-proxy) does OIDC discovery against the
keycloak Service URL; without an explicit `keycloakInternalUrl`, it
falls back to the public ingress URL which isn't routable from inside
the cluster yet during install.

## Origin

All four commits lifted unchanged from #2619.

### Release note

```release-note
NONE
```


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

* **Tests**
* Integrated Ouroboros validations into the main Kubernetes end-to-end
flow (removed separate standalone Ouroboros test)
* Added hairpin-NAT and in-tenant DNS resolution checks for Ouroboros
scenarios
* Improved Keycloak OIDC test by configuring an in-cluster internal URL
* Increased various timeouts and readiness waits and added tenant CSI
readiness gating to improve test reliability

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2728?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 23:58:12 +05:00
myasnikovdaniil 6b76db6929 fix(platform): add cert-manager dependency to webhook-cert consumers (#2726)
## What this PR does

Adds `cert-manager` as a `dependsOn` entry to the five PackageSources
whose charts apply `cert-manager.io/v1` `Certificate` CRs and need the
webhook ready before their first reconcile. Without this, the helm
install renders Certificate manifests, the cert-manager webhook isn't
admitting requests yet (cold cluster), the apply fails, and
helm-controller retries until the webhook eventually comes up — burning
~2-3 minutes per affected package on cold installs.

The five PackageSources: `capi-operator`, `cozystack-engine`,
`ingress-nginx`, `kamaji`, and `rabbitmq-operator` (the platform
`cozy-system` set with webhook-cert consumers; not the apps layer).

In `cozystack-engine`, the dependency is added to both the `default` and
`oidc` variants — the `oidc` variant installs the same `cozystack-api`
and `lineage-controller-webhook` components that render Certificate
resources, so it had the same race.

## Origin

First commit lifted unchanged from #2619. Second commit (`oidc` variant)
added in response to review feedback.

### Release note

```release-note
NONE
```


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

## Summary by CodeRabbit

## Release Notes

* **Chores**
* Updated package source dependencies across multiple components to
ensure proper installation sequencing.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2726?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 22:44:21 +05:00
Aleksei Sviridkin 78b9740094 feat(dashboard): add cluster-usage RBAC for the new admin page (#2743)
## What this PR does

Adds a new `ClusterRole` `cozystack-dashboard-cluster-usage` and its
`ClusterRoleBinding` under `packages/system/dashboard/templates/`, plus
the matching helm-unittest fixture. The role grants the
`cozystack-cluster-admin` group cluster-wide read on `nodes`, `pods`
(get/list/watch) and on `metrics.k8s.io` `nodes` (get/list).

Companion to the upcoming UI work in cozystack/cozystack-ui#15 — a new
admin page `Console → Administration → Cluster Usage` that renders
cluster-scoped resource utilisation and a per-node table including
arbitrary extended resources (GPUs and other accelerators). The
page-side sidebar item is gated by a `SelfSubjectAccessReview` on `nodes
list`; without this binding the gate fails closed and the entry stays
invisible, which is the desired fallback for clusters that have not
adopted the new RBAC yet.

The built-in `edit` `ClusterRole` already bound to
`cozystack-cluster-admin` via `cozystack-dashboard-cluster-admin` does
not grant cluster-wide pod read or any access to `nodes`, so an explicit
grant is required for the page to render anything beyond an error.

This chart intentionally does not add permissions for deeper sources
(Prometheus, DCGM, HAMi). Those belong in their respective packages or
in operator-provided RBAC; the page detects them via discovery and gates
each section with its own per-source review.

### Screenshots

No UI in this PR — RBAC only.

### Release note

```release-note
feat(dashboard): grant cluster-admin read access to nodes, pods and metrics.k8s.io for the new Cluster Usage admin page
```


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

## Summary by CodeRabbit

* **New Features**
* Dashboard cluster usage monitoring now includes configured role-based
access control with appropriate read permissions for nodes, pods, and
metrics data.

* **Tests**
* Added comprehensive test suite to validate the dashboard's role-based
access control configuration, including verification of proper
permission bindings and authorization rules.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2743?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 18:55:49 +03:00
Myasnikov Daniil ea7a8c8675 test(e2e): quote HelmRelease names in kubectl wait
Both \${test_name} and \${component} are literal-safe today (no
whitespace, no glob characters), so this is a style-only change — but
quoting variable expansions in kubectl arguments matches the rest of
the file and removes a future-foot-gun if a test name ever grows a
hyphen-y suffix that interacts oddly with completion or globbing.

Scoped to the two HelmRelease wait lines flagged by CodeRabbit and
echoed by the human reviewer; the machinedeployment wait on the
preceding line keeps its existing style.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-27 20:36:10 +05:00
Myasnikov Daniil 64c443caf1 test(e2e): split local k8s_version declaration from yq invocation
\`local k8s_version=\$(yq ...)\` masks the exit status of the command
substitution — the assignment to a local variable always succeeds, so
a failing yq is swallowed. Declare the local first, then assign, so
non-zero yq exits propagate via shell errexit semantics if the script
ever runs under \`set -e\`.

Pure shell hygiene; no behavioral change on the success path. Surfaced
by CodeRabbit and confirmed by the human reviewer as non-blocking but
worth applying.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-27 20:36:10 +05:00
Myasnikov Daniil 8c6e149177 test(e2e): delete standalone ouroboros.bats now folded into kubernetes-latest
The previous commit (3c81261b) folded the ouroboros hairpin-NAT
reconciliation assertions into the kubernetes-latest tenant under
run-kubernetes.sh, but did not remove the standalone bats file.

The e2e workflow auto-discovers every hack/e2e-apps/*.bats and dispatches
test-apps-<name> via the wildcard rule in packages/core/testing/Makefile.
Leaving ouroboros.bats in the tree meant the assertions ran twice: once
on the standalone ~25m Kamaji-backed tenant and again as the folded
addon on the kubernetes-latest tenant — the opposite of the consolidation
the fold was intended to deliver.

The folded assertions in hack/e2e-apps/run-kubernetes.sh cover the same
surface (HR Ready, controller pod Running, coredns-custom rewrite-line
injection, and end-to-end DNS resolution from inside the tenant), so
the standalone file is dead weight after the fold.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-27 20:36:09 +05:00
Myasnikov Daniil 3ecc030a64 test(e2e): set keycloakInternalUrl when enabling OIDC
The dashboard gatekeeper (oauth2-proxy) does OIDC discovery against the
external host at container startup. The e2e sandbox uses
host=example.org which does not resolve, so the auth-proxy crashloops:

  Get "https://keycloak.example.org/realms/cozy/.well-known/openid-configuration":
    dial tcp: lookup keycloak.example.org: no such host

Under Flux v2.8 kstatus the gatekeeper Deployment then flips to
'Failed' (rather than the pre-v2.8 'InProgress') and stalls the
cozy-dashboard/dashboard HelmRelease, which in turn blocks
cozy-fluxcd/flux-plunger via dependsOn. Previous runs masked this with
an unrelated ImagePullBackOff on cozy-dashboard-console; with the
console image fixed the OIDC discovery failure now surfaces.

packages/core/platform/values.yaml:194-198 already declares
authentication.oidc.keycloakInternalUrl. When set, the dashboard
chart (packages/system/dashboard/templates/gatekeeper.yaml:59-66)
renders --skip-oidc-discovery plus explicit redeem/jwks/validate/
backend-logout URLs pointing at the in-cluster keycloak Service.
Browser-facing redirect-url and login-url stay on the external host
(they are not exercised by the e2e flow).

The patch sets the value alongside oidc.enabled in the same
kubectl-patch so the configmap and gatekeeper rollout happen in one
reconcile pass.

Observed on CI run 26142986969 (PR #2619).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 5915a579ee)
2026-05-27 20:36:09 +05:00
Myasnikov Daniil 68a9a6e5ad test(e2e): bump NFS pod wait to 10m to fit slow-path chain
Previous run failed kubernetes-latest (and then kubernetes-previous via
cascade) on the 5m budget for nfs-test-pod to reach Succeeded. The
chain is host linstor prime PVC + CDI import + tenant CSI attach +
busybox image pull + containerd CreateContainer (which occasionally
stalls past kubelet's 2m CRI deadline and retries with name-reservation
conflict). ~40s on the happy path, 6m+ in observed worst case.

When kubernetes-latest times out, its tenant cluster (2x 20Gi worker
PVCs) leaks because set -e exits before any cleanup. kubernetes-
previous then runs, adds another 40Gi, and the host tenant-quota
(100Gi) is exhausted -- every downstream app test (mariadb, mongo,
postgres, qdrant, redis, openbao, vminstance) fails with
ErrCreatingPVCPrime / exceeded quota.

Bumping the wait to 10m gives the slow path room. Headroom costs
nothing on green runs since the pod is busybox echo.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit c5cc7b60b8)
2026-05-27 20:36:09 +05:00
Myasnikov Daniil 04a54b01cf test(e2e): wait for tenant CSI HR Ready before NFS test
kubernetes-previous fails on the NFS pod step. From cozyreport + job log:

  - Pod scheduled to tenant worker
  - 7x FailedAttachVolume over 35s: CSINode does not contain driver
    csi.kubevirt.io (kubevirt-csi-node DaemonSet still rolling out)
  - Attach succeeds at T+1m08s after the driver registers
  - busybox pull queued ~1m25s, completes at T+2m48s
  - CreateContainer in containerd then stalls past kubelet's CRI
    deadline; retry trips "failed to reserve container name ... is
    reserved for <prev>" -- the 5m pod-Succeeded wait then expires.

Root cause: run-kubernetes.sh creates the NFS PVC+Pod before asserting
kubernetes-${test_name}-csi is Ready. That HR lives in the parent
cluster but installs cozy-kubevirt-csi-node into the tenant via
kubeConfig.secretRef; helm-controller walks kstatus on the tenant
DaemonSet using the tenant kubeconfig. HR Ready in parent therefore
implies kubevirt-csi-node Pods Ready on every tenant worker, which
implies csi.kubevirt.io is published in CSINode. The existing wait at
line 354 runs AFTER the NFS test, so it does not gate the race.

Move the wait earlier with a 10m budget. Post-NFS sibling waits stay
as is, so component coverage is unchanged.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 394b1b2b9a)
2026-05-27 20:36:09 +05:00
Myasnikov Daniil 5035ac1414 test(e2e): fold ouroboros assertions into kubernetes-latest
The standalone hack/e2e-apps/ouroboros.bats provisioned a full Kamaji-
backed tenant Kubernetes cluster (control plane + 2 CAPI workers + base
addons) just to flip `addons.ouroboros.enabled=true` on top, then ran
~15m of hairpin-NAT reconciliation assertions. Cluster shape was
identical to what hack/e2e-apps/run-kubernetes.sh already provisions for
kubernetes-latest, so the second Kamaji bringup was pure duplication —
roughly 25-45m of CI wall-time per run.

Add an `enable_ouroboros` 4th parameter to `run_kubernetes_test()`. When
"true", inject the ouroboros addon block into the Kubernetes CR's
addons map and run the post-cluster assertions inline:

- Wait for the ouroboros HR Ready in the host namespace.
- Verify coredns-custom ConfigMap exists in tenant kube-system.
- Wait for the ouroboros controller pod Ready.
- Apply an Ingress with a TLS host in tenant default namespace.
- Poll coredns coredns-custom for the rewrite line (5m budget).
- Resolve the hairpin host from inside the tenant via a netshoot pod
  and assert it points at the ouroboros-proxy ClusterIP.
- Clean up dnscheck pod + Ingress.

kubernetes-latest passes "true" (gets ouroboros coverage); kubernetes-
previous leaves it empty (control-plane-only smoke remains).

(The standalone ouroboros.bats was removed in the preceding commit
alongside an unrelated foundationdb chart fix — the git rm staging had
already happened when the foundationdb commit landed.)

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit b5c9c2ac90)
2026-05-27 20:36:09 +05:00
Myasnikov Daniil 535f881160 fix(platform): include cert-manager in cozystack-engine oidc variant
The oidc variant installs system/cozystack-api and
system/lineage-controller-webhook, both of which render
cert-manager.io/v1 Certificate resources. Add cozystack.cert-manager to
the oidc variant's dependsOn so it matches the default variant and
webhook-cert consumers do not race the cert-manager controller on cold
installs.

Address review feedback from gemini-code-assist and coderabbitai on
packages/core/platform/sources/cozystack-engine.yaml.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-27 20:35:49 +05:00
Myasnikov Daniil 69cb7b5f18 fix(platform): add cert-manager dependency to webhook-cert consumers
Five PackageSources whose charts apply cert-manager Certificate CRs and
mount the resulting Secret on a webhook Deployment lacked an explicit
dependsOn: cozystack.cert-manager. Under Flux v2.8 helm-controller v1.5.0
kstatus, the dependent Deployment trips progressDeadlineSeconds (10m
default) when cert-manager controller isn't running yet, and the HR is
failed early. Repeatedly observed on PR #2619 with capi-operator,
kamaji, rabbitmq-operator, cozystack-api (cozystack-engine), and
ingress-nginx all cascading to InstallFailed at ~12-15m into a fresh
install.

The mariadb-operator, kubeovn-webhook, snapshot-controller, etcd-operator,
postgres-operator, mongodb-operator, victoria-metrics-operator, linstor,
and linstor-scheduler PackageSources already had the dependency, which
is why they weren't in the failure set.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 0fc0796b19)
2026-05-27 20:35:49 +05:00
Arsolitt d7934b89d0 refactor(postgres): move version param out of the TLS section
version is a general deployment parameter, not a TLS setting; relocate
its @param/@enum block back into Common parameters so the generated
parameter reference groups it correctly instead of under TLS.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 17:10:46 +03:00
Arsolitt 18de2119d9 docs(postgres): describe tls.enabled default as omitted, not null
The generated JSON schema types tls.enabled as a scalar boolean, which
rejects an explicit null value; describe the tri-state unset case as
"omitted" so the wording matches what the schema actually accepts.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 17:08:56 +03:00
Arsolitt 532fff8a8c test(kafka): assert scalar boolean tls.enabled type in rd-schema check
The drop-null refactor switched the embedded openAPISchema to a scalar
"boolean" type (the ["boolean","null"] array form breaks the
ApplicationDefinition CRD's own unmarshal of the embedded schema). The
rd-schema bats guard still asserted the old array form and started
failing; align it with the scalar type the chart now generates.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 16:21:46 +03:00
myasnikovdaniil 628ebce653 feat(platform): add deletion-protection guardrail via ValidatingAdmissionPolicy (#2650)
## What this PR does

Adds a `ValidatingAdmissionPolicy` that blocks `DELETE` on critical
platform
objects labeled `platform.cozystack.io/no-delete=true`. Native,
in-process CEL
evaluation — no webhook DaemonSet, no Service, no TLS, no image to
maintain.
Requires Kubernetes 1.30+.

This is an alternative to the closed #2402 (admission webhook approach).

### Protected objects (labeled in this PR)

| Resource | Kind | Source |
|---|---|---|
| `cozy-system` | Namespace |
`packages/core/installer/templates/cozy-system-{namespace,labels}.yaml`
|
| `tenant-root` | Namespace |
`packages/system/cozystack-basics/templates/tenant-root.yaml` |
| `tenant-root` | HelmRelease (ns: tenant-root) | same |
| `cozystack-version` | ConfigMap (ns: cozy-system) |
`packages/core/platform/templates/cozystack-version.yaml` |
| `cozystack-packages` | OCIRepository (ns: cozy-system) |
`packages/core/platform/templates/repository.yaml` |
| `letsencrypt-prod`, `letsencrypt-stage`, `selfsigned-cluster-issuer` |
ClusterIssuer |
`packages/system/cert-manager-issuers/templates/cluster-issuers.yaml` |
| `linstorcluster` | LinstorCluster |
`packages/system/linstor/templates/cluster.yaml` |
| `packages.cozystack.io`, `packagesources.cozystack.io` | CRD | label
stamped in `internal/crdinstall/install.go` |

### Bypass

```bash
kubectl label <kind> <name> platform.cozystack.io/no-delete-
kubectl delete <kind> <name>
```

### Why VAP instead of a webhook

Same outcome as #2402 but with zero runtime infrastructure: no
DaemonSet, Service,
TLS certificate, or image. The kube-apiserver evaluates the CEL
`expression: "false"` in-process and denies because `validationActions:
[Deny]`.
`objectSelector` on the binding scopes evaluation to labeled objects
only — every
other DELETE in the cluster is unaffected.

### Release note

```release-note
feat(platform): add deletion-protection guardrail (ValidatingAdmissionPolicy)
blocking DELETE on cozystack platform objects labeled
`platform.cozystack.io/no-delete=true`. Bypass by removing the label first.
Requires Kubernetes 1.30+.
```


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

* **New Features**
* Deletion-protection guardrail: objects labeled
platform.cozystack.io/no-delete=true are denied DELETE requests; removal
of the label allows deletion.

* **Chores**
* Platform migration to v43 backfills the no-delete label onto system
resources and namespaces.

* **Tools**
* Added an "unprotect" teardown utility to remove the no-delete label
for uninstall/disaster recovery.

* **Documentation**
* Helm NOTES now warns when the guardrail is inactive due to missing
cluster API.

* **Tests**
  * New tests and e2e checks validate deletion-protection behavior.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2650?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 17:25:58 +05:00
Timofei Larkin 72c4eb9c08 feat(backups): implement Job backup strategy (#1721)
## What

Implements the `strategy.backups.cozystack.io/Job` backup strategy — a
generic, application-agnostic driver that runs a user-supplied
Kubernetes Job to create (and restore) a backup.

## Background

This PR began as a draft written against an earlier shape of the backups
API. The API has since moved to a `BackupClass`-based dispatch model,
which broke the original draft on three counts:

- `BackupJob.Spec.StrategyRef` and `BackupJob.Spec.StorageRef` were
removed; the strategy and its parameters are now resolved from a
`BackupClass` into a `ResolvedBackupConfig`.
- Strategy handling moved from standalone per-strategy reconcilers to a
dispatch model: `BackupJobReconciler.Reconcile` /
`RestoreJobReconciler.Reconcile` resolve the strategy and switch into
per-strategy methods (`reconcileVelero`, `reconcileCNPG`,
`reconcileAltinity`, …).
- The draft's template context (`.Values`, `.Storage`) diverged from the
convention the other strategies adopted, and its `.Storage` half
targeted a storage API that does not exist.

## What changed

- **`reconcileJob` / `reconcileJobRestore`**
(`internal/backupcontroller/jobstrategy_controller.go`): replaces the
standalone `BackupJobStrategyReconciler` with the two dispatch methods
the `BackupJob` / `RestoreJob` reconcilers call. The Job strategy is the
generic counterpart of the Altinity driver — same lifecycle (render
`PodTemplateSpec` → run a one-shot `batch/v1.Job` → poll its terminal
condition → emit a `Backup`), minus the ClickHouse-specific
`applicationRef` gate, so it runs for any application. Shared package
helpers (`renderJobTemplate`, `jobConditionState`,
`getApplicationUnstructured`, status helpers, deterministic Job naming,
controller refs) are reused.
- **Template context** follows the in-tree convention used by the other
drivers: `.Application` (live application object), `.Release.Name` /
`.Release.Namespace`, `.Mode` (`backup` / `restore`), `.Parameters`
(resolved `BackupClass` parameters), and `.Backup` (restore runs only).
Strategy parameters round-trip through the `Backup`'s `driverMetadata`
so a later restore re-renders against the same values.
- **Restore** re-renders the same template with `.Mode == "restore"`.
Cross-namespace restore is unsupported (the RestoreJob target reference
is namespace-local), matching the Altinity driver.
- **`JobSpec.Template` doc + regenerated CRD**: the field doc previously
advertised `.Values`; it now documents the real context, and
`strategy.backups.cozystack.io_jobs.yaml` is regenerated to match (the
change is limited to that field's description).
- **Tests** (`jobstrategy_controller_test.go`): backup-Job creation /
args / labels / status, completion → `Backup` artifact with parameter
round-trip, Job-failure surfacing, missing-strategy terminal failure,
restore into the target namespace, restore completion, and the
parameter-prefix unit. A deliberately generic application kind pins that
the driver never gates on `applicationRef.kind`.

No RBAC changes are required: the controller already holds
`strategy.backups.cozystack.io/*`, `batch/jobs`, `apps.cozystack.io/*`,
and `backups` permissions.

## Notes for reviewers

- Backup and restore share one template, so a strategy author's
`PodTemplateSpec` must branch on `.Mode` to do anything
restore-specific. This matches the existing convention; worth confirming
it's the intended contract for the Job strategy's docs/examples.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


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

* **New Features**
* Implemented full backup/restore job strategy with deterministic Job
creation, idempotent execution, and status/condition updates; added demo
example scripts and reusable shell helpers for a NATS JetStream
backup/restore workflow.

* **Documentation**
* Clarified Job template rendering context and listed supported template
variables; added README and step-by-step scenario docs for the NATS
example.

* **Tests**
* Added comprehensive unit tests covering job creation,
completion/failure handling, parameter extraction, and unmappable-target
failure cases.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/1721?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 14:45:19 +04:00
Arsolitt 0c7c39333a refactor(postgres): drop null from tls.enabled schema, use kindIs invalid pattern
Remove the jq post-processing patch that widened tls.enabled type to
["boolean","null"] in values.schema.json and the openAPISchema embedded
in the cozyrds CRD. Plain "type":"boolean" is emitted directly by
cozyvalues-gen and is the correct form.

The cozystack-api server unmarshals openAPISchema into
apiextv1.JSONSchemaProps whose Type field is a single string; an array
value silently zeros the field and disables server-side defaulting for
the entire app.

Tri-state semantics are preserved on the Helm template side via
kindIs "invalid": omitting tls.enabled triggers the external-based
fallback; null is no longer a valid input and is rejected by the schema.

Remove two helm-unittest cases that injected tls.enabled: null (now
intentionally rejected) and update the suite comment to match.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:51 +03:00
Arsolitt 272c417888 fix(postgres): drop CA Secret from cozyrds — tenant retrieves ca.crt from user-credentials Secret
The dashboard RBAC intentionally does not grant access to the operator-managed
<release>-ca Secret (it contains ca.key, which would leak the private key).
Advertising it in cozyrds secrets.include created a UX gap: the dashboard
showed a Secret the tenant could never read.

CNPG bundles ca.crt in every user-credentials Secret it creates, so tenants
already have the trust anchor they need via the <release>-credentials Secret
which is covered by RBAC. Update README and the db.yaml comment to point there.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:50 +03:00
Arsolitt f1731869f2 build(postgres): assert schema shape before patching tls.enabled.type
Add a precondition that fails early if cozyvalues-gen no longer emits
tls.enabled.type as the bare "boolean" string the patch step expects.
Without the guard, a generator output that drops or moves the field
would silently create it via the assignment, and the post-patch
equality check would still pass against the synthetic value.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:50 +03:00
Arsolitt bc6c3f2611 docs(postgres): rewrite TLS section for operator-managed CNPG flow
The README still described the chart-side cert-manager chain that was
replaced when the chart switched to CNPG operator-managed TLS via
spec.certificates.serverAltDNSNames. Rewrite the section to explain the
actual cert ownership boundary (CNPG owns the chain, chart only injects
the external hostname), the trust anchor location (operator-managed
<release>-ca Secret, key ca.crt), and the chart-side semantics of the
tri-state tls.enabled flag. The values.yaml @field comment is updated
in the same direction, with regenerated schema/types.go/cozyrds.

Clarifies that tls.enabled=false only skips the chart-side SAN
injection - CNPG retains its built-in TLS on the wire regardless of
this flag. To force PostgreSQL TLS off entirely, the caller would set
postgresql.parameters.ssl="off" at the CNPG layer.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:50 +03:00
Arsolitt df4f6522c5 chore(postgres): tidy go.mod to demote unused direct dependency to indirect
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:50 +03:00
Arsolitt 37347e23e0 test(postgres): align TLS suite with operator-managed mode
Drop the templates/certmanager.yaml suite entirely (the file no longer
exists) and rewrite the spec.certificates assertions to match the new
operator-managed flow: certificates block emitted only when both TLS is
on AND external is true, with serverAltDNSNames carrying just the
external hostname.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:50 +03:00
Arsolitt c7bc56acbb fix(postgres): switch to operator-managed CNPG TLS with extra-SAN injection
CNPG admission webhook rejects spec.certificates that combines a
serverTLSSecret with serverAltDNSNames, and the operator's internal
client-CA generation step expects ca.key on the secret bound to
serverCASecret — a field cert-manager does not produce, since it stores
the private key as tls.key. The previous chart configuration triggered
exactly this path and left the Cluster stuck on
"generating client CA certificate: missing ca.key secret data".

Drop the chart-rendered cert-manager Issuer/Certificate chain and the
spec.certificates.serverCASecret/serverTLSSecret references. CNPG now
auto-generates the self-signed CA and signs server, replication, and
client certs from it, producing the expected per-purpose secrets
(<release>-ca, <release>-server, <release>-replication). The operator's
default SAN list already covers the three built-in services
(rw, r, ro) in four DNS forms each.

The chart still needs to inject the external LoadBalancer hostname when
external=true, since that name lives outside the operator's auto-derived
set. Emit spec.certificates.serverAltDNSNames with that single entry
under that condition only.

End-to-end test on dev13: Cluster reaches "healthy state", probe pod
with PGSSLMODE=verify-full against the operator-managed CA succeeds and
SHOW ssl reports on.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:49 +03:00
Arsolitt d918b25d24 test(postgres): add TLS test coverage
- Add helm-unittest suite covering tri-state TLS (nil/false/true), CA/leaf cert
  properties, external SAN injection, and cert-manager resource absence when disabled

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:49 +03:00
Arsolitt f204345e8f feat(postgres): implement TLS in Helm templates
- Add _tls.tpl helper with tri-state resolution logic (nil/false/true)
- Add certmanager.yaml to provision CA and leaf Certificate resources
- Wire TLS listener configuration into db.yaml when TLS is enabled

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:49 +03:00
Arsolitt 114a69d3ce feat(postgres): add TLS schema and generated artifacts
- Extend PostgresSpec with nullable Tls.Enabled (*bool) for tri-state control
- Regenerate deepcopy and update go.mod with k8s.io/utils as direct dep
- Add TLS fields to values.yaml, values.schema.json (with nullable support)
- Update README and Makefile; propagate TLS field to cozyrds postgres.yaml

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:26:49 +03:00
Arsolitt 87ad834b5d refactor(qdrant): switch CA and leaf private keys to ECDSA P-256
RSA keys are unnecessarily large for internal PKI. ECDSA P-256 provides
equivalent security with smaller key material, faster TLS handshakes,
and smaller certificates — beneficial for high-frequency p2p connections.

CA retains rotationPolicy: Never (root CA stability).
Leaf uses rotationPolicy: Always (new key on every renewal).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:26 +03:00
Arsolitt 51b5d270a7 refactor(qdrant): use kindIs invalid pattern for tri-state tls.enabled detection
Replace the hasKey + index approach with the canonical kindIs "invalid"
check. This is the standard cozystack pattern for detecting an unset
*bool field, making the helper consistent with other charts in the
project.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:26 +03:00
Arsolitt c26b51a2b2 feat(qdrant): trigger pod restart on TLS Secret rotation via Reloader annotation
cert-manager rotates the leaf cert approximately every 11 months
(renewBefore: 720h, duration: 8760h). Qdrant reads tls.cert/key/ca_cert
at process start and does not watch for file changes, so without a
restart trigger TLS would silently break at the renewal boundary.

Add a targeted Reloader annotation (secret.reloader.stakater.com/reload)
pointing at the specific TLS Secret. This uses the targeted form, not the
auto-watch form, to limit scope to the TLS Secret only. Reloader is
installed cluster-side at packages/system/reloader.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:26 +03:00
Arsolitt 055085a6f3 fix(qdrant): add per-replica short-form SANs for p2p TLS hostname verification
Qdrant's upstream chart constructs bootstrap/uri using short-form pod
hostnames (<release>-N.<release>-headless). The existing wildcard SAN
only covered FQDN form, causing TLS handshake failures in multi-replica
clusters when ca_cert peer verification is enabled.

Add a range loop that emits three SAN variants per replica:
- short form: <release>-N.<release>-headless
- svc form: <release>-N.<release>-headless.<ns>.svc
- FQDN form: <release>-N.<release>-headless.<ns>.svc.<cluster-domain>

Scale-up beyond initial replicas requires cert re-issue; Reloader
(see next commit) will restart pods to pick up the new cert.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:26 +03:00
Arsolitt b610bfa1a3 refactor(qdrant): extract TLS enablement logic into _helpers.tpl
Address review feedback: the TLS-enablement block was duplicated in both
qdrant.yaml and certmanager.yaml. Introduce a named helper template
"qdrant.tls.enabled" in _helpers.tpl and replace both inline copies with
a single include call.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:25 +03:00
Arsolitt 17089eec0a chore(qdrant): regenerate codegen deepcopy artifacts
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:25 +03:00
Arsolitt 8cd989e1f6 test(qdrant): add TLS test coverage
- Add helm-unittest suite for cert-manager resources covering all TLS mode combinations
- Add helm-unittest suite for Qdrant StatefulSet verifying TLS volume mounts and p2p behavior
- Cover explicit enabled/disabled and external-inherited cases; document p2p standalone behavior

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:25 +03:00
Arsolitt cdb03a9929 feat(qdrant): implement TLS in Helm templates
- Add certmanager.yaml to provision cert-manager Certificate and Issuer for p2p and client TLS
- Extend qdrant.yaml to mount TLS secrets and configure Qdrant with TLS settings when enabled
- TLS activation follows a tri-state: explicit true/false or inherit from cluster external setting

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:25 +03:00
Arsolitt 612274693f feat(qdrant): add TLS schema and configuration artifacts
- Extend QdrantSpec with tls.enabled tri-state bool and expose it via values.yaml and JSON schema
- Update cozyrds integration to pass TLS config to the operator
- Add test: Makefile target and TLS parameter documentation to README

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:20:25 +03:00
Arsolitt d9a11a2c04 refactor(nats): drop null from tls.enabled schema, use kindIs invalid pattern
Remove the jq post-processing step from the Makefile that was patching
tls.enabled to ["boolean","null"]. The plain "type":"boolean" form that
cozyvalues-gen emits is correct: cozystack-api unmarshals openAPISchema into
apiextv1.JSONSchemaProps where Type is a single string, so an array type
silently zeroes out specSchema and disables server-side defaulting.

Tri-state semantics (nil / true / false) are preserved entirely on the
Helm side via kindIs "invalid" guards in the templates.

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:44 +03:00
Arsolitt 63cf7a3f14 refactor(nats): switch CA and leaf private keys to ECDSA P-256
RSA is slower and produces larger keys and certificates than ECDSA. P-256
provides equivalent security with smaller cert size and faster TLS handshakes,
which matters for frequent cluster-internal reconnections.

CA retains rotationPolicy: Never; leaf retains rotationPolicy: Always.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt dd18a80b8b fix(nats): force cluster route useFQDN when TLS enabled to match cert SAN wildcard
The upstream NATS chart defaults routeURLs.useFQDN to false, which causes
peer routes to resolve as tls://<release>-N.<release>-headless:6222 (short
form). The cert SAN wildcard *.<release>-headless.<ns>.svc.<cluster> matches
FQDN forms but not the short form, so inter-pod cluster traffic runs in
encryption-only mode without hostname verification.

Setting useFQDN: true when TLS is enabled makes peer routes use the FQDN
form that matches the wildcard SAN, restoring authenticated TLS for cluster
routing.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt 261d8486af fix(nats): add precondition check before schema patch in Makefile
Guard the tls.enabled nullable patch with an existence check so that
generator drift (cozyvalues-gen changing its output shape) is detected
immediately rather than being masked by jq silently creating the path.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt 85f9cc9727 chore(nats): regenerate codegen artifacts
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt a3b8aa0893 test(nats): add TLS test coverage
- certmanager_test.yaml: verify Certificate and Issuer resources are rendered correctly for all TLS scenarios
- nats_tls_test.yaml: verify nats.yaml mounts, config blocks, and tri-state TLS logic across enabled/disabled/auto modes

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt c308374b78 feat(nats): implement TLS in Helm templates
- Add _tls.tpl helper with tri-state resolver (nil auto-detects, true/false explicit)
- Add certmanager.yaml to provision Certificate and Issuer resources for client, server, and leaf certs
- Mount CA and certs in nats.yaml with correct EKUs for cluster mTLS and external client auth

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:43 +03:00
Arsolitt 81239b332d feat(nats): add TLS schema and generated artifacts
- Extend values.yaml and JSON schema with tls.enabled (*bool tri-state) and tls.secretName fields
- Update Go types and regenerate README for TLS field
- Enable TLS toggle in nats-rd cozyrds config
- Wire helm-unittest into Makefile as test target

Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:19:42 +03:00
Arsolitt 244f19afa3 refactor(kafka): drop null from tls.enabled schema, use kindIs invalid pattern
cozyvalues-gen v1.5.0 natively emits *bool for @param {*bool} fields,
so the post-generate sed patch for types.go and the jq patch forcing
["boolean","null"] on values.schema.json are no longer needed. Remove
both patches from the Makefile.

Regenerate values.schema.json and cozyrds/kafka.yaml so tls.enabled
carries "type":"boolean" instead of ["boolean","null"]. The single-string
type is required by apiextv1.JSONSchemaProps, which cozystack-api uses for
server-side defaulting; the array form silently disabled defaulting for
the Kafka app.

Refactor the inline tri-state TLS guard in kafka.yaml to use the canonical
kindIs "invalid" pattern and extract it into a dedicated _tls.tpl named
template (kafka.tls.enabled). Add three explicit test cases (J-1, J-2, J-3)
covering the inherit-from-external path, the explicit-false override, and
the external-gate suppression cases.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
2026-05-27 11:16:42 +03:00