[lxc-devel] [lxd/master] Introduce security.mac_filtering
stgraber on Github
lxc-bot at linuxcontainers.org
Tue Sep 27 04:39:23 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 520 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160927/19d3f55c/attachment.bin>
-------------- next part --------------
From e24bf440a8c8b991300961bca7de94525ad4fbd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 26 Sep 2016 17:41:25 -0400
Subject: [PATCH] Introduce security.mac_filtering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This option for bridged nic devices turns on MAC filtering on the host,
preventing the container from sending traffic for a MAC other than the
one known by LXD.
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
doc/api-extensions.md | 1 +
doc/configuration.md | 27 +++++-----
lxd/container.go | 2 +
lxd/container_lxc.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 152 insertions(+), 14 deletions(-)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 268bf44..83818a9 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -114,3 +114,4 @@ This includes:
* DELETE /1.0/networks/<entry> (see rest-api.md for details)
* ipv4.address property on "nic" type devices (when nictype is "bridged")
* ipv6.address property on "nic" type devices (when nictype is "bridged")
+ * security.mac\_filtering property on "nic" type devices (when nictype is "bridged")
diff --git a/doc/configuration.md b/doc/configuration.md
index 1faa622..ce0036d 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -195,19 +195,20 @@ LXD supports different kind of network devices:
Different network interface types have different additional properties, the current list is:
-Key | Type | Default | Required | Used by | API extension | Description
-:-- | :-- | :-- | :-- | :-- | :-- | :--
-nictype | string | - | yes | all | - | The device type, one of "physical", "bridged", "macvlan" or "p2p"
-limits.ingress | string | - | no | bridged, p2p | - | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
-limits.egress | string | - | no | bridged, p2p | - | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
-limits.max | string | - | no | bridged, p2p | - | Same as modifying both limits.read and limits.write
-name | string | kernel assigned | no | all | - | The name of the interface inside the container
-host\_name | string | randomly assigned | no | bridged, p2p, macvlan | - | The name of the interface inside the host
-hwaddr | string | randomly assigned | no | all | - | The MAC address of the new interface
-mtu | integer | parent MTU | no | all | - | The MTU of the new interface
-parent | string | - | yes | physical, bridged, macvlan | - | The name of the host device or bridge
-ipv4.address | string | - | no | bridged | network | An IPv4 address to assign to the container through DHCP
-ipv6.address | string | - | no | bridged | network | An IPv6 address to assign to the container through DHCP
+Key | Type | Default | Required | Used by | API extension | Description
+:-- | :-- | :-- | :-- | :-- | :-- | :--
+nictype | string | - | yes | all | - | The device type, one of "physical", "bridged", "macvlan" or "p2p"
+limits.ingress | string | - | no | bridged, p2p | - | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
+limits.egress | string | - | no | bridged, p2p | - | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
+limits.max | string | - | no | bridged, p2p | - | Same as modifying both limits.read and limits.write
+name | string | kernel assigned | no | all | - | The name of the interface inside the container
+host\_name | string | randomly assigned | no | bridged, p2p, macvlan | - | The name of the interface inside the host
+hwaddr | string | randomly assigned | no | all | - | The MAC address of the new interface
+mtu | integer | parent MTU | no | all | - | The MTU of the new interface
+parent | string | - | yes | physical, bridged, macvlan | - | The name of the host device or bridge
+ipv4.address | string | - | no | bridged | network | An IPv4 address to assign to the container through DHCP
+ipv6.address | string | - | no | bridged | network | An IPv6 address to assign to the container through DHCP
+security.mac\_filtering | boolean | false | no | bridged | network | Prevent the container from spoofing another's MAC address
### Type: disk
Disk entries are essentially mountpoints inside the container. They can
diff --git a/lxd/container.go b/lxd/container.go
index 2d61b51..cd6060f 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -106,6 +106,8 @@ func containerValidDeviceConfigKey(t, k string) bool {
return true
case "ipv6.address":
return true
+ case "security.mac_filtering":
+ return true
default:
return false
}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 99282dc..c95fa0a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -873,8 +873,16 @@ func (c *containerLXC) initLXC() error {
}
// Host Virtual NIC name
+ vethName := ""
if m["host_name"] != "" {
- err = lxcSetConfigItem(cc, "lxc.network.veth.pair", m["host_name"])
+ vethName = m["host_name"]
+ } else if shared.IsTrue(m["security.mac_filtering"]) {
+ // We need a known device name for MAC filtering
+ vethName = deviceNextVeth()
+ }
+
+ if vethName != "" {
+ err = lxcSetConfigItem(cc, "lxc.network.veth.pair", vethName)
if err != nil {
return err
}
@@ -1177,6 +1185,7 @@ func (c *containerLXC) startCommon() (string, error) {
// Cleanup any existing leftover devices
c.removeUnixDevices()
c.removeDiskDevices()
+ c.removeNetworkFilters()
var usbs []usbDevice
@@ -1264,6 +1273,44 @@ func (c *containerLXC) startCommon() (string, error) {
return "", err
}
}
+ } else if m["type"] == "nic" {
+ if m["nictype"] == "bridged" && shared.IsTrue(m["security.mac_filtering"]) {
+ m, err = c.fillNetworkDevice(k, m)
+ if err != nil {
+ return "", err
+ }
+
+ // Read device name from config
+ vethName := ""
+ for i := 0; i < len(c.c.ConfigItem("lxc.network")); i++ {
+ val := c.c.ConfigItem(fmt.Sprintf("lxc.network.%d.hwaddr", i))
+ if len(val) == 0 || val[0] != m["hwaddr"] {
+ continue
+ }
+
+ val = c.c.ConfigItem(fmt.Sprintf("lxc.network.%d.link", i))
+ if len(val) == 0 || val[0] != m["parent"] {
+ continue
+ }
+
+ val = c.c.ConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))
+ if len(val) == 0 {
+ continue
+ }
+
+ vethName = val[0]
+ break
+ }
+
+ if vethName == "" {
+ return "", fmt.Errorf("Failed to find device name for mac_filtering")
+ }
+
+ err = c.createNetworkFilter(vethName, m["parent"], m["hwaddr"])
+ if err != nil {
+ return "", err
+ }
+ }
}
}
@@ -1746,6 +1793,12 @@ func (c *containerLXC) OnStop(target string) error {
shared.LogError("Unable to remove disk devices", log.Ctx{"err": err})
}
+ // Clean all network filters
+ err = c.removeNetworkFilters()
+ if err != nil {
+ shared.LogError("Unable to remove network filters", log.Ctx{"err": err})
+ }
+
// Reboot the container
if target == "reboot" {
@@ -2055,6 +2108,7 @@ func (c *containerLXC) cleanup() {
// Unmount any leftovers
c.removeUnixDevices()
c.removeDiskDevices()
+ c.removeNetworkFilters()
// Remove the security profiles
AADeleteProfile(c)
@@ -4303,6 +4357,14 @@ func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string
return "", fmt.Errorf("Failed to bring up the interface: %s", err)
}
+ // Set the filter
+ if m["nictype"] == "bridged" && shared.IsTrue(m["security.mac_filtering"]) {
+ err = c.createNetworkFilter(dev, m["parent"], m["hwaddr"])
+ if err != nil {
+ return "", err
+ }
+ }
+
return dev, nil
}
@@ -4441,6 +4503,70 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
return newDevice, nil
}
+func (c *containerLXC) createNetworkFilter(name string, bridge string, hwaddr string) error {
+ err := shared.RunCommand("ebtables", "-A", "FORWARD", "-s", "!", hwaddr, "-i", name, "-o", bridge, "-j", "DROP")
+ if err != nil {
+ return err
+ }
+
+ err = shared.RunCommand("ebtables", "-A", "INPUT", "-s", "!", hwaddr, "-i", name, "-j", "DROP")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *containerLXC) removeNetworkFilter(hwaddr string, bridge string) error {
+ out, err := exec.Command("ebtables", "-L", "--Lmac2", "--Lx").Output()
+ for _, line := range strings.Split(string(out), "\n") {
+ line = strings.TrimSpace(line)
+ fields := strings.Fields(line)
+
+ if len(fields) == 12 {
+ match := []string{"ebtables", "-t", "filter", "-A", "INPUT", "-s", "!", hwaddr, "-i", fields[9], "-j", "DROP"}
+ if reflect.DeepEqual(fields, match) {
+ fields[3] = "-D"
+ err = shared.RunCommand(fields[0], fields[1:]...)
+ if err != nil {
+ return err
+ }
+ }
+ } else if len(fields) == 14 {
+ match := []string{"ebtables", "-t", "filter", "-A", "FORWARD", "-s", "!", hwaddr, "-i", fields[9], "-o", bridge, "-j", "DROP"}
+ if reflect.DeepEqual(fields, match) {
+ fields[3] = "-D"
+ err = shared.RunCommand(fields[0], fields[1:]...)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *containerLXC) removeNetworkFilters() error {
+ for k, m := range c.expandedDevices {
+ m, err := c.fillNetworkDevice(k, m)
+ if err != nil {
+ return err
+ }
+
+ if m["type"] != "nic" || m["nictype"] != "bridged" {
+ continue
+ }
+
+ err = c.removeNetworkFilter(m["hwaddr"], m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func (c *containerLXC) insertNetworkDevice(name string, m shared.Device) error {
// Load the go-lxc struct
err := c.initLXC()
@@ -4521,6 +4647,14 @@ func (c *containerLXC) removeNetworkDevice(name string, m shared.Device) error {
deviceRemoveInterface(hostName)
}
+ // Remove any filter
+ if m["nictype"] == "bridged" {
+ err = c.removeNetworkFilter(m["hwaddr"], m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+
return nil
}
More information about the lxc-devel
mailing list