The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6450

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Signed-off-by: Louise Montalvo <louanmonta...@gmail.com>

In reference to Issue #6223 point 2.
From 337347843d414058b370f6977b5e97711846ea8f Mon Sep 17 00:00:00 2001
From: Louise Montalvo <louanmonta...@gmail.com>
Date: Wed, 13 Nov 2019 14:11:54 -0600
Subject: [PATCH] lxd/firewall: Create Firewall interface and implement XTables

Signed-off-by: Louise Montalvo <louanmonta...@gmail.com>
---
 lxd/firewall/interfaces.go |  32 ++++
 lxd/firewall/xtables.go    | 344 +++++++++++++++++++++++++++++++++++++
 2 files changed, 376 insertions(+)
 create mode 100644 lxd/firewall/interfaces.go
 create mode 100644 lxd/firewall/xtables.go

diff --git a/lxd/firewall/interfaces.go b/lxd/firewall/interfaces.go
new file mode 100644
index 0000000000..00d3bdde15
--- /dev/null
+++ b/lxd/firewall/interfaces.go
@@ -0,0 +1,32 @@
+package firewall
+
+import (
+       "net"
+
+       "github.com/lxc/lxd/lxd/device"
+       deviceConfig "github.com/lxc/lxd/lxd/device/config"
+)
+
+
+// Firewall represents an LXD firewall.
+type Firewall interface {
+       // Lower-level Functions
+       NetworkClear(name string, protocol string, table string) error
+       InstanceClear(inst device.Instance, protocol string, table string) error
+       VerifyIPv6Module() error
+
+       // Proxy Functions
+       InstanceProxySetupNAT(protocol string, ipAddr net.IP, comment string, 
connType, address, port string, cPort string) error
+
+       // NIC Bridged Functions
+       InstanceNicBridgedRemoveFilters(m deviceConfig.Device, ipv4 net.IP, 
ipv6 net.IP) error
+       InstanceNicBridgedSetFilters(m deviceConfig.Device, ipv4 net.IP, ipv6 
net.IP, name string) error
+
+       // Network Functions
+       NetworkSetupAllowForwarding(protocol string, name string, should_accept 
bool) error
+       NetworkSetupNAT(protocol string, name string, is_after bool, args 
...string) error
+       NetworkSetupIPv4DNSOverrides(name string) error
+       NetworkSetupIPv4DHCPWorkaround(name string) error
+       NetworkSetupIPv6DNSOverrides(name string) error
+       NetworkSetupTunnelNAT(name string, is_after bool, overlaySubnet 
net.IPNet) error
+}
diff --git a/lxd/firewall/xtables.go b/lxd/firewall/xtables.go
new file mode 100644
index 0000000000..d440a495d1
--- /dev/null
+++ b/lxd/firewall/xtables.go
@@ -0,0 +1,344 @@
+package firewall
+
+import (
+       "fmt"
+       "net"
+       "strings"
+       "encoding/hex"
+
+       "github.com/lxc/lxd/lxd/iptables"
+       "github.com/lxc/lxd/lxd/device"
+       deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/shared"
+)
+
+
+// XTables is an implmentation of LXD firewall using {ip, ip6, eb}tables
+type XTables struct {}
+
+
+// Lower-level Functions
+
+// NetworkClear removes network rules.
+func (xt *XTables) NetworkClear(name string, protocol string, table string) 
error {
+       return iptables.NetworkClear(protocol, name, table)
+}
+
+// Removes rules all rules for the given instance.
+func (xt *XTables) InstanceClear(inst device.Instance, protocol string, table 
string) error {
+       return iptables.ContainerClear(protocol, fmt.Sprintf("%s (%s)", 
inst.Name(), inst.Project()), table)
+}
+
+// VerifyIPv6Module checks to see if the ipv6 kernel module is present.
+func (xt *XTables) VerifyIPv6Module() error {
+       // Check br_netfilter is loaded and enabled for IPv6.
+       sysctlPath := "bridge/bridge-nf-call-ip6tables"
+       sysctlVal, err := device.NetworkSysctlGet(sysctlPath)
+       if err != nil {
+               return fmt.Errorf("Error reading net sysctl %s: %v", 
sysctlPath, err)
+       }
+
+       if sysctlVal != "1\n" {
+               return fmt.Errorf("security.ipv6_filtering requires 
br_netfilter and sysctl net.bridge.bridge-nf-call-ip6tables=1")
+       }
+
+       return nil
+}
+
+
+// Proxy Functions
+
+// ProxySetupNAT creates a default NAT setup.
+func (xt *XTables) InstanceProxySetupNAT(protocol string, ipAddr net.IP, 
comment string, connType, address, port string, cPort string) error {
+       toDest := fmt.Sprintf("%s:%s", ipAddr, cPort)
+       if protocol == "ipv6" {
+               toDest = fmt.Sprintf("[%s]:%s", ipAddr, cPort)
+       }
+
+       // outbound <-> container
+       err := iptables.ContainerPrepend(protocol, comment, "nat", 
"PREROUTING", "-p", connType, "--destination", address, "--dport", port, "-j", 
"DNAT", "--to-destination", toDest)
+       if err != nil {
+               return err
+       }
+
+       // host <-> container
+       err = iptables.ContainerPrepend(protocol, comment, "nat", "OUTPUT", 
"-p", connType, "--destination", address, "--dport", port, "-j", "DNAT", 
"--to-destination", toDest)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+
+// NIC Bridged Functions
+
+// Removes any non-standard rules from the nic instance.
+func (xt *XTables) InstanceNicBridgedRemoveFilters(m deviceConfig.Device, ipv4 
net.IP, ipv6 net.IP) error {
+       // Get a current list of rules active on the host.
+       out, err := shared.RunCommand("ebtables", "--concurrent", "-L", 
"--Lmac2", "--Lx")
+       if err != nil {
+               return fmt.Errorf("Failed to remove network filters for %s: 
%v", m["name"], err)
+       }
+
+       // Get a list of rules that we would have applied on instance start.
+       rules := generateFilterEbtablesRules(m, ipv4, ipv6)
+
+       errs := []error{}
+       // Iterate through each active rule on the host and try and match it to 
one the LXD rules.
+       for _, line := range strings.Split(out, "\n") {
+               line = strings.TrimSpace(line)
+               fields := strings.Fields(line)
+               fieldsLen := len(fields)
+
+               for _, rule := range rules {
+                       // Rule doesn't match if the field lenths aren't the 
same, move on.
+                       if len(rule) != fieldsLen {
+                               continue
+                       }
+
+                       // Check whether active rule matches one of our rules 
to delete.
+                       if !matchEbtablesRule(fields, rule, true) {
+                               continue
+                       }
+
+                       // If we get this far, then the current host rule 
matches one of our LXD
+                       // rules, so we should run the modified command to 
delete it.
+                       _, err = shared.RunCommand(fields[0], 
append([]string{"--concurrent"}, fields[1:]...)...)
+                       if err != nil {
+                               errs = append(errs, err)
+                       }
+               }
+       }
+
+       if len(errs) > 0 {
+               return fmt.Errorf("Failed to remove network filters rule for 
%s: %v", m["name"], errs)
+       }
+
+       return nil
+}
+
+// Sets the nic rules to standard filtering.
+func (xt *XTables) InstanceNicBridgedSetFilters(m deviceConfig.Device, ipv4 
net.IP, ipv6 net.IP, name string) error {
+       rules := generateFilterEbtablesRules(m, ipv4, ipv6)
+       for _, rule := range rules {
+               _, err := shared.RunCommand(rule[0], 
append([]string{"--concurrent"}, rule[1:]...)...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       rules, err := generateFilterIptablesRules(m, ipv6)
+       if err != nil {
+               return err
+       }
+
+       for _, rule := range rules {
+               err = iptables.ContainerPrepend(rule[0], fmt.Sprintf("%s - 
%s_filtering", name, rule[0]), "filter", rule[1], rule[2:]...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+
+// Network Functions
+
+// Allow forwarding
+func (xt *XTables) NetworkSetupAllowForwarding(protocol string, name string, 
should_accept bool) error {
+       forward_type := "REJECT"
+       if should_accept {
+               forward_type = "ACCEPT"
+       }
+
+       err := iptables.NetworkPrepend(protocol, name, "", "FORWARD", "-i", 
name, "-j", forward_type)
+       if err != nil {
+               return err
+       }
+
+       err = iptables.NetworkPrepend(protocol, name, "", "FORWARD", "-o", 
name, "-j", forward_type)
+       if err != nil {
+               return err
+       }
+
+       return err
+}
+
+// Configure NAT
+func (xt *XTables) NetworkSetupNAT(protocol string, name string, is_after 
bool, args ...string) error {
+       if is_after {
+               err := iptables.NetworkAppend(protocol, name, "nat", 
"POSTROUTING", args...)
+               if err != nil {
+                       return err
+               }
+       } else {
+               err := iptables.NetworkPrepend(protocol, name, "nat", 
"POSTROUTING", args...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// Setup basic iptables overrides for DHCP/DNS
+func (xt *XTables) NetworkSetupIPv4DNSOverrides(name string) error {
+       rules := [][]string{
+               {"ipv4", name, "", "INPUT", "-i", name, "-p", "udp", "--dport", 
"67", "-j", "ACCEPT"},
+               {"ipv4", name, "", "INPUT", "-i", name, "-p", "udp", "--dport", 
"53", "-j", "ACCEPT"},
+               {"ipv4", name, "", "INPUT", "-i", name, "-p", "tcp", "--dport", 
"53", "-j", "ACCEPT"},
+               {"ipv4", name, "", "OUTPUT", "-o", name, "-p", "udp", 
"--sport", "67", "-j", "ACCEPT"},
+               {"ipv4", name, "", "OUTPUT", "-o", name, "-p", "udp", 
"--sport", "53", "-j", "ACCEPT"},
+               {"ipv4", name, "", "OUTPUT", "-o", name, "-p", "tcp", 
"--sport", "53", "-j", "ACCEPT"}}
+
+       for _, rule := range rules {
+               err := iptables.NetworkPrepend(rule[0], rule[1], rule[2], 
rule[3], rule[4:]...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// Attempt a workaround for broken DHCP clients
+func (xt *XTables) NetworkSetupIPv4DHCPWorkaround(name string) error {
+       return iptables.NetworkPrepend("ipv4", name, "mangle", "POSTROUTING", 
"-o", name, "-p", "udp", "--dport", "68", "-j", "CHECKSUM", "--checksum-fill")
+}
+
+// Setup basic iptables overrides for DHCP/DNS
+func (xt *XTables) NetworkSetupIPv6DNSOverrides(name string) error {
+       rules := [][]string{
+               {"ipv6", name, "", "INPUT", "-i", name, "-p", "udp", "--dport", 
"547", "-j", "ACCEPT"},
+               {"ipv6", name, "", "INPUT", "-i", name, "-p", "udp", "--dport", 
"53", "-j", "ACCEPT"},
+               {"ipv6", name, "", "INPUT", "-i", name, "-p", "tcp", "--dport", 
"53", "-j", "ACCEPT"},
+               {"ipv6", name, "", "OUTPUT", "-o", name, "-p", "udp", 
"--sport", "547", "-j", "ACCEPT"},
+               {"ipv6", name, "", "OUTPUT", "-o", name, "-p", "udp", 
"--sport", "53", "-j", "ACCEPT"},
+               {"ipv6", name, "", "OUTPUT", "-o", name, "-p", "tcp", 
"--sport", "53", "-j", "ACCEPT"}}
+
+       for _, rule := range rules {
+               err := iptables.NetworkPrepend(rule[0], rule[1], rule[2], 
rule[3], rule[4:]...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// Configure Tunnel NAT
+func (xt *XTables) NetworkSetupTunnelNAT(name string, is_after bool, 
overlaySubnet net.IPNet) error {
+       if is_after {
+               err := iptables.NetworkAppend("ipv4", name, "nat", 
"POSTROUTING", "-s", overlaySubnet.String(), "!", "-d", overlaySubnet.String(), 
"-j", "MASQUERADE")
+               if err != nil {
+                       return err
+               }
+       } else {
+               err := iptables.NetworkPrepend("ipv4", name, "nat", 
"POSTROUTING", "-s", overlaySubnet.String(), "!", "-d", overlaySubnet.String(), 
"-j", "MASQUERADE")
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+
+// Helper Functions
+
+// generateFilterEbtablesRules returns a customised set of ebtables filter 
rules based on the device.
+func generateFilterEbtablesRules(m deviceConfig.Device, ipv4 net.IP, ipv6 
net.IP) [][]string {
+       // MAC source filtering rules. Blocks any packet coming from instance 
with an incorrect Ethernet source MAC.
+       // This is required for IP filtering too.
+       rules := [][]string{
+               {"ebtables", "-t", "filter", "-A", "INPUT", "-s", "!", 
m["hwaddr"], "-i", m["host_name"], "-j", "DROP"},
+               {"ebtables", "-t", "filter", "-A", "FORWARD", "-s", "!", 
m["hwaddr"], "-i", m["host_name"], "-j", "DROP"},
+       }
+
+       if shared.IsTrue(m["security.ipv4_filtering"]) && ipv4 != nil {
+               rules = append(rules,
+                       // Prevent ARP MAC spoofing (prevents the instance 
poisoning the ARP cache of its neighbours with a MAC address that isn't its 
own).
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "ARP", "-i", m["host_name"], "--arp-mac-src", "!", m["hwaddr"], "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "ARP", "-i", m["host_name"], "--arp-mac-src", "!", m["hwaddr"], "-j", 
"DROP"},
+                       // Prevent ARP IP spoofing (prevents the instance 
redirecting traffic for IPs that are not its own).
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", ipv4.String(), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", ipv4.String(), "-j", 
"DROP"},
+                       // Allow DHCPv4 to the host only. This must come before 
the IP source filtering rules below.
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv4", "-s", m["hwaddr"], "-i", m["host_name"], "--ip-src", "0.0.0.0", 
"--ip-dst", "255.255.255.255", "--ip-proto", "udp", "--ip-dport", "67", "-j", 
"ACCEPT"},
+                       // IP source filtering rules. Blocks any packet coming 
from instance with an incorrect IP source address.
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", ipv4.String(), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", ipv4.String(), "-j", 
"DROP"},
+               )
+       }
+
+       if shared.IsTrue(m["security.ipv6_filtering"]) && ipv6 != nil {
+               rules = append(rules,
+                       // Allow DHCPv6 and Router Solicitation to the host 
only. This must come before the IP source filtering rules below.
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-s", m["hwaddr"], "-i", m["host_name"], "--ip6-src", 
"fe80::/ffc0::", "--ip6-dst", 
"ff02::1:2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "--ip6-proto", "udp", 
"--ip6-dport", "547", "-j", "ACCEPT"},
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-s", m["hwaddr"], "-i", m["host_name"], "--ip6-src", 
"fe80::/ffc0::", "--ip6-dst", 
"ff02::2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "--ip6-proto", "ipv6-icmp", 
"--ip6-icmp-type", "router-solicitation", "-j", "ACCEPT"},
+                       // IP source filtering rules. Blocks any packet coming 
from instance with an incorrect IP source address.
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ipv6.String()), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ipv6.String()), "-j", 
"DROP"},
+               )
+       }
+
+       return rules
+}
+
+// generateFilterIptablesRules returns a customised set of iptables filter 
rules based on the device.
+func generateFilterIptablesRules(m deviceConfig.Device, ipv6 net.IP) (rules 
[][]string, err error) {
+       mac, err := net.ParseMAC(m["hwaddr"])
+       if err != nil {
+               return
+       }
+
+       macHex := hex.EncodeToString(mac)
+
+       // These rules below are implemented using ip6tables because the 
functionality to inspect
+       // the contents of an ICMPv6 packet does not exist in ebtables (unlike 
for IPv4 ARP).
+       // Additionally, ip6tables doesn't really provide a nice way to do what 
we need here, so we
+       // have resorted to doing a raw hex comparison of the packet contents 
at fixed positions.
+       // If these rules are not added then it is possible to hijack traffic 
for another IP that is
+       // not assigned to the instance by sending a specially crafted 
gratuitous NDP packet with
+       // correct source address and MAC at the IP & ethernet layers, but a 
fraudulent IP or MAC
+       // inside the ICMPv6 NDP packet.
+       if shared.IsTrue(m["security.ipv6_filtering"]) && ipv6 != nil {
+               ipv6Hex := hex.EncodeToString(ipv6)
+
+               rules = append(rules,
+                       // Prevent Neighbor Advertisement IP spoofing (prevents 
the instance redirecting traffic for IPs that are not its own).
+                       []string{"ipv6", "INPUT", "-i", m["parent"], "-p", 
"ipv6-icmp", "-m", "physdev", "--physdev-in", m["host_name"], "-m", "icmp6", 
"--icmpv6-type", "136", "-m", "string", "!", "--hex-string", 
fmt.Sprintf("|%s|", ipv6Hex), "--algo", "bm", "--from", "48", "--to", "64", 
"-j", "DROP"},
+                       []string{"ipv6", "FORWARD", "-i", m["parent"], "-p", 
"ipv6-icmp", "-m", "physdev", "--physdev-in", m["host_name"], "-m", "icmp6", 
"--icmpv6-type", "136", "-m", "string", "!", "--hex-string", 
fmt.Sprintf("|%s|", ipv6Hex), "--algo", "bm", "--from", "48", "--to", "64", 
"-j", "DROP"},
+                       // Prevent Neighbor Advertisement MAC spoofing 
(prevents the instance poisoning the NDP cache of its neighbours with a MAC 
address that isn't its own).
+                       []string{"ipv6", "INPUT", "-i", m["parent"], "-p", 
"ipv6-icmp", "-m", "physdev", "--physdev-in", m["host_name"], "-m", "icmp6", 
"--icmpv6-type", "136", "-m", "string", "!", "--hex-string", 
fmt.Sprintf("|%s|", macHex), "--algo", "bm", "--from", "66", "--to", "72", 
"-j", "DROP"},
+                       []string{"ipv6", "FORWARD", "-i", m["parent"], "-p", 
"ipv6-icmp", "-m", "physdev", "--physdev-in", m["host_name"], "-m", "icmp6", 
"--icmpv6-type", "136", "-m", "string", "!", "--hex-string", 
fmt.Sprintf("|%s|", macHex), "--algo", "bm", "--from", "66", "--to", "72", 
"-j", "DROP"},
+               )
+       }
+
+       return
+}
+
+// matchEbtablesRule compares an active rule to a supplied match rule to see 
if they match.
+// If deleteMode is true then the "-A" flag in the active rule will be 
modified to "-D" and will
+// not be part of the equality match. This allows delete commands to be 
generated from dumped add commands.
+func matchEbtablesRule(activeRule []string, matchRule []string, deleteMode 
bool) bool {
+       for i := range matchRule {
+               // Active rules will be dumped in "add" format, we need to 
detect
+               // this and switch it to "delete" mode if requested. If this 
has already been
+               // done then move on, as we don't want to break the comparison 
below.
+               if deleteMode && (activeRule[i] == "-A" || activeRule[i] == 
"-D") {
+                       activeRule[i] = "-D"
+                       continue
+               }
+
+               // Check the match rule field matches the active rule field.
+               // If they don't match, then this isn't one of our rules.
+               if activeRule[i] != matchRule[i] {
+                       return false
+               }
+       }
+
+       return true
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to