PortSecurity Resource Kind
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.
Example Manifest
Section titled “Example Manifest”apiVersion: pivirt.hu/v1kind: PortSecuritymetadata: name: web-01-nic0spec: 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: allowSpecification Reference (spec)
Section titled “Specification Reference (spec)”| 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 |
What the flows do
Section titled “What the flows do”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.
Behaviour
Section titled “Behaviour”- 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-nic0deletes 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 metadata service and the pin
Section titled “The metadata service and the pin”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.
CLI Examples
Section titled “CLI Examples”pivirtctl apply -f port-security.yamlpivirtctl get portsecpivirtctl describe portsec web-01-nic0 # cookie, flow count, TAPpivirtctl delete portsec web-01-nic0