fix(kubevirt): route the export proxy to the Service KubeVirt creates (#4637)

## What this PR does

The VM export routes point at a Service that does not exist, and the
tenant network policy would block the Ingress even if it did.

KubeVirt creates the export proxy Service under the name
`virt-exportproxy` (`VirtExportProxyName` in
`pkg/virt-operator/resource/generate/components`, v1.9.0). The Ingress
and the TLSRoute that publish `vm-exportproxy.<host>` name a
`vm-exportproxy` Service, and nothing creates one. The backend was
`virt-exportproxy` when the Ingress was added in 0ddaff938. Then
3780925a6 renamed the hostname to `vm-exportproxy` and the backend along
with it. The hostname stays, only the backend goes back.

Renaming the backend is not enough for the Ingress, because it is served
by the ingress controller in tenant-root and tenant egress is
default-deny. Each allowed destination has its own `allow-to-*` policy.
The CDI upload proxy has `allow-to-cdi-upload-proxy`, the export proxy
had nothing. The new `allow-to-vm-exportproxy` policy renders for the
same tenants and the same source pods as the CDI one. Its destination is
narrower: only the `kubevirt.io=virt-exportproxy` pods in
`cozy-kubevirt`, on TCP 8443, the container port virt-operator gives
them.

The unit tests pin the backend name for the Ingress and the TLSRoute,
and the placement, selector and port of the new policy.

I checked this on a test cluster with KubeVirt v1.9.0 and ingress-nginx.
Without the fix the controller logs `Error obtaining Endpoints for
Service "cozy-kubevirt/vm-exportproxy"`. With the backend renamed and
the tenant allowed egress to virt-exportproxy, a passthrough request to
`vm-exportproxy.<host>` reaches the export proxy, and it answers with
its own serving certificate.

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

### Release note

```release-note
fix(kubevirt): requests to vm-exportproxy.<host> now reach the KubeVirt export proxy: the Ingress and TLSRoute route to the virt-exportproxy Service KubeVirt creates, and tenants are allowed egress to the export proxy.
```


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

## Summary by CodeRabbit

* **New Features**
* Tenant workloads can now connect to the KubeVirt export proxy over TCP
port 8443.
* **Bug Fixes**
* Updated export-proxy Ingress and TLSRoute routing to use the
`virt-exportproxy` backend, keeping export-proxy traffic directed to the
correct service.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Aleksei Sviridkin authored and GitHub committed 2026-10-07 11:19:32 +03:00
commit 6addef4d3c
5 files changed
+142 -2

No files matched your search

@@ -242,6 +242,23 @@ spec:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": cozy-kubevirt-cdi
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-to-vm-exportproxy
namespace: {{ include "tenant.name" . }}
spec:
endpointSelector: {}
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": cozy-kubevirt
kubevirt.io: virt-exportproxy
toPorts:
- ports:
- port: "8443"
protocol: TCP
{{- if .Values.monitoring }}
---
apiVersion: cilium.io/v2
@@ -0,0 +1,85 @@
suite: tenant egress to the KubeVirt export proxy
# The vm-exportproxy Ingress in cozy-kubevirt is served by the ingress
# controller running in tenant-root. Tenant egress is default-deny plus
# explicit allow-to-* policies, so without this one the controller cannot
# reach virt-exportproxy. It follows allow-to-cdi-upload-proxy, the policy for
# the sibling CDI upload proxy, in where and when it renders.
templates:
- templates/networkpolicy.yaml
tests:
- it: renders next to allow-to-cdi-upload-proxy in the root tenant
release:
name: tenant-root
namespace: tenant-root
asserts:
- documentSelector:
path: metadata.name
value: allow-to-cdi-upload-proxy
equal:
path: metadata.namespace
value: tenant-root
- documentSelector:
path: metadata.name
value: allow-to-vm-exportproxy
isKind:
of: CiliumNetworkPolicy
- documentSelector:
path: metadata.name
value: allow-to-vm-exportproxy
equal:
path: metadata.namespace
value: tenant-root
- it: renders next to allow-to-cdi-upload-proxy in a nested tenant with every module off
release:
name: tenant-bar
namespace: tenant-foo
set:
monitoring: false
ingress: false
etcd: false
asserts:
- documentSelector:
path: metadata.name
value: allow-to-cdi-upload-proxy
equal:
path: metadata.namespace
value: tenant-foo-bar
- documentSelector:
path: metadata.name
value: allow-to-vm-exportproxy
equal:
path: metadata.namespace
value: tenant-foo-bar
- it: applies to every pod of the tenant, as allow-to-cdi-upload-proxy does
release:
name: tenant-root
namespace: tenant-root
asserts:
- documentSelector:
path: metadata.name
value: allow-to-vm-exportproxy
equal:
path: spec.endpointSelector
value: {}
- it: allows only the export proxy pods on their container port
release:
name: tenant-root
namespace: tenant-root
asserts:
- documentSelector:
path: metadata.name
value: allow-to-vm-exportproxy
equal:
path: spec.egress
value:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": cozy-kubevirt
kubevirt.io: virt-exportproxy
toPorts:
- ports:
- port: "8443"
protocol: TCP
@@ -20,7 +20,7 @@ spec:
paths:
- backend:
service:
name: vm-exportproxy
name: virt-exportproxy
port:
number: 443
path: /
@@ -18,6 +18,6 @@ spec:
- {{ printf "vm-exportproxy.%s" $host | quote }}
rules:
- backendRefs:
- name: vm-exportproxy
- name: virt-exportproxy
port: 443
{{- end }}
@@ -0,0 +1,38 @@
suite: vm-exportproxy routes reach the Service KubeVirt creates
templates:
- templates/vm-exportproxy-ingress.yaml
- templates/vm-exportproxy-tlsroute.yaml
release:
name: kubevirt
namespace: cozy-kubevirt
# virt-operator names the export proxy Service virt-exportproxy
# (VirtExportProxyName in pkg/virt-operator/resource/generate/components).
# The public hostname is vm-exportproxy.<host>; the Service name is not
# derived from it.
tests:
- it: the Ingress backend is virt-exportproxy
set:
_cluster:
root-host: example.org
expose-services: "vm-exportproxy"
gateway-enabled: "false"
template: templates/vm-exportproxy-ingress.yaml
asserts:
- equal:
path: spec.rules[0].http.paths[0].backend.service.name
value: virt-exportproxy
- it: the TLSRoute backend is virt-exportproxy
set:
_cluster:
root-host: example.org
expose-services: "vm-exportproxy"
gateway-enabled: "true"
template: templates/vm-exportproxy-tlsroute.yaml
asserts:
- equal:
path: spec.rules[0].backendRefs[0].name
value: virt-exportproxy