[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