VLAN Segmentation, MAC Spoof Filtering, and MultiNetworkPolicy
Duration: ~30 minutes
Overview
virt-05 isolated disks. This module isolates L2. The hardening guide wants dedicated VLANs (Level 2), MAC spoof filtering (Level 1), and MultiNetworkPolicy microsegmentation inside a VLAN (Level 2, OVN-Kubernetes localnet).
Pod NetworkPolicy from 101-05 and cluster AdminNetworkPolicy do not replace secondary-network policy on Multus/localnet. VMs on extra networks need NADs, spoof checking, and MultiNetworkPolicy.
Why it matters
Two VMs on the same bridge see each other’s broadcasts. MAC spoofing is a classic manipulator-in-the-middle on that segment. Without MultiNetworkPolicy, a VLAN is one flat trust zone.
What does it solve
-
All VMs on the default pod network with no L2 split
-
SR-IOV or bridge ports with spoof check off
-
A VLAN that is "isolated" but still allows every MAC on that VLAN to talk
Your Mission
Inventory NetworkAttachmentDefinitions and SriovNetworks. Parse each NAD spec.config JSON for vlanID / vlan and MAC spoof fields. If the cluster has no secondary network yet, that is the default (VLANs are opt-in): you still leave with the YAML you would apply in production. Do not attach a new network to rhel-webserver in this lab.
Prerequisites
-
virt-05 complete
-
jqon the bastion -
OVN-Kubernetes is the usual default CNI; SR-IOV and bridge NADs are optional
Click each step only if you need a hint.
Control: Use dedicated VLANs to segment network traffic
Level: 2
Put VM secondary interfaces on separate VLANs (OVN localnet vlanID, SR-IOV spec.vlan, or bridge CNI vlan) so guests are not on one L2 broadcast domain.
Pros: Broadcast and L2 attacks stay inside a VLAN. Tenants cannot ARP for each other unless you routed them.
Cons / impact: Services on other VLANs are unreachable without explicit routing or additional NICs. You must design which workloads share a VLAN.
Default: Dedicated VLANs are not configured. No VM has a VLAN until you create a NAD or SriovNetwork.
Audit NAD and SR-IOV VLAN IDs
spec.config on a NetworkAttachmentDefinition is a string of JSON. Parse it.
oc get networkattachmentdefinition -A -o json | jq -c '
.items[]
| {
ns: .metadata.namespace,
name: .metadata.name,
config: (.spec.config | fromjson? // .spec.config)
}
'
echo '--- sriovnetwork vlan ---'
oc get sriovnetwork -A -o json 2>/dev/null | jq -c '
.items[]? | {ns: .metadata.namespace, name: .metadata.name, vlan: .spec.vlan}
' || echo "SriovNetwork CRD not installed (skip SR-IOV audit)"
Note each vlanID / vlan. null or missing VLAN on a secondary net that VMs use is a Level 2 finding. Zero NADs means you have not opted in yet; plan VLANs before you attach production VMs to a bridge.
|
Remediation (apply when you have a real secondary network)
Examples (replace phy-net, vlanID, and namespace). Do not apply blindly if the physical network is not ready.
OVN-Kubernetes localnet:
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: vlan-100
namespace: default
spec:
config: |-
{
"cniVersion": "0.3.1",
"type": "ovn-k8s-cni-overlay",
"topology": "localnet",
"netAttachDefName": "default/vlan-100",
"vlanID": 100
}
Bridge CNI should set "vlan": 100 and "preserveDefaultVlan": false. SR-IOV uses spec.vlan: 100 on SriovNetwork.
After you create a NAD, attach it only to VMs that should sit on that VLAN. Leave rhel-webserver on its current network unless you are on a cluster you own and have a change window.
|
Control: Enable MAC spoof filtering
Level: 1
Spoof checking drops frames whose source MAC is not the MAC allocated to that VM port.
Pros: A compromised guest cannot impersonate another MAC on the segment (manipulator-in-the-middle, some DHCP/ARP tricks).
Cons / impact: None for ordinary guests. Workloads that must use additional MACs (some clustered NICs, MACVLAN inside the guest) need a documented exception.
Default: OVN-Kubernetes localnet enables MAC spoof filtering. Bridge and SR-IOV must be turned on in the NAD or SriovNetwork.
Audit spoof check
echo '=== SR-IOV spoofChk (expect on) ==='
oc get sriovnetwork -A -o json 2>/dev/null | jq -c '
.items[]? | {name: .metadata.name, spoofChk: .spec.spoofChk}
' || echo "no SriovNetwork"
echo '=== NAD config macspoofchk / macSpoofChk ==='
oc get networkattachmentdefinition -A -o json | jq -c '
.items[]
| {ns: .metadata.namespace, name: .metadata.name, config: (.spec.config | fromjson? // {})}
| {ns, name, macspoofchk: (.config.macspoofchk // .config.macSpoofChk // .config.ipam.macspoofchk // "see raw config")}
'
SR-IOV spoofChk should be on. Bridge NADs should set mac spoof check true. OVN localnet is on by default even if the JSON omits the key.
|
Remediation
Enable spoof check in the NAD or SriovNetwork for that provider. See SR-IOV CNI and the bridge CNI NAD macspoofchk field in multiple networks.
| Re-run the audit after you change a NAD. Existing VM attachments may need a restart to pick up CNI config. |
Control: Use MultiNetworkPolicy
Level: 2
MultiNetworkPolicy selects a VM and the NetworkAttachmentDefinition it uses, then allows only listed ingress/egress on that secondary network. After the first policy in a namespace matches a VM+NAD pair, everything else on that network is denied.
Pros: Microsegmentation inside a VLAN. East-west is explicit.
Cons / impact: A partial policy can isolate a VM from DNS, backup, or its application peers. You must allow what you still need. Feature is tied to OVN-Kubernetes localnet.
Default: No MultiNetworkPolicy objects. The feature is off until you create policies.
Audit MultiNetworkPolicy
oc get multi-networkpolicy -A 2>/dev/null || echo "MultiNetworkPolicy CRD not available"
Empty is the default. The hardened goal is at least one policy per namespace that has VMs on a secondary network, matching both the VM and the NAD. Primary-network-only VMs (like a default rhel-webserver) are covered by NetworkPolicy/ANP, not this CR.
|
Remediation
Create MultiNetworkPolicy in the project that owns the VM. The policy must match the VM and the NAD. See MultiNetworkPolicy.
Example shape (names and labels must match your NAD):
apiVersion: k8s.cni.cncf.io/v1beta1
kind: MultiNetworkPolicy
metadata:
name: allow-same-vlan
namespace: rhel-webserver
annotations:
k8s.v1.cni.cncf.io/policy-for: rhel-webserver/vlan-100
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
Do not apply this example unless you created a matching NAD named vlan-100 in that namespace. A policy for a NAD that does not exist does nothing useful; a policy for a NAD that does exist can cut the VM off.
Re-run oc get multi-networkpolicy -A after a real apply.
|
Debrief
L2 first (VLAN), then spoof check on the port, then MultiNetworkPolicy inside the VLAN. Pod NetworkPolicy remains for the primary CNI (101-05, 301-03).
What breaks without this:
-
Flat L2 between VMs
-
Forged MACs on a bridge
-
A VLAN that is one big allow-any
| Next is virt-07 — firmware TXT, nested virt, vCPU metrics, and CPU vulnerability mitigations. |