Skip to content

PortSecurity Resource Kind

Kind: PortSecurityapiVersion: pivirt.hu/v1Aliases: portsecurity, portsec

A PortSecurity object pins one VM interface to its identity and applies a compiled, stateful ACL to it. It is the tenant-isolation enforcement point: a guest that changes its MAC, takes a neighbour’s IP, answers DHCP or sends router advertisements has those frames dropped at its own port, before they reach the bridge.

It is realized as OpenFlow rules on the OVS bridge the TAP is attached to, under one cookie per object, so removing it removes exactly its rules and nothing else on the bridge. On a managed node the director compiles a tenant’s SecurityGroup into the acl of every member port; a host never evaluates group names.


apiVersion: pivirt.hu/v1
kind: PortSecurity
metadata:
name: web-01-nic0
spec:
tap: tap1a2b3c4d # pivirtd names VM TAPs tap<uuid8>[n]
bridge: br-vpc-10042 # must be an OVS bridge
allowedMac: 52:54:00:ab:cd:ef
allowedIps: [ 10.42.1.10/32 ]
dhcpClientOnly: true # may ask for a lease, never answer one
acl:
ingress:
- { proto: tcp, ports: [22, 80, 443], from: 0.0.0.0/0 }
- { proto: tcp, ports: [5432], from: 10.42.2.0/24 }
egress:
- { proto: any, to: 0.0.0.0/0 }
defaultIngress: drop
defaultEgress: allow

Field Type Required Default Description
tap string Yes The VM interface on the host
bridge string Yes The OVS bridge the TAP is attached to; a Linux bridge is refused
allowedMac string Yes The only source MAC the port may send from
allowedIps list[string] One of IPv4 prefixes the port may send from (usually one /32)
allowedIp6 list[string] One of IPv6 prefixes the port may send from
dhcpClientOnly bool No false Drop DHCP-server replies and router advertisements sent by the guest
acl.ingress[] list No [] Allowed new inbound connections
acl.egress[] list No [] Allowed new outbound connections
acl.*[].proto string No any tcp, udp, icmp or any
acl.*[].ports list[int] No all Destination ports (tcp/udp only; at most 64 per rule)
acl.ingress[].from string No anywhere Remote source prefix
acl.egress[].to string No anywhere Remote destination prefix
defaultIngress string No drop Fate of inbound connections no rule matched: drop or allow
defaultEgress string No allow Fate of outbound connections no rule matched: allow or drop

Connections are tracked, so a reply to an allowed connection passes without a rule of its own.

Table Direction Rule
0 from the VM ARP only as (allowedMac, allowedIps); DHCP client requests; IP only from (allowedMac, allowedIps) → conntrack → table 11; everything else from the port is dropped
0 from the VM with dhcpClientOnly: DHCP-server replies and IPv6 router advertisements dropped
0 to the VM ARP and DHCP replies pass; IP → conntrack → table 10; other unicast to the MAC dropped (broadcast still reaches the guest)
10 ingress established/related pass, invalid dropped, ICMP allowed, each acl.ingress rule commits a new connection, then defaultIngress
11 egress established/related pass, invalid dropped, each acl.egress rule commits a new connection, then defaultEgress

Tables 10 and 11 and priorities 90–130 are reserved for port security; the bridge’s own NORMAL forwarding and any overlay flows keep working for ports that carry no PortSecurity.


  • Honest re-apply. Every flow carries a cookie whose upper half identifies the object and whose lower half fingerprints the spec. An unchanged spec whose flows are all present issues no command; a changed spec replaces every version of the object’s flows in one atomic bundle.
  • Exact removal. pivirtctl delete portsec web-01-nic0 deletes exactly this object’s flows, every version, by cookie.
  • Boot order. Restored right after the bridges and before any VM can be started, so there is no window with an unguarded port.
  • Status reports the cookie, the flow count and the TAP.

The IMDS (169.254.169.254) identifies a VM by the source address of the connection and nothing else — X-Forwarded-For is ignored. That is trustworthy exactly because PortSecurity pins the address to the port. As a second layer it issues IMDSv2-style session tokens (PUT /latest/api/token, EC2’s header names, so cloud-init’s EC2 datasource uses them unchanged): a token presented on a read must have been minted for the requesting VM, and a node can require tokens outright.


Terminal window
pivirtctl apply -f port-security.yaml
pivirtctl get portsec
pivirtctl describe portsec web-01-nic0 # cookie, flow count, TAP
pivirtctl delete portsec web-01-nic0