Commit Graph
15 Commits
Author SHA1 Message Date
Aleksei Sviridkin b201a0f878 fix(api): leave failed golden images out of the image options
The image option source lists every vm-default-images-* PVC in
cozy-public. CDI creates that PVC as soon as an import starts, so a
golden whose DataVolume ended in phase Failed is still offered, and a
disk cloned from it never populates: the user picks an image the
platform just suggested and ends up with a disk that never becomes
usable.

Read the DataVolumes next to the PVCs and drop the names whose import
Failed. Filtering beats marking: the option can never be used, and
OptionItem has no disabled state to show it in. A golden still
importing stays, since CDI holds a clone until the source is
populated, and so does a PVC with no DataVolume. When the DataVolumes
cannot be read, without CDI or without the grant, the PVCs go out
unfiltered: the Option list drops a source whose provider errors, and
an empty picker would be a regression rather than a safeguard.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-29 12:40:36 +03:00
Aleksei Sviridkin f301cb551b feat(api): add configurable resources and replicas for cozystack-engine components (#2956)
## What this PR does

Exposes standard Helm `resources` values for the three core workloads in
the `cozystack-engine` package, and makes `cozystack-controller`
replicas configurable (was hardcoded to `1`).

**Changes by component:**

| Component | `resources` | `replicas` |
|---|---|---|
| `cozystack-api` | ✅ NEW (`cozystackAPI.resources`) | already
configurable |
| `cozystack-controller` | ✅ NEW (`cozystackController.resources`) | ✅
NEW (`cozystackController.replicas`, default `1`) |
| `lineage-controller-webhook` | ✅ NEW
(`lineageControllerWebhook.resources`) | already configurable |

Each template uses `{{- with .Values.xxx.resources }}` so the
`resources:` block is only rendered when values are provided. Default
`resources: {}` produces no output, so resource requests/limits stay
opt-in.

**Leader election:** `cozystack-controller` now always runs with leader
election — the chart passes `--leader-elect` unconditionally and ships a
namespaced `Role`/`RoleBinding` for the Lease in `cozy-system` (where
controller-runtime creates it). Raising `cozystackController.replicas`
therefore adds standbys instead of a second active writer over the
reconcilers that patch HelmReleases, Gateways, Certificates,
ResourceQuotas and Secrets; this holds whatever `replicas` is. A restart
can pause reconciliation for up to the Lease duration (15s by default),
because `LeaderElectionReleaseOnCancel` is off. With `replicas: 1` the
change for an existing install is the flag plus the two RBAC objects.

**Why:** Without these values, operators cannot set QoS guarantees and
must resort to `kubectl patch`, Flux Kustomize SSA patches, or Kyverno
mutations — all fragile under Helm upgrades.

Closes #2955

### Release note

```release-note
feat(api): add configurable `resources` (requests/limits) for cozystack-api, cozystack-controller, and lineage-controller-webhook.
`cozystack-controller` replicas is now configurable via `cozystackController.replicas` (default: 1).
The `cozystack-controller` Deployment now always runs with leader election and creates a Lease in `cozy-system`, so raising `replicas` adds standbys rather than a second active controller; a restart can pause reconciliation for up to the Lease duration (15s by default).
An empty `resources: {}` renders no resources block, so requests and limits remain opt-in.
```


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

## Summary by CodeRabbit

* **New Features**
* Container resource requests and limits can now be configured for the
API, controller, and webhook services; defaults remain unchanged.
* The controller replica count is configurable, and leader election is
enabled for multi-replica operation.
* Controller leader-election access is limited to the `cozy-system`
namespace.

* **Documentation**
  * Documented the webhook resource configuration option.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-28 20:56:43 +03:00
Nikita Aboltin 8732dc96cf feat(api): add configurable resources and replicas for cozystack-engine components
- cozystack-api, cozystack-controller, lineage-controller-webhook: conditional
  resources block via `{{- with .Values.*.resources }}`, defaulting to `{}` so the
  default render stays unchanged
- cozystack-controller: replicas comes from values (was hardcoded 1); the chart
  now passes --leader-elect unconditionally and grants the Lease verbs through a
  namespaced Role and RoleBinding in cozy-system (the Lease lives in the manager's
  own namespace), so raising replicas adds a standby instead of a second active
  writer over the reconcilers that patch HelmReleases, Gateways, Certificates,
  ResourceQuotas and Secrets

Signed-off-by: Nikita Aboltin <aboltin.nikita@rwb.ru>
2026-09-28 15:19:45 +03:00
IvanHunters aa20495df3 feat(marketplace): register a tapped repository's apps on connect
Connecting a marketplace repository materialized its PackageSource but created no Package, so its ApplicationDefinitions never rendered and its apps never appeared in the dashboard catalog until a manual `cozypkg add` the dashboard cannot drive (the package list is computed from the ApplicationDefinitions, which do not exist yet).

The tap materializer now creates a tap-managed registration Package for each materialized PackageSource, the way the platform ships a Package per built-in app, so the ApplicationDefinition registrations deploy on connect and the apps become browsable; the app components carry no install block and are still instantiated per user resource.

Privileged install components of an unconfirmed tap auto-registration (a Package that still carries the marketplace-tap label) are refused on several best-effort layers: the Package reconciler skips their HelmRelease and prunes a lingering one at the install site, and the PackageSource reconciler withholds their ExternalArtifact so the helm-controller has no privileged content to deploy, re-running on confirmation via a Package watch. `cozypkg add --allow-privileged` sheds the label as an explicit ownership handover, which is the confirmation; a platform Package never carries the label. A benign component flipped to privileged in a new revision can still be materialized by source-watcher before these layers observe the flip; closing that race fully requires decoupling the catalog registration from install and is tracked in issue #4359.

`cozypkg untap` and the dashboard disconnect remove the registration Package (de-registering the apps) under a UID + ResourceVersion precondition, so a concurrent `cozypkg add` handover is never clobbered; running instances are left but unmanaged. `cozypkg add` re-opens a tap auto-registration for variant selection and hands it over. The cozystack-api ClusterRole gains delete on packages for the disconnect path.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-21 21:57:04 +03:00
Aleksei Sviridkin d0fce925cf fix(platform): declare the injected _cluster values in system charts (#4102)
## What this PR does

Ten charts under `packages/system` read `_cluster`, and `bucket` and
`monitoring` also read `_namespace`. The platform injects both through
the `cozystack-values` Secret, so installs never notice. Rendering a
chart on its own does: `helm template packages/system/kubevirt` dies on
`index of untyped nil` before it emits anything, and the `_namespace`
readers die on `nil pointer evaluating interface {}.host`.

Each of the ten now declares the keys it reads, with an empty default.
Injected values merge over chart defaults, so an installed release
renders exactly what it rendered before. Checked per chart against a
realistic injected `_cluster`/`_namespace`: six came out byte-identical,
and the other four differ only in `randAlphaNum` secrets, which also
differ between two renders of the same tree.

The declaration on its own is not worth much, so every chart also gets a
suite that renders with nothing injected. The existing suites all set
`_cluster` themselves and cannot see whether `values.yaml` still
declares it. Drop a declaration and exactly that chart's suite goes red,
with the error its comment quotes; I ran that for all twelve. None of
the new suites carries a suite-level `templates:` list, on purpose: that
list narrows what helm-unittest renders, and would hide the same
breakage in a template added later.

`kubevirt-cdi` had no `tests/` and no `test:` target, so it gets both.
`etcd-operator` reads `_cluster` too and stays as it is, since its one
call site already guards with `| default dict`. `cozystack-basics`
already declared the key.

Closes #4030.

### Not in this PR

No CI job runs `helm lint` or a standalone `helm template`, which is why
none of this was red. A gate is worth proposing separately. Its value is
narrow though: `_cluster` is always populated on a real install, and
eight of these ten already had a suite rendering the affected template
with `_cluster` supplied. The two with nothing were `kubevirt`, whose
`vm-exportproxy-*` templates no test touched, and `kubevirt-cdi`, which
had no tests at all.

`helm lint` still exits 1 on `keycloak` and `monitoring` with `chart
metadata is missing these dependencies: cozy-lib`. Both carry a
`charts/cozy-lib` symlink that `Chart.yaml` never declares. That one is
not a two-chart item: 82 charts across
`packages/{system,apps,extra,core}` fail lint the same way, which is
invisible because no job runs `helm lint`. Separate defect, untouched
here.

Fifteen charts outside `packages/system` break the same way:
`apps/{bucket,foundationdb,harbor,kubernetes,mongodb,nats,opensearch,postgres,vpn}`
and `extra/{bootbox,etcd,gateway,info,ingress,seaweedfs}`. `cozy-lib`'s
own helpers already guard every access with `| default dict`, so routing
those reads through the library would close the class instead of
patching fifteen more values files.

`kubevirt`'s suite only pins that the routes stay absent with nothing
injected. Nothing pins that they show up once `_cluster` selects the
service.

### Screenshots

No UI change: the diff is ten `values.yaml` files, ten helm-unittest
suites, and one package `Makefile`.

### Downstream repositories

Walked the trigger map in `docs/agents/contributing.md` file by file
against this diff. No package is added, renamed or removed under
`packages/apps` or `packages/extra`;
`packages/core/platform/values.yaml` is untouched; no variant, bundle,
component, release asset, namespace or `ApplicationDefinition` semantic
changes; no `values.schema.json` and no version enum moves, so the
provider's hand-written schemas and defaults are unaffected; nothing
under `hack/` moves and no existing make target changes behaviour. The
one `Makefile` edit adds a `test:` target to
`packages/system/kubevirt-cdi`, matching the target the other nine
already have, so `make generate` and the ccp skills that drive it see
nothing new.

- [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:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(platform): declare the platform-injected `_cluster` and `_namespace` values in the ten system charts that read them, so each chart renders with `helm template` outside a cluster instead of failing on a nil map. No change to what an installed release renders.
```


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

## Summary by CodeRabbit

- **Bug Fixes**
- Improved standalone Helm chart rendering when platform-provided values
are unavailable.
- Prevented rendering failures across system charts, including bucket,
dashboard, Keycloak, monitoring, KubeVirt, and related components.

- **Tests**
  - Added comprehensive standalone rendering coverage for system charts.
- Added validation for expected ingress, gateway, TLS route, and
resource output under different configuration scenarios.
  - Added a chart test command for KubeVirt CDI.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 02:50:43 +04:00
Andrei Kvapil 54621bc046 feat(migration): tenant self-service VM import from VMware vSphere
Hidora's customers are leaving VMware now, and a tenant has to be able to
import their own machines without a platform administrator. The chart
approach could not express that: a tenant can neither create the Secret
holding vCenter credentials nor name the VDDK image, so both had to come
from somewhere they cannot reach.

Two CRDs in a new `forklift.cozystack.io` group, split by lifecycle. A
VMImportSource is a long-lived connection -- type, endpoint, credentials,
readiness -- and a VMImportTask is the one-shot operation that names
machines and produces VMDisks and VMInstances. The outputs carry no owner
reference back to the task, so deleting a finished import leaves the
machines alone. Konveyor Forklift does the transfer underneath; the
controller renders its objects, mirrors its verdicts, and hands each
finished volume into a VMDisk without copying it.

What a live vCenter migration forced out, and no amount of source reading
would have:

- A guest copied verbatim keeps the drivers it had under VMware and no
  virtio, so on a virtio disk it does not fail the import -- it succeeds
  and then does not boot. Imported disks go on SATA.
- VDDK connects straight to the ESXi host at the address vCenter
  advertises, which is routinely unreachable, and on the first cluster
  collided with the Service CIDR. `spec.hosts` redirects per host.
- Firmware must be carried across or a UEFI guest imports as BIOS and
  never boots (stacked on #3002).
- Deleting a task mid-transfer orphaned its DataVolume; a finalizer now
  removes the engine's volumes and leaves the outputs.

The console gains a Migration section: connections are editable, and the
VM field is a picker built from a machine list the controller publishes,
since the aggregated API cannot reach the inventory and should not hold a
source's credentials to try. Two general mechanisms fall out of that --
an option source may take an argument, and one named in a CRD annotation
may reference a sibling field.

Design: cozystack/community#62. Docs: cozystack/website#673.

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-09-07 17:03:44 +02:00
IvanHunters 0c90abcedd test(rbac): cover the marketplace tap grants in the pinned RBAC suites
The tap flow added a packagesources delete grant and an ocirepositories read
grant to the cozystack-api ClusterRole, and taps to the tenant core.cozystack.io
grants. Both suites pin those lists on purpose, so move the count bound and
assert the new grants by name instead of leaving them uncovered.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-07 10:58:39 +03:00
Aleksei Sviridkin eef5fe6908 fix(platform): declare the injected _cluster values in system charts
Ten system charts cannot be rendered on their own. They read the
platform-injected _cluster channel, and two of them (bucket,
monitoring) also read _namespace, without declaring either key in
values.yaml. On a cluster the platform supplies both through the
cozystack-values Secret, so installs are unaffected, but rendering a
chart by itself dies before a single template is emitted: on "index of
untyped nil" for a missing _cluster, and on "nil pointer evaluating
interface {}.host" for a missing _namespace.

Declare the keys with empty defaults. Helm merges chart defaults under
the supplied values, so an injected _cluster still wins and rendered
output does not change. Each of the ten also gets a suite that renders
with nothing injected; drop a declaration again and that chart's suite
goes red.

This makes `helm template` succeed for all ten. `helm lint` also
reports a missing cozy-lib dependency on keycloak and monitoring,
which is a separate pre-existing defect and survives this change.

etcd-operator reads _cluster too and stays undeclared here: its single
call site already guards with `| default dict`, so the chart renders
standalone as it is.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-07 02:23:47 +03:00
Aleksei Sviridkin 9d1d6808db test(seaweedfs,api): bound both Roles against a respelled rule
Each suite forbade a rule by naming it in full, and helm-unittest compares
every field it is handed by value equality, so one respelling walks past.

For seaweedfs that is not theoretical. Ungating the cnpg block and adding a
verb to the rule (get,list,delete rather than get,delete) is the same grant
and more, and the guard stayed green while a Client instance held delete on
another app's Postgres cluster. Bound the Role by shape: a Client renders
exactly two rules, both in the core group, and the cnpg grant is gated
between them, so ungating it adds a third whatever its spelling. The rules
themselves stay unpinned because the secret list is long and changes on its
own schedule.

For cozystack-api, add a count bound beside the existing spelling assert
rather than in place of it. The two catch different things and neither
covers the other: templates/rbac.yaml carries no template directives, so
the list is pinned at exactly sixteen rules, while a grant swapped in for
an existing one leaves the count where it was and only the named-rule
assert sees it. The count is pinned from both sides — a dropped grant is
the failure this suite is about, since the apiserver then returns forbidden
and List silently drops the dropdown. Measured on all three: append,
delete, and swap.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-08-08 03:09:47 +03:00
Aleksei Sviridkin bf14a73b39 feat(api): add SecurityGroup network policy resource (sdn.cozystack.io) (#2922)
## What this PR does

Introduces a tenant-facing `SecurityGroup` resource in a new
`sdn.cozystack.io/v1alpha1` API group, served by the Cozystack
aggregated API server as a 1:1 projection of a `CiliumNetworkPolicy`. A
SecurityGroup attaches to one of the tenant's managed applications by
reference (`spec.targetRef: {apiGroup, kind, name}`) and declares
allow-list ingress/egress for that application. Tenants manage network
policy for their own apps entirely through
`securitygroups.sdn.cozystack.io`, without being granted any access to
the `cilium.io` API group.

The translation is synchronous and stateless — the API server converts
each SecurityGroup into a CiliumNetworkPolicy in the same namespace on
write and back on read; there is no controller. The backing policy's
`endpointSelector` is **derived** from `targetRef` (matched on the
application's lineage labels
`apps.cozystack.io/application.{group,kind,name}`), never copied from
tenant input, so the selector is machine-generated and can only ever
match the referenced application's own pods in the tenant's namespace.
Policies owned by this API carry an `sdn.cozystack.io/securitygroup`
marker label that the storage always re-asserts and hides from the view,
so it only ever surfaces or mutates its own policies and never touches
platform-managed network policies.

The value is self-service network policy on a Cozystack-native,
RBAC-scoped surface, without exposing Cilium's version-coupled CRDs to
tenants. Because the selector is derived from an authorized application
reference rather than free-form tenant input, the RBAC boundary is
structural, not selector-validated: a tenant cannot express network
policy against arbitrary or platform-owned pods. The full design —
selector derivation from lineage labels, the structural boundary, the
additive-policy reality (a SecurityGroup widens a managed application's
allowed traffic but cannot sever its platform-managed management plane,
and equally cannot tighten below the platform baseline), the in-tree
CiliumNetworkPolicy mirror that avoids a Cilium Go-module dependency,
RBAC, and deferred extensions (raw-pod targeting, cluster scope,
reusable groups) — is in `pkg/apis/sdn/DESIGN.md`.

Implements the attachment model from #1614.

### Release note

```release-note
feat(api): add the SecurityGroup resource (sdn.cozystack.io/v1alpha1) — a tenant-facing, namespace-scoped firewall that attaches to a managed application by reference and projects 1:1 onto a CiliumNetworkPolicy with a machine-derived endpoint selector, letting tenants manage their application's network policy without cilium.io access.
```

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

* **New Features**
* Added the `SecurityGroup` API resource (`sdn.cozystack.io` v1alpha1)
with a namespaced projection backed by Cilium network policies.
* Enabled the SDN `APIService` and wired SDN routing, scheme, and REST
support.
* Expanded RBAC to include SecurityGroup access and required Cilium
policy permissions.
* **Documentation**
* Added a design document describing projection behavior and rule
semantics.
* **Bug Fixes**
  * Improved API rule validation coverage for missing list-type fields.
* **Tests**
* Added SDN round-trip, fuzzing, RBAC, and comprehensive REST
create/get/list/update/delete/watch/resource-version tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 14:30:23 +03:00
Aleksei Sviridkin aed0162711 fix(hooks): run the refreshed alpine/k8s kubectl hooks non-root
The vm-disk pvc-resize, cozystack-api, seaweedfs and vm-instance update
hooks (whose alpine/k8s image this PR refreshes) ran as root with no
securityContext. Add a pod-level runAsNonRoot + runAsUser 65534 +
seccompProfile RuntimeDefault and a container-level
allowPrivilegeEscalation: false + capabilities drop ALL, mirroring the
dashboard adopt hook. kubectl needs no root, so this is behavior-neutral.
Each hook is lookup-gated; pin the securityContext in a per-chart
helm-unittest that mocks the gating object (DaemonSet / PVC / Service) via
kubernetesProvider so the guard renders without a cluster. vm-disk and
vm-instance had no Makefile test target, so add 'test: helm unittest .' to
both so CI runs the new suites.

Scope note: other kubectl hooks on clastix/kubectl (mariadb, keycloak,
kubernetes, tenant) are untouched here — they are not part of this
alpine/k8s refresh and belong to a separate hardening pass.

Also fix the vm-disk resize hook's backoffLimit: it was nested under the
pod spec (silently ignored, leaving the Job at the default 6 retries)
instead of Job.spec. Move it to Job.spec and pin it in the test.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-22 21:26:42 +03:00
Aleksei SviridkinandTimofei Larkin 8ac68b24e9 feat(cozystack-api): register sdn APIService and SecurityGroup RBAC
Register the v1alpha1.sdn.cozystack.io aggregated APIService, grant the
cozystack-api ServiceAccount full access to CiliumNetworkPolicy (the
backing object for SecurityGroup), and let tenants self-service their
SecurityGroups: full access in the base tenant role and read access in
the view role, mirroring how apps.cozystack.io resources are exposed.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Co-authored-by: Timofei Larkin <lllamnyp@gmail.com>
2026-06-22 16:35:10 +03:00
Aleksei Sviridkin b30eb4602d fix(cozystack-api): drop unused objectstorage bucketclasses RBAC grant
The Option providers RBAC block granted read on
objectstorage.k8s.io/bucketclasses, but no provider in the registry lists
BucketClasses and no schema field references a bucketclass source — the
bucket app's storage pool field uses the storagepool provider, which reads
seaweedfs HelmReleases. Drop the unused grant and pin its absence in the
chart test.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-02 17:13:18 +03:00
Aleksei Sviridkin 4cbe5b62d1 fix(cozystack-api): grant the option apiserver read on plans and backups
The plan and backup option providers list namespaced plans and backups in
the backups.cozystack.io group, but the cozystack-api ClusterRole only
granted backupclasses there. The apiserver's privileged client runs as this
ServiceAccount, so both providers got forbidden — List silently dropped the
sources and Get returned 500, leaving the plan/backup dropdowns in the
backup forms empty.

Add plans and backups to the backups.cozystack.io rule and cover the grant
with a chart test.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-02 16:42:59 +03:00
Aleksei Sviridkin d2e851f63b feat: Gateway API support via Cilium
Replace ingress-nginx as the default ingress layer for cozystack-native
services with Cilium Gateway API, materialised per-tenant via a new
TenantGateway CRD reconciled by cozystack-controller.

The extra/gateway chart renders one gateway.cozystack.io/v1alpha1
TenantGateway per tenant; the controller owns the resulting Gateway,
per-tenant Issuer, Certificate(s), and the http->https redirect
HTTPRoute. This avoids the helm-vs-controller race on
Gateway.spec.listeners when route-driven listeners are added.

publishing.certificates.solver selects HTTP-01 (default; per-app HTTPS
listener + per-app Certificate) or DNS-01 (opt-in; single wildcard
Certificate per apex; providers: cloudflare, route53, digitalocean,
rfc2136). HTTP-01 keeps zero-config per-app onboarding; DNS-01 saves
Let's Encrypt budget on apex-shared clusters.

Security model -- seven independent layers, all admission-time gates
fail-closed:

  1. Listener allowedRoutes namespace whitelist via the
     kube-apiserver-written kubernetes.io/metadata.name label;
     plain-HTTP listener is strictly narrower than HTTPS, and HTTPS /
     passthrough listeners restrict allowedRoutes.kinds to
     HTTPRoute / TLSRoute respectively.
  2. cozystack-gateway-hostname-policy VAP on Gateway.
  3. cozystack-gateway-attached-namespaces-policy VAP on Package --
     blocks tenant-* in gateway.attachedNamespaces.
  4. cozystack-tenant-host-policy VAP on Tenant -- blocks spec.host
     writes from non-trusted callers.
  5. cozystack-namespace-host-label-policy VAP on Namespace -- blocks
     writes to namespace.cozystack.io/host from non-trusted callers.
  6. Render-time fail in cozystack-basics for tenant-* in
     gateway.attachedNamespaces.
  7. cozystack-route-hostname-policy VAP on HTTPRoute / TLSRoute,
     scoped to tenant-* namespaces.

Within-apex cross-namespace hostname conflict is resolved at reconcile
time by a HostnameConflict condition (cozy-* namespace wins).

Five reconcile paths refuse to rewrite pre-existing objects with the
controller-derived name but no OwnerReference back to the
TenantGateway: Gateway, redirect HTTPRoute, per-tenant Issuer, wildcard
Certificate, per-listener Certificate.

Derived-apex tenants (tenants whose host is a subdomain of a parent
tenant's) auto-enable Gateway when the platform-level gateway.enabled
is on.

Bonus fix: pkg/registry/apps/application/rest.go was ignoring the
createValidation and deleteValidation callbacks that genericapiserver
hands to every storage method, silently bypassing admission for Create
and Delete on every apps.cozystack.io resource (Tenant, MariaDB,
Postgres, Kubernetes, ...). Aligned with the existing updateValidation
pattern.

Always-on changes: cilium.envoy.enabled + cilium.gatewayAPI.enabled
flip to true (extra cilium-envoy DaemonSet, ~100 MB RAM/node idle),
and the cozystack-api admission-chain fix. Everything else is gated
behind gateway.enabled=false and tenant.spec.gateway=false; existing
clusters see no behavioural change until an operator opts in.

Tests: 60+ Go cases in internal/controller/tenantgateway/, admission-
chain regression tests in pkg/registry/apps/application/, helm-unittest
across 7 charts (extra/gateway, core/platform, apps/tenant,
system/cozystack-basics, system/cert-manager-issuers,
system/cozystack-api, system/dashboard), and 14 e2e bats scenarios in
hack/e2e-apps/gateway.bats. New make test-controllers target wired
into pull-requests.yaml.

Deps: sigs.k8s.io/gateway-api v1.4.1, cert-manager v1.17.4.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-05-25 17:07:02 +03:00