Skip to content

Commit be2cba0

Browse files
authored
Merge pull request #1587 from stgraber/bridge-nic-acl
Support for ACLs for bridge NIC device when using nftables driver
2 parents 452fbc5 + bc1b18d commit be2cba0

17 files changed

+515
-123
lines changed

doc/api-extensions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,3 +2685,7 @@ This adds extra fields to the OVN network state struct for the IPv4 and IPv6 add
26852685
## `qemu_scriptlet_config`
26862686

26872687
This extends the QEMU scriptlet feature by allowing to modify QEMU configuration before a VM starts, and passing information about the instance to the scriptlet.
2688+
2689+
## `network_bridge_acl_devices`
2690+
2691+
This adds support for device ACLs when attached to a bridged network.

doc/howto/network_acls.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ incus config device set <instance_name> <device_name> security.acls.default.ingr
210210
When using network ACLs with a bridge network, be aware of the following limitations:
211211

212212
- Unlike OVN ACLs, bridge ACLs are applied only on the boundary between the bridge and the Incus host.
213-
This means they can only be used to apply network policies for traffic going to or from external networks.
213+
This means they can only be used to apply network policies for traffic going to or from external networks (see exception for `nftables` firewall driver below).
214214
They cannot be used for to create {spellexception}`intra-bridge` firewalls, thus firewalls that control traffic between instances connected to the same bridge.
215+
- When using the `nftables` firewall driver you can apply ACLs to the NIC device and control traffic between the instances. In this case the `reject` ACL rules applied to the ingress traffic are converted to `drop` to address `nftables` limitation.
215216
- {ref}`ACL groups and network selectors <network-acls-selectors>` are not supported.
216217
- When using the `iptables` firewall driver, you cannot use IP range subjects (for example, `192.0.2.1-192.0.2.10`).
217218
- Baseline network service rules are added before ACL rules (in their respective INPUT/OUTPUT chains), because we cannot differentiate between INPUT/OUTPUT and FORWARD traffic once we have jumped into the ACL chain.

doc/reference/devices_nic.md

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,32 +70,37 @@ A `bridged` NIC uses an existing bridge on the host and creates a virtual device
7070

7171
NIC devices of type `bridged` have the following device options:
7272

73-
Key | Type | Default | Managed | Description
74-
:-- | :-- | :-- | :-- | :--
75-
`boot.priority` | integer | - | no | Boot priority for VMs (higher value boots first)
76-
`host_name` | string | randomly assigned | no | The name of the interface inside the host
77-
`hwaddr` | string | randomly assigned | no | The MAC address of the new interface
78-
`ipv4.address` | string | - | no | An IPv4 address to assign to the instance through DHCP (can be `none` to restrict all IPv4 traffic when `security.ipv4_filtering` is set)
79-
`ipv4.routes` | string | - | no | Comma-delimited list of IPv4 static routes to add on host to NIC
80-
`ipv4.routes.external` | string | - | no | Comma-delimited list of IPv4 static routes to route to the NIC and publish on uplink network (BGP)
81-
`ipv6.address` | string | - | no | An IPv6 address to assign to the instance through DHCP (can be `none` to restrict all IPv6 traffic when `security.ipv6_filtering` is set)
82-
`ipv6.routes` | string | - | no | Comma-delimited list of IPv6 static routes to add on host to NIC
83-
`ipv6.routes.external` | string | - | no | Comma-delimited list of IPv6 static routes to route to the NIC and publish on uplink network (BGP)
84-
`limits.egress` | string | - | no | I/O limit in bit/s for outgoing traffic (various suffixes supported, see {ref}`instances-limit-units`)
85-
`limits.ingress` | string | - | no | I/O limit in bit/s for incoming traffic (various suffixes supported, see {ref}`instances-limit-units`)
86-
`limits.max` | string | - | no | I/O limit in bit/s for both incoming and outgoing traffic (same as setting both `limits.ingress` and `limits.egress`)
87-
`limits.priority` | integer | - | no | The `skb->priority` value (32-bit unsigned integer) for outgoing traffic, to be used by the kernel queuing discipline (qdisc) to prioritize network packets (The effect of this value depends on the particular qdisc implementation, for example, `SKBPRIO` or `QFQ`. Consult the kernel qdisc documentation before setting this value.)
88-
`mtu` | integer | parent MTU | yes | The MTU of the new interface
89-
`name` | string | kernel assigned | no | The name of the interface inside the instance
90-
`network` | string | - | no | The managed network to link the device to (instead of specifying the `nictype` directly)
91-
`parent` | string | - | yes | The name of the host device (required if specifying the `nictype` directly)
92-
`queue.tx.length` | integer | - | no | The transmit queue length for the NIC
93-
`security.ipv4_filtering`| bool | `false` | no | Prevent the instance from spoofing another instance's IPv4 address (enables `security.mac_filtering`)
94-
`security.ipv6_filtering`| bool | `false` | no | Prevent the instance from spoofing another instance's IPv6 address (enables `security.mac_filtering`)
95-
`security.mac_filtering` | bool | `false` | no | Prevent the instance from spoofing another instance's MAC address
96-
`security.port_isolation`| bool | `false` | no | Prevent the NIC from communicating with other NICs in the network that have port isolation enabled
97-
`vlan` | integer | - | no | The VLAN ID to use for non-tagged traffic (can be `none` to remove port from default VLAN)
98-
`vlan.tagged` | integer | - | no | Comma-delimited list of VLAN IDs or VLAN ranges to join for tagged traffic
73+
Key | Type | Default | Managed | Description
74+
:-- | :-- | :-- | :-- | :--
75+
`boot.priority` | integer | - | no | Boot priority for VMs (higher value boots first)
76+
`host_name` | string | randomly assigned | no | The name of the interface inside the host
77+
`hwaddr` | string | randomly assigned | no | The MAC address of the new interface
78+
`ipv4.address` | string | - | no | An IPv4 address to assign to the instance through DHCP (can be `none` to restrict all IPv4 traffic when `security.ipv4_filtering` is set)
79+
`ipv4.routes` | string | - | no | Comma-delimited list of IPv4 static routes to add on host to NIC
80+
`ipv4.routes.external` | string | - | no | Comma-delimited list of IPv4 static routes to route to the NIC and publish on uplink network (BGP)
81+
`ipv6.address` | string | - | no | An IPv6 address to assign to the instance through DHCP (can be `none` to restrict all IPv6 traffic when `security.ipv6_filtering` is set)
82+
`ipv6.routes` | string | - | no | Comma-delimited list of IPv6 static routes to add on host to NIC
83+
`ipv6.routes.external` | string | - | no | Comma-delimited list of IPv6 static routes to route to the NIC and publish on uplink network (BGP)
84+
`limits.egress` | string | - | no | I/O limit in bit/s for outgoing traffic (various suffixes supported, see {ref}`instances-limit-units`)
85+
`limits.ingress` | string | - | no | I/O limit in bit/s for incoming traffic (various suffixes supported, see {ref}`instances-limit-units`)
86+
`limits.max` | string | - | no | I/O limit in bit/s for both incoming and outgoing traffic (same as setting both `limits.ingress` and `limits.egress`)
87+
`limits.priority` | integer | - | no | The `skb->priority` value (32-bit unsigned integer) for outgoing traffic, to be used by the kernel queuing discipline (qdisc) to prioritize network packets (The effect of this value depends on the particular qdisc implementation, for example, `SKBPRIO` or `QFQ`. Consult the kernel qdisc documentation before setting this value.)
88+
`mtu` | integer | parent MTU | yes | The MTU of the new interface
89+
`name` | string | kernel assigned | no | The name of the interface inside the instance
90+
`network` | string | - | no | The managed network to link the device to (instead of specifying the `nictype` directly)
91+
`parent` | string | - | yes | The name of the host device (required if specifying the `nictype` directly)
92+
`queue.tx.length` | integer | - | no | The transmit queue length for the NIC
93+
`security.acls` | string | - | no | Comma-separated list of network ACLs to apply
94+
`security.acls.default.egress.action` | string | `drop` | no | Action to use for egress traffic that doesn't match any ACL rule
95+
`security.acls.default.egress.logged` | bool | `false` | no | Whether to log egress traffic that doesn't match any ACL rule
96+
`security.acls.default.ingress.action`| string | `drop` | no | Action to use for ingress traffic that doesn't match any ACL rule
97+
`security.acls.default.ingress.logged`| bool | `false` | no | Whether to log ingress traffic that doesn't match any ACL rule
98+
`security.ipv4_filtering` | bool | `false` | no | Prevent the instance from spoofing another instance's IPv4 address (enables `security.mac_filtering`)
99+
`security.ipv6_filtering` | bool | `false` | no | Prevent the instance from spoofing another instance's IPv6 address (enables `security.mac_filtering`)
100+
`security.mac_filtering` | bool | `false` | no | Prevent the instance from spoofing another instance's MAC address
101+
`security.port_isolation` | bool | `false` | no | Prevent the NIC from communicating with other NICs in the network that have port isolation enabled
102+
`vlan` | integer | - | no | The VLAN ID to use for non-tagged traffic (can be `none` to remove port from default VLAN)
103+
`vlan.tagged` | integer | - | no | Comma-delimited list of VLAN IDs or VLAN ranges to join for tagged traffic
99104

100105
(nic-macvlan)=
101106
### `nictype`: `macvlan`

internal/server/device/nic_bridged.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@ import (
2626
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
2727
"github.com/lxc/incus/v6/internal/server/dnsmasq"
2828
"github.com/lxc/incus/v6/internal/server/dnsmasq/dhcpalloc"
29+
firewallDrivers "github.com/lxc/incus/v6/internal/server/firewall/drivers"
2930
"github.com/lxc/incus/v6/internal/server/instance"
3031
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
3132
"github.com/lxc/incus/v6/internal/server/ip"
3233
"github.com/lxc/incus/v6/internal/server/network"
34+
"github.com/lxc/incus/v6/internal/server/network/acl"
35+
"github.com/lxc/incus/v6/internal/server/project"
3336
"github.com/lxc/incus/v6/internal/server/resources"
3437
localUtil "github.com/lxc/incus/v6/internal/server/util"
3538
internalUtil "github.com/lxc/incus/v6/internal/util"
@@ -89,6 +92,11 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
8992
"security.ipv4_filtering",
9093
"security.ipv6_filtering",
9194
"security.port_isolation",
95+
"security.acls",
96+
"security.acls.default.ingress.action",
97+
"security.acls.default.egress.action",
98+
"security.acls.default.ingress.logged",
99+
"security.acls.default.egress.logged",
92100
"boot.priority",
93101
"vlan",
94102
}
@@ -286,6 +294,24 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
286294
}
287295
}
288296

297+
// Check if security ACL(s) are configured.
298+
if d.config["security.acls"] != "" {
299+
if d.state.Firewall.String() != "nftables" {
300+
return fmt.Errorf("Security ACLs are only supported when using nftables firewall")
301+
}
302+
303+
// The NIC's network may be a non-default project, so lookup project and get network's project name.
304+
networkProjectName, _, err := project.NetworkProject(d.state.DB.Cluster, instConf.Project().Name)
305+
if err != nil {
306+
return fmt.Errorf("Failed loading network project name: %w", err)
307+
}
308+
309+
err = acl.Exists(d.state, networkProjectName, util.SplitNTrimSpace(d.config["security.acls"], ",", -1, true)...)
310+
if err != nil {
311+
return err
312+
}
313+
}
314+
289315
rules := nicValidationRules(requiredFields, optionalFields, instConf)
290316

291317
// Add bridge specific vlan validation.
@@ -445,7 +471,7 @@ func (d *nicBridged) UpdatableFields(oldDevice Type) []string {
445471
return []string{}
446472
}
447473

448-
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes", "ipv4.routes.external", "ipv6.routes.external", "ipv4.address", "ipv6.address", "security.mac_filtering", "security.ipv4_filtering", "security.ipv6_filtering"}
474+
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes", "ipv4.routes.external", "ipv6.routes.external", "ipv4.address", "ipv6.address", "security.mac_filtering", "security.ipv4_filtering", "security.ipv6_filtering", "security.acls", "security.acls.default.egress.action", "security.acls.default.egress.logged", "security.acls.default.ingress.action", "security.acls.default.ingress.logged"}
449475
}
450476

451477
// Add is run when a device is added to a non-snapshot instance whether or not the instance is running.
@@ -851,7 +877,7 @@ func (d *nicBridged) postStop() error {
851877
routes = append(routes, util.SplitNTrimSpace(d.config["ipv6.routes.external"], ",", -1, true)...)
852878
networkNICRouteDelete(bridgeName, routes...)
853879

854-
if util.IsTrue(d.config["security.mac_filtering"]) || util.IsTrue(d.config["security.ipv4_filtering"]) || util.IsTrue(d.config["security.ipv6_filtering"]) {
880+
if util.IsTrue(d.config["security.mac_filtering"]) || util.IsTrue(d.config["security.ipv4_filtering"]) || util.IsTrue(d.config["security.ipv6_filtering"]) || d.config["security.acls"] != "" {
855881
d.removeFilters(d.config)
856882
}
857883

@@ -964,12 +990,12 @@ func (d *nicBridged) setupHostFilters(oldConfig deviceConfig.Device) (revert.Hoo
964990
}
965991

966992
// Remove any old network filters if non-empty oldConfig supplied as part of update.
967-
if oldConfig != nil && (util.IsTrue(oldConfig["security.mac_filtering"]) || util.IsTrue(oldConfig["security.ipv4_filtering"]) || util.IsTrue(oldConfig["security.ipv6_filtering"])) {
993+
if oldConfig != nil && (util.IsTrue(oldConfig["security.mac_filtering"]) || util.IsTrue(oldConfig["security.ipv4_filtering"]) || util.IsTrue(oldConfig["security.ipv6_filtering"]) || oldConfig["security.acls"] != "") {
968994
d.removeFilters(oldConfig)
969995
}
970996

971997
// Setup network filters.
972-
if util.IsTrue(d.config["security.mac_filtering"]) || util.IsTrue(d.config["security.ipv4_filtering"]) || util.IsTrue(d.config["security.ipv6_filtering"]) {
998+
if util.IsTrue(d.config["security.mac_filtering"]) || util.IsTrue(d.config["security.ipv4_filtering"]) || util.IsTrue(d.config["security.ipv6_filtering"]) || d.config["security.acls"] != "" {
973999
err := d.setFilters()
9741000
if err != nil {
9751001
return nil, err
@@ -1059,7 +1085,7 @@ func (d *nicBridged) removeFilters(m deviceConfig.Device) {
10591085
}
10601086

10611087
// setFilters sets up any network level filters defined for the instance.
1062-
// These are controlled by the security.mac_filtering, security.ipv4_Filtering and security.ipv6_filtering config keys.
1088+
// These are controlled by the security.mac_filtering, security.ipv4_Filtering, security.ipv6_filtering and security.acls config keys.
10631089
func (d *nicBridged) setFilters() (err error) {
10641090
if d.config["hwaddr"] == "" {
10651091
return fmt.Errorf("Failed to set network filters: require hwaddr defined")
@@ -1149,7 +1175,16 @@ func (d *nicBridged) setFilters() (err error) {
11491175
return err
11501176
}
11511177

1152-
err = d.state.Firewall.InstanceSetupBridgeFilter(d.inst.Project().Name, d.inst.Name(), d.name, d.config["parent"], d.config["host_name"], d.config["hwaddr"], IPv4Nets, IPv6Nets, d.network != nil)
1178+
var aclRules []firewallDrivers.ACLRule
1179+
1180+
if config["security.acls"] != "" {
1181+
aclRules, err = acl.FirewallACLRules(d.state, d.name, d.inst.Project().Name, d.config)
1182+
if err != nil {
1183+
return err
1184+
}
1185+
}
1186+
1187+
err = d.state.Firewall.InstanceSetupBridgeFilter(d.inst.Project().Name, d.inst.Name(), d.name, d.config["parent"], d.config["host_name"], d.config["hwaddr"], IPv4Nets, IPv6Nets, d.network != nil, util.IsTrue(config["security.mac_filtering"]), aclRules)
11531188
if err != nil {
11541189
return err
11551190
}

0 commit comments

Comments
 (0)