[lxc-devel] [lxd/master] Firewall: nftables driver

tomponline on Github lxc-bot at linuxcontainers.org
Wed Feb 12 17:30:20 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 4824 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200212/ca488bff/attachment.bin>
-------------- next part --------------
From 912fb4ea74231759d16b15cb8a3546035f4d00c4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 08:51:34 +0000
Subject: [PATCH 1/6] lxd/firewall/drivers/drivers/nftables: Initial structure

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/firewall/drivers/drivers_nftables.go | 55 ++++++++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100644 lxd/firewall/drivers/drivers_nftables.go

diff --git a/lxd/firewall/drivers/drivers_nftables.go b/lxd/firewall/drivers/drivers_nftables.go
new file mode 100644
index 0000000000..d1435a8c76
--- /dev/null
+++ b/lxd/firewall/drivers/drivers_nftables.go
@@ -0,0 +1,55 @@
+package drivers
+
+import (
+	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"net"
+)
+
+// Nftables is an implmentation of LXD firewall using nftables.
+type Nftables struct{}
+
+// NetworkSetupForwardingPolicy allows forwarding dependent on boolean argument
+func (d Nftables) NetworkSetupForwardingPolicy(networkName string, ipVersion uint, allow bool) error {
+	return nil
+}
+
+// NetworkSetupOutboundNAT configures outbound NAT.
+// If srcIP is non-nil then SNAT is used with the specified address, otherwise MASQUERADE mode is used.
+func (d Nftables) NetworkSetupOutboundNAT(networkName string, subnet *net.IPNet, srcIP net.IP, append bool) error {
+	return nil
+}
+
+// NetworkSetupDHCPDNSAccess sets up basic iptables overrides for DHCP/DNS.
+func (d Nftables) NetworkSetupDHCPDNSAccess(networkName string, ipVersion uint) error {
+	return nil
+}
+
+// NetworkSetupDHCPv4Checksum attempts a workaround for broken DHCP clients.
+func (d Nftables) NetworkSetupDHCPv4Checksum(networkName string) error {
+	return nil
+}
+
+// NetworkClear removes network rules from filter, mangle and nat tables.
+func (d Nftables) NetworkClear(networkName string, ipVersion uint) error {
+	return nil
+}
+
+// InstanceSetupBridgeFilter sets up the filter rules to apply bridged device IP filtering.
+func (d Nftables) InstanceSetupBridgeFilter(projectName, instanceName, deviceName, parentName, hostName, hwAddr string, IPv4, IPv6 net.IP) error {
+	return nil
+}
+
+// InstanceClearBridgeFilter removes any filter rules that were added to apply bridged device IP filtering.
+func (d Nftables) InstanceClearBridgeFilter(projectName, instanceName, deviceName, parentName, hostName, hwAddr string, IPv4, IPv6 net.IP) error {
+	return nil
+}
+
+// InstanceSetupProxyNAT creates DNAT rules for proxy devices.
+func (d Nftables) InstanceSetupProxyNAT(projectName, instanceName, deviceName string, listen, connect *deviceConfig.ProxyAddress) error {
+	return nil
+}
+
+// InstanceClearProxyNAT remove DNAT rules for proxy devices.
+func (d Nftables) InstanceClearProxyNAT(projectName, instanceName, deviceName string) error {
+	return nil
+}

From 0179c5a3f8c9d3594ca51e047ae2666cb4a207f5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 17:05:51 +0000
Subject: [PATCH 2/6] lxd/daemon: Checks firewall is loaded OK

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/daemon.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 1ac9175d18..44ee367cbb 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -783,7 +783,10 @@ func (d *Daemon) init() error {
 		return errors.Wrap(err, "failed to open cluster database")
 	}
 
-	d.firewall = firewall.New()
+	d.firewall, err = firewall.New()
+	if err != nil {
+		return errors.Wrap(err, "Failed to load firewall")
+	}
 
 	err = cluster.NotifyUpgradeCompleted(d.State(), certInfo)
 	if err != nil {

From 8471c0405e0cba1174b8555fb4212a2881d76dfd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 17:06:09 +0000
Subject: [PATCH 3/6] lxd/state/testing: firewall.New usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/state/testing.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/state/testing.go b/lxd/state/testing.go
index b0bc97fed5..d6e69f6f51 100644
--- a/lxd/state/testing.go
+++ b/lxd/state/testing.go
@@ -26,7 +26,8 @@ func NewTestState(t *testing.T) (*State, func()) {
 		osCleanup()
 	}
 
-	state := NewState(node, cluster, nil, os, nil, nil, nil, firewall.New(), nil)
+	fw, _ := firewall.New()
+	state := NewState(node, cluster, nil, os, nil, nil, nil, fw, nil)
 
 	return state, cleanup
 }

From 86531f9badf2b1f9c214690ebba041257ea3c48f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 17:06:24 +0000
Subject: [PATCH 4/6] lxd/network/network: Handle errors during firewall setup

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network.go | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/lxd/network/network.go b/lxd/network/network.go
index 40612fdc19..3510bec05a 100644
--- a/lxd/network/network.go
+++ b/lxd/network/network.go
@@ -350,12 +350,18 @@ func (n *Network) setup(oldConfig map[string]string) error {
 	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
 		if n.HasDHCPv4() && n.HasIPv4Firewall() {
 			// Setup basic iptables overrides for DHCP/DNS
-			n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 4)
+			err = n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 4)
+			if err != nil {
+				return err
+			}
 		}
 
 		// Attempt a workaround for broken DHCP clients
 		if n.HasIPv4Firewall() {
-			n.state.Firewall.NetworkSetupDHCPv4Checksum(n.name)
+			err = n.state.Firewall.NetworkSetupDHCPv4Checksum(n.name)
+			if err != nil {
+				return err
+			}
 		}
 
 		// Allow forwarding
@@ -532,7 +538,10 @@ func (n *Network) setup(oldConfig map[string]string) error {
 		if n.HasDHCPv6() {
 			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
 				// Setup basic iptables overrides for DHCP/DNS
-				n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 6)
+				err = n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 6)
+				if err != nil {
+					return err
+				}
 			}
 
 			// Build DHCP configuration

From e7df8d9e009af708b560a288c0c4588dddfc2984 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 17:06:41 +0000
Subject: [PATCH 5/6] lxd/firewall/firewall/load: Add nftable support

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/firewall/firewall_load.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/firewall/firewall_load.go b/lxd/firewall/firewall_load.go
index 3eb3543466..f30d28e522 100644
--- a/lxd/firewall/firewall_load.go
+++ b/lxd/firewall/firewall_load.go
@@ -5,7 +5,8 @@ import (
 )
 
 // New returns an appropriate firewall implementation.
-func New() Firewall {
+func New() (Firewall, error) {
 	// TODO: Issue #6223: add startup logic to choose xtables or nftables
-	return drivers.XTables{}
+	d := drivers.Nftables{}
+	return d, nil
 }

From dd6daef0b6cf20cea26a278b0bb4e7ea953bf3c0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 12 Feb 2020 17:07:09 +0000
Subject: [PATCH 6/6] lxd/firewall/drivers/drivers/nftables: Adds nftables
 driver

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/firewall/drivers/drivers_nftables.go      | 134 +++++++++++++++++-
 .../drivers/drivers_nftables_templates.go     |  39 +++++
 2 files changed, 168 insertions(+), 5 deletions(-)
 create mode 100644 lxd/firewall/drivers/drivers_nftables_templates.go

diff --git a/lxd/firewall/drivers/drivers_nftables.go b/lxd/firewall/drivers/drivers_nftables.go
index d1435a8c76..d426666747 100644
--- a/lxd/firewall/drivers/drivers_nftables.go
+++ b/lxd/firewall/drivers/drivers_nftables.go
@@ -1,36 +1,160 @@
 package drivers
 
 import (
-	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"fmt"
 	"net"
+	"strings"
+
+	"github.com/pkg/errors"
+
+	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/shared"
 )
 
+const nftablesNamespace = "lxd"
+const nftableContentTemplate = "nftablesContent"
+
 // Nftables is an implmentation of LXD firewall using nftables.
 type Nftables struct{}
 
+// getIPFamily converts IP version number into family name used by nftables.
+func (d Nftables) getIPFamily(ipVersion uint) (string, error) {
+	if ipVersion == 4 {
+		return "ip", nil
+	} else if ipVersion == 6 {
+		return "ip6", nil
+	}
+
+	return "", fmt.Errorf("Invalid IP version")
+}
+
 // NetworkSetupForwardingPolicy allows forwarding dependent on boolean argument
 func (d Nftables) NetworkSetupForwardingPolicy(networkName string, ipVersion uint, allow bool) error {
+	action := "reject"
+	if allow {
+		action = "accept"
+	}
+
+	family, err := d.getIPFamily(ipVersion)
+	if err != nil {
+		return err
+	}
+
+	config := &strings.Builder{}
+	tplFields := map[string]interface{}{
+		"namespace":   nftablesNamespace,
+		"networkName": networkName,
+		"family":      family,
+		"action":      action,
+	}
+
+	nftablesCommonTable.AddParseTree(nftableContentTemplate, nftablesNetFwdPolicy.Tree)
+	err = nftablesCommonTable.Execute(config, tplFields)
+	if err != nil {
+		return errors.Wrapf(err, "Failed loading Forwarding Policy template for network %q (%s)", networkName, family)
+	}
+
+	_, err = shared.RunCommand("nft", config.String())
+	if err != nil {
+		return errors.Wrapf(err, "Failed adding nftables Forwarding Policy rules for network %q (%s)", networkName, family)
+	}
+
 	return nil
 }
 
 // NetworkSetupOutboundNAT configures outbound NAT.
 // If srcIP is non-nil then SNAT is used with the specified address, otherwise MASQUERADE mode is used.
-func (d Nftables) NetworkSetupOutboundNAT(networkName string, subnet *net.IPNet, srcIP net.IP, append bool) error {
+// Append mode is always on and so the append argument is ignored.
+func (d Nftables) NetworkSetupOutboundNAT(networkName string, subnet *net.IPNet, srcIP net.IP, _ bool) error {
+	family := "ip"
+	if subnet.IP.To4() == nil {
+		family = "ip6"
+	}
+
+	// If SNAT IP not supplied then use the IP of the outbound interface (MASQUERADE).
+	srcIPStr := ""
+	if srcIP != nil {
+		srcIPStr = srcIP.String()
+	}
+
+	config := &strings.Builder{}
+	tplFields := map[string]interface{}{
+		"namespace":   nftablesNamespace,
+		"networkName": networkName,
+		"family":      family,
+		"subnet":      subnet.String(),
+		"srcIP":       srcIPStr,
+	}
+
+	nftablesCommonTable.AddParseTree(nftableContentTemplate, nftablesNetOutboundNAT.Tree)
+	err := nftablesCommonTable.Execute(config, tplFields)
+	if err != nil {
+		return errors.Wrapf(err, "Failed loading Outbound NAT template for network %q (%s)", networkName, family)
+	}
+
+	_, err = shared.RunCommand("nft", config.String())
+	if err != nil {
+		return errors.Wrapf(err, "Failed adding nftables Outbound NAT rules for network %q (%s)", networkName, family)
+	}
+
 	return nil
 }
 
-// NetworkSetupDHCPDNSAccess sets up basic iptables overrides for DHCP/DNS.
+// NetworkSetupDHCPDNSAccess sets up basic nftables overrides for DHCP/DNS.
 func (d Nftables) NetworkSetupDHCPDNSAccess(networkName string, ipVersion uint) error {
+	family, err := d.getIPFamily(ipVersion)
+	if err != nil {
+		return err
+	}
+
+	config := &strings.Builder{}
+	tplFields := map[string]interface{}{
+		"namespace":   nftablesNamespace,
+		"networkName": networkName,
+		"family":      family,
+	}
+
+	nftablesCommonTable.AddParseTree(nftableContentTemplate, nftablesNetDHCPDNS.Tree)
+	err = nftablesCommonTable.Execute(config, tplFields)
+	if err != nil {
+		return errors.Wrapf(err, "Failed loading DHCP/DNS Access template for network %q (%s)", networkName, family)
+	}
+
+	_, err = shared.RunCommand("nft", config.String())
+	if err != nil {
+		return errors.Wrapf(err, "Failed adding nftables DHCP/DNS Access rules for network %q (%s)", networkName, family)
+	}
+
 	return nil
 }
 
-// NetworkSetupDHCPv4Checksum attempts a workaround for broken DHCP clients.
+// NetworkSetupDHCPv4Checksum attempts a workaround for broken DHCP clients. No-op as not supported by nftables.
+// See https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#CHECKSUM.
 func (d Nftables) NetworkSetupDHCPv4Checksum(networkName string) error {
 	return nil
 }
 
-// NetworkClear removes network rules from filter, mangle and nat tables.
+// NetworkClear removes the LXD network related chains.
 func (d Nftables) NetworkClear(networkName string, ipVersion uint) error {
+	family, err := d.getIPFamily(ipVersion)
+	if err != nil {
+		return err
+	}
+
+	chains := []string{"fwd", "nat", "in"}
+	for _, chain := range chains {
+		chainName := fmt.Sprintf("%s_%s", chain, networkName)
+
+		// Check if chain exists by attempting to list its rules, and if that succeeds, delete it.
+		_, err := shared.RunCommand("nft", "list", "chain", family, nftablesNamespace, chainName)
+		if err == nil {
+			_, err = shared.RunCommand("nft", "delete", "chain", family, nftablesNamespace, chainName)
+			if err != nil {
+				return errors.Wrapf(err, "Failed clearing nftables rules for network %q, chain %q (%d)", networkName, chainName, ipVersion)
+			}
+		}
+	}
+
 	return nil
 }
 
diff --git a/lxd/firewall/drivers/drivers_nftables_templates.go b/lxd/firewall/drivers/drivers_nftables_templates.go
new file mode 100644
index 0000000000..85157be9b6
--- /dev/null
+++ b/lxd/firewall/drivers/drivers_nftables_templates.go
@@ -0,0 +1,39 @@
+package drivers
+
+import (
+	"text/template"
+)
+
+var nftablesCommonTable = template.Must(template.New("nftablesCommonTable").Parse(`
+table {{.family}} {{.namespace}} {
+	{{- template "nftablesContent" . -}}
+}
+`))
+
+var nftablesNetFwdPolicy = template.Must(template.New("nftablesNetFwdPolicy").Parse(`
+chain fwd_{{.networkName}} {
+	type filter hook forward priority 0; policy accept;
+	oifname "{{.networkName}}" {{.action}}
+	iifname "{{.networkName}}" {{.action}}
+}
+`))
+
+var nftablesNetOutboundNAT = template.Must(template.New("nftablesNetOutboundNAT").Parse(`
+chain nat_{{.networkName}} {
+	type nat hook postrouting priority 100; policy accept;
+	{{- if .srcIP -}}
+	{{.family}} saddr {{.subnet}} {{.family}} daddr != {{.subnet}} snat {{.srcIP}}
+	{{else}}
+	{{.family}} saddr {{.subnet}} {{.family}} daddr != {{.subnet}} masquerade
+	{{- end }}
+}
+`))
+
+var nftablesNetDHCPDNS = template.Must(template.New("nftablesNetDHCPDNS").Parse(`
+chain in_{{.networkName}} {
+	type filter hook input priority 0; policy accept;
+	iifname "{{.networkName}}" tcp dport 53 accept
+	iifname "{{.networkName}}" udp dport 53 accept
+	iifname "{{.networkName}}" udp dport 67 accept
+}
+`))


More information about the lxc-devel mailing list