NetworkPolicies for VirtRigaud¶
This page gives concrete NetworkPolicy templates for the manager and provider pods in v0.3.8. Since v0.3.7, manager↔provider gRPC is protected by mTLS by default (see mTLS), so mTLS — not NetworkPolicy — is the primary transport control. NetworkPolicy remains valuable as defence-in-depth, and is the required compensating control for any Provider you deliberately run with tls.enabled=false (audit-flagged plaintext). Regulated deployments should still treat the policies on this page as required, not optional.
NetworkPolicy requires an enforcing CNI
NetworkPolicy is a Kubernetes API; enforcement depends on the CNI. The major CNIs that enforce it are Calico, Cilium, Antrea, Kube-router, and Weave. Flannel does not enforce NetworkPolicy by default. Verify with kubectl get pods -n kube-system and your CNI's documentation before assuming these policies are taking effect.
Port surface in v0.3.8¶
Verify against cmd/manager/main.go, charts/virtrigaud/templates/manager-deployment.yaml, cmd/provider-*/main.go, and internal/controller/provider_controller.go:617-652.
Manager pod¶
| Port | Protocol | Purpose | Notes |
|---|---|---|---|
| 8080 | TCP | Prometheus /metrics endpoint | HTTP by default; HTTPS + RBAC when --metrics-secure=true |
| 8081 | TCP | Health probes (/healthz, /readyz) | HTTP, used by kubelet only |
| 9443 | TCP | Admission webhook server | TLS; the kube-apiserver dials this for CRD webhooks |
The manager does not open a gRPC server port — it is purely a gRPC client to providers.
Provider pod (any of vsphere / libvirt / proxmox / mock)¶
| Port | Protocol | Purpose | Notes |
|---|---|---|---|
| 9443 | TCP | gRPC server (provider.v1.Provider) | mTLS by default since v0.3.7; plaintext only on explicit per-Provider opt-out (see mTLS) |
| 8080 | TCP | Health probes (/healthz) | HTTP, used by kubelet only. NOT a metrics endpoint. |
The provider's health endpoint on :8080 is a kubelet-only path. Provider pods do not export Prometheus metrics — all virtrigaud_* series come from the manager pod.
Hypervisor endpoints (provider egress)¶
| Provider | Egress destination | Port(s) |
|---|---|---|
| vSphere | vCenter SOAP API | 443 TCP |
| Libvirt | libvirt host over SSH (qemu+ssh://) | 22 TCP |
| Libvirt | libvirt daemon over TLS (qemu+tls://) | 16514 TCP |
| Libvirt | libvirt daemon over plain TCP (qemu+tcp://) | 16509 TCP |
| Proxmox | Proxmox VE REST API | 8006 TCP |
For the libvirt provider over SSH, note that SSH host-key verification is on by default since v0.3.7; still plan the egress allowlist tightly (single host CIDR, not a broad block) as defence-in-depth.
Default-deny baseline¶
Apply a default-deny NetworkPolicy in every namespace that hosts a manager or provider pod, then layer specific allow rules on top.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: virtrigaud-system
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Repeat in the namespace where the per-Provider Deployments land (by default the same namespace as the manager).
Manager NetworkPolicy¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: virtrigaud-manager
namespace: virtrigaud-system
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: manager
app.kubernetes.io/name: virtrigaud
policyTypes:
- Ingress
- Egress
ingress:
# /metrics — only from your Prometheus pod.
# Tighten the selector to match your monitoring stack
# (kube-prometheus-stack defaults shown).
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- protocol: TCP
port: 8080
# Webhook :9443 — only from the kube-apiserver.
# The CNI must expose an apiserver selector; on managed
# clusters check the cloud provider's recommendation.
- from:
- namespaceSelector: {}
podSelector:
matchLabels:
component: kube-apiserver
ports:
- protocol: TCP
port: 9443
# Health probes :8081 are kubelet-initiated and do not
# traverse pod network. No NetworkPolicy entry needed —
# kubelet bypasses NetworkPolicy by design.
egress:
# DNS to kube-system CoreDNS.
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# kube-apiserver — for ListWatch and SubjectAccessReview.
# On most clusters the apiserver is at the cluster-internal
# virtual IP 10.96.0.1:443. If yours differs, adjust the CIDR.
- to:
- ipBlock:
cidr: 10.96.0.1/32 # cluster apiserver virtual IP
ports:
- protocol: TCP
port: 443
# Egress to provider pods (gRPC :9443) in the same namespace
# AND in any namespace that holds provider deployments. The
# provider controller deploys providers into the same namespace
# as the manager unless overridden.
- to:
- podSelector:
matchLabels:
app.kubernetes.io/component: provider-runtime
ports:
- protocol: TCP
port: 9443
If your provider runtimes live in a different namespace than the manager, add a namespaceSelector to the manager's egress rule and a matching namespaceSelector on the providers' ingress rule below.
Provider NetworkPolicy¶
This is a high-value policy: the provider pod should be reachable only from the manager pod. In v0.3.8 the gRPC channel is mTLS-protected by default, so this policy is defence-in-depth rather than the sole control — but it becomes the primary network safeguard for any Provider you run with tls.enabled=false (audit-flagged plaintext).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: virtrigaud-provider
namespace: virtrigaud-system # adjust if providers run elsewhere
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: provider-runtime
policyTypes:
- Ingress
- Egress
ingress:
# ONLY the manager pod may dial the provider's gRPC port.
- from:
- podSelector:
matchLabels:
app.kubernetes.io/component: manager
app.kubernetes.io/name: virtrigaud
ports:
- protocol: TCP
port: 9443
# Health probes :8080 are kubelet-initiated; no rule needed.
egress:
# DNS — required for resolving the hypervisor endpoint hostname.
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Hypervisor API — adjust per provider type. Each provider needs
# exactly ONE of these blocks. Replace the CIDR with the actual
# hypervisor host or vCenter VIP.
# --- vSphere example ---
- to:
- ipBlock:
cidr: 10.10.20.5/32 # vCenter VIP
ports:
- protocol: TCP
port: 443
# --- Libvirt-over-SSH example (replace with your libvirt host) ---
# - to:
# - ipBlock:
# cidr: 10.10.30.10/32
# ports:
# - protocol: TCP
# port: 22
# --- Proxmox example ---
# - to:
# - ipBlock:
# cidr: 10.10.40.20/32
# ports:
# - protocol: TCP
# port: 8006
Specify hypervisor CIDRs explicitly
Do not use broad blocks like 10.0.0.0/8. The hypervisor endpoint is highly sensitive — write the policy with single-host CIDRs (/32) where possible and document each.
Per-provider variants¶
vSphere¶
The vSphere provider needs egress to:
- vCenter SOAP API (
/sdkendpoint) on TCP 443 - Optionally ESXi hosts directly if your topology has the provider talk to ESXi for some operations (rare in current code paths)
Use the template above, with the egress ipBlock pointing at the vCenter VIP only.
Libvirt (qemu+ssh://)¶
The libvirt provider needs egress to the libvirt host on TCP 22. SSH host-key verification is on by default since v0.3.7 (see the libvirt provider page), so it — not NetworkPolicy — is the primary control on the SSH path. Combine it with the NetworkPolicy below as defence-in-depth, keeping the SSH egress scoped to the single libvirt host so the traffic does not cross an untrusted boundary.
Maintainer choice: plaintext gRPC to the libvirt sidecar
The libvirt provider talks plaintext gRPC to its in-pod sidecar while reaching the libvirt host over SSH with verified known_hosts — a documented maintainer choice. Verify this posture meets your controls before relying on it in regulated/banking environments.
The libvirt provider also runs scp for disk transfers on the same SSH connection — that is still port 22, no extra rule needed.
Proxmox¶
The Proxmox provider needs egress to the Proxmox VE REST API on TCP 8006.
egress:
- to:
- ipBlock:
cidr: 10.10.40.20/32 # PVE node or cluster VIP
ports:
- protocol: TCP
port: 8006
If your PVE deployment is a cluster, list all node IPs (or the cluster's shared VIP).
Mock provider¶
The mock provider has no egress requirement beyond DNS. Its ingress rule is identical to the production providers — only the manager pod may dial it.
Verification¶
After applying the policies, verify enforcement from inside a pod that should be denied:
# From a random pod in a different namespace, attempt to dial the
# provider's gRPC port. The expected outcome is a CONNECTION TIMEOUT
# (NOT a refused connection — connection refused means there is no
# policy and the port is just closed; timeout means the policy is
# dropping the SYN, which is what you want).
kubectl run -it --rm netcheck \
--image=nicolaka/netshoot:v0.13 \
--restart=Never \
--namespace=default \
-- nc -zv -w 5 provider-vsphere-prod.virtrigaud-system.svc.cluster.local 9443
Then verify that the manager pod CAN still dial it:
kubectl exec -n virtrigaud-system deploy/virtrigaud-manager -- \
/bin/sh -c "command -v nc >/dev/null && nc -zv provider-vsphere-prod 9443"
The manager pod runs distroless and does not include nc, so this exec will likely fail with command not found — check the gRPC behaviour via a real reconcile instead (look at virtrigaud_provider_rpc_requests_total after applying a VirtualMachine).
CNI-specific notes¶
Cilium¶
If you use CiliumNetworkPolicy (extends NetworkPolicy with L7 rules), you can add gRPC-method-level restrictions:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: virtrigaud-provider-l7
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/component: provider-runtime
ingress:
- fromEndpoints:
- matchLabels:
app.kubernetes.io/component: manager
toPorts:
- ports:
- port: "9443"
protocol: TCP
You CAN add a gRPC-method allowlist here (e.g. allow only Validate, Create, Describe, Power, Delete, TaskStatus). Be careful: if you forget a method, the manager will fail with codes.PermissionDenied and the operator-visible signal will be virtrigaud_provider_rpc_requests_total{code="PermissionDenied"} going up.
Calico¶
Calico's GlobalNetworkPolicy lets you apply the same rule across all namespaces. Combine with NetworkSet for hypervisor IP allowlists that you can update without re-applying every Provider policy:
apiVersion: projectcalico.org/v3
kind: GlobalNetworkSet
metadata:
name: hypervisor-endpoints
labels:
role: hypervisor
spec:
nets:
- 10.10.20.5/32
- 10.10.20.6/32
Then reference selector: role == "hypervisor" in your GlobalNetworkPolicy egress rules.
See also¶
- mTLS — what this NetworkPolicy is compensating for.
- Operations -> Security — the gap inventory that motivates these policies.
- Libvirt provider — the SSH host-key trust model.