You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ra...@apache.org on 2017/01/27 00:13:43 UTC

[3/4] git commit: updated refs/heads/master to f10c8bf

CLOUDSTACK-676: IPv6 In -and Egress filtering for Basic Networking

This commit implements Ingress and Egress filtering for IPv6 in
Basic Networking.

It allows for opening and closing ports just as can be done with IPv4.

Rules have to be specified twice, once for IPv4 and once for IPv6, for
example:

- 22 until 22: 0.0.0.0/0
- 22 until 22: ::/0

Egress filtering works the same as with IPv4. When no rule is applied all
traffic is allowed. Otherwise only the specified traffic (with DNS being
the exception) is allowed.

Signed-off-by: Wido den Hollander <wi...@widodh.nl>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/115d6d5d
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/115d6d5d
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/115d6d5d

Branch: refs/heads/master
Commit: 115d6d5dc774715b0d17238dc8e8d9f02017c690
Parents: 84e496b
Author: Wido den Hollander <wi...@widodh.nl>
Authored: Thu Oct 20 10:39:28 2016 +0200
Committer: Wido den Hollander <wi...@widodh.nl>
Committed: Thu Jan 26 15:36:20 2017 +0100

----------------------------------------------------------------------
 scripts/vm/network/security_group.py            | 161 +++++++++++--------
 ui/lib/jquery.validate.additional-methods.js    |   5 +-
 ui/scripts/network.js                           |   4 +-
 ui/scripts/sharedFunctions.js                   |  12 +-
 .../main/java/com/cloud/utils/net/NetUtils.java |   6 +
 .../java/com/cloud/utils/net/NetUtilsTest.java  |   6 +
 6 files changed, 123 insertions(+), 71 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/scripts/vm/network/security_group.py
----------------------------------------------------------------------
diff --git a/scripts/vm/network/security_group.py b/scripts/vm/network/security_group.py
index ca80a55..0e81565 100755
--- a/scripts/vm/network/security_group.py
+++ b/scripts/vm/network/security_group.py
@@ -28,7 +28,7 @@ import re
 import libvirt
 import fcntl
 import time
-from netaddr import IPAddress
+from netaddr import IPAddress, IPNetwork
 from netaddr.core import AddrFormatError
 
 
@@ -542,15 +542,15 @@ def default_network_rules(vm_name, vm_id, vm_ip, vm_ip6, vm_mac, vif, brname, se
         execute('ip6tables -A ' + vmchain_default + ' -m state --state RELATED,ESTABLISHED -j ACCEPT')
 
         # Allow Instances to receive Router Advertisements, send out solicitations, but block any outgoing Advertisement from a Instance
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' --src fe80::/64 --dst ff02::1 -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT')
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' --dst ff02::2 -p icmpv6 --icmpv6-type router-solicitation -j RETURN')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' --src fe80::/64 --dst ff02::1 -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' --dst ff02::2 -p icmpv6 --icmpv6-type router-solicitation -m hl --hl-eq 255 -j RETURN')
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type router-advertisement -j DROP')
 
         # Allow neighbor solicitations and advertisements
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type neighbor-solicitation -j RETURN')
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT')
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type neighbor-advertisement -m set --match-set ' + vm_ip6_set_name + ' src -j RETURN')
-        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j RETURN')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type neighbor-advertisement -m set --match-set ' + vm_ip6_set_name + ' src -m hl --hl-eq 255 -j RETURN')
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT')
 
         # Packets to allow as per RFC4890
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type packet-too-big -m set --match-set ' + vm_ip6_set_name + ' src -j RETURN')
@@ -565,6 +565,9 @@ def default_network_rules(vm_name, vm_id, vm_ip, vm_ip6, vm_mac, vif, brname, se
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --icmpv6-type parameter-problem -m set --match-set ' + vm_ip6_set_name + ' src -j RETURN')
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p icmpv6 --icmpv6-type parameter-problem -j ACCEPT')
 
+        # MLDv2 discovery packets
+        execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p icmpv6 --dst ff02::16 -j RETURN')
+
         # Allow Instances to send out DHCPv6 client messages, but block server messages
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-in ' + vif + ' -p udp --sport 546 --dst ff02::1:2 --src ' + str(ipv6_link_local) + ' -j RETURN')
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -p udp --src fe80::/64 --dport 546 --dst ' + str(ipv6_link_local) + ' -j ACCEPT')
@@ -582,11 +585,6 @@ def default_network_rules(vm_name, vm_id, vm_ip, vm_ip6, vm_mac, vif, brname, se
 
         execute('ip6tables -A ' + vmchain_default + ' -m physdev --physdev-is-bridged --physdev-out ' + vif + ' -j ' + vmchain)
 
-        # For now allow ICMPv6 echo-request, UDP and TCP traffic to the Unicast address of the Instance
-        execute('ip6tables -A ' + vmchain + ' -p icmpv6 --icmpv6-type echo-request -m set --match-set ' + vm_ip6_set_name + ' dst -j ACCEPT')
-        execute('ip6tables -A ' + vmchain + ' -p udp -m set --match-set ' + vm_ip6_set_name + ' dst -j ACCEPT')
-        execute('ip6tables -A ' + vmchain + ' -p tcp -m set --match-set ' + vm_ip6_set_name + ' dst -j ACCEPT')
-
         # Drop all other traffic into the Instance
         execute('ip6tables -A ' + vmchain + ' -j DROP')
     except:
@@ -904,6 +902,42 @@ def remove_rule_log_for_vm(vmName):
 def egress_chain_name(vm_name):
     return vm_name + "-eg"
 
+
+def parse_network_rules(rules):
+  ret = []
+
+  if rules is None or len(rules) == 0:
+    return ret
+
+  lines = rules.split(';')[:-1]
+  for line in lines:
+    tokens = line.split(':', 4)
+    if len(tokens) != 5:
+      continue
+
+    ruletype = tokens[0]
+    protocol = tokens[1]
+    start = int(tokens[2])
+    end = int(tokens[3])
+    cidrs = tokens.pop();
+
+    ipv4 = []
+    ipv6 = []
+    for ip in cidrs.split(","):
+        try:
+            network = IPNetwork(ip)
+            if network.version == 4:
+                ipv4.append(ip)
+            else:
+                ipv6.append(ip)
+        except:
+            pass
+
+    ret.append({'ipv4': ipv4, 'ipv6': ipv6, 'ruletype': ruletype,
+                'start': start, 'end': end, 'protocol': protocol})
+
+  return ret
+
 def add_network_rules(vm_name, vm_id, vm_ip, vm_ip6, signature, seqno, vmMac, rules, vif, brname, sec_ips):
   try:
     vmName = vm_name
@@ -919,83 +953,76 @@ def add_network_rules(vm_name, vm_id, vm_ip, vm_ip6, signature, seqno, vmMac, ru
     if changes[0] or changes[1] or changes[2] or changes[3]:
         default_network_rules(vmName, vm_id, vm_ip, vm_ip6, vmMac, vif, brname, sec_ips)
 
-    if rules == "" or rules == None:
-        lines = []
-    else:
-        lines = rules.split(';')[:-1]
-
     logging.debug("    programming network rules for IP: " + vm_ip + " vmname=" + vm_name)
+
+    vmchain = vm_name
+    egress_chain_name(vm_name)
     try:
-      vmchain = vm_name
-      execute("iptables -F " + vmchain)
-      egress_vmchain = egress_chain_name(vm_name)
-      execute("iptables -F " + egress_vmchain)
+      for chain in [vmchain, egress_vmchain]:
+          execute('iptables -F ' + chain)
+          execute('ip6tables -F ' + chain)
     except:
       logging.debug("Error flushing iptables rules for " + vmchain + ". Presuming firewall rules deleted, re-initializing." )
       default_network_rules(vm_name, vm_id, vm_ip, vm_ip6, vmMac, vif, brname, sec_ips)
-    egressrule = 0
-    for line in lines:
-        tokens = line.split(':')
-        if len(tokens) != 5:
-          continue
-        ruletype = tokens[0]
-        protocol = tokens[1]
-        start = tokens[2]
-        end = tokens[3]
-        cidrs = tokens.pop();
-        ips = cidrs.split(",")
-        ips.pop()
-        allow_any = False
-        if ruletype == 'E':
+
+    egressrule_v4 = 0
+    egressrule_v6 = 0
+
+    for rule in parse_network_rules(rules):
+        start = rule['start']
+        end = rule['end']
+        protocol = rule['protocol']
+
+        if rule['ruletype'] == 'E':
             vmchain = egress_chain_name(vm_name)
             direction = "-d"
             action = "RETURN"
-            egressrule = egressrule + 1
+            if rule['ipv4']:
+                egressrule_v4 =+ 1
+
+            if rule['ipv6']:
+                egressrule_v6 +=1
+
         else:
             vmchain = vm_name
             action = "ACCEPT"
             direction = "-s"
-        if '0.0.0.0/0' in ips:
-            i = ips.index('0.0.0.0/0')
-            del ips[i]
-            allow_any = True
-        range = start + ":" + end
-        if ips:
+
+        range = str(start) + ':' + str(end)
+        if 'icmp' == protocol:
+            range = str(start) + '/' + str(end)
+            if start == -1:
+                range = 'any'
+
+        for ip in rule['ipv4']:
             if protocol == 'all':
-                for ip in ips:
-                    execute("iptables -I " + vmchain + " -m state --state NEW " + direction + " " + ip + " -j "+action)
+                execute('iptables -I ' + vmchain + ' -m state --state NEW ' + direction + ' ' + ip + ' -j ' + action)
             elif protocol != 'icmp':
-                for ip in ips:
-                    execute("iptables -I " + vmchain + " -p " + protocol + " -m " + protocol + " --dport " + range + " -m state --state NEW " + direction + " " + ip + " -j "+ action)
+                execute('iptables -I ' + vmchain + ' -p ' + protocol + ' -m ' + protocol + ' --dport ' + range + ' -m state --state NEW ' + direction + ' ' + ip + ' -j ' + action)
             else:
-                range = start + "/" + end
-                if start == "-1":
-                    range = "any"
-                for ip in ips:
-                    execute("iptables -I " + vmchain + " -p icmp --icmp-type " + range + " " + direction + " " + ip + " -j "+ action)
+                execute('iptables -I ' + vmchain + ' -p icmp --icmp-type ' + range + ' ' + direction + ' ' + ip + ' -j ' + action)
 
-        if allow_any:
+        for ip in rule['ipv6']:
             if protocol == 'all':
-                execute("iptables -I " + vmchain + " -m state --state NEW " + direction + " 0.0.0.0/0 -j "+action)
-            elif protocol != 'icmp':
-                execute("iptables -I " + vmchain + " -p " + protocol + " -m " + protocol + " --dport " + range + " -m state --state NEW -j "+ action)
+                execute('ip6tables -I ' + vmchain + ' -m state --state NEW ' + direction + ' ' + ip + ' -j ' + action)
+            elif 'icmp' != protocol:
+                execute('ip6tables -I ' + vmchain + ' -p ' + protocol + ' -m ' + protocol + ' --dport ' + range + ' -m state --state NEW ' + direction + ' ' + ip + ' -j ' + action)
             else:
-                range = start + "/" + end
-                if start == "-1":
-                    range = "any"
-                execute("iptables -I " + vmchain + " -p icmp --icmp-type " + range + " -j "+action)
+                execute('ip6tables -I ' + vmchain + ' -p icmpv6 --icmpv6-type ' + range + ' ' + direction + ' ' + ip + ' -j ' + action)
 
     egress_vmchain = egress_chain_name(vm_name)
-    if egressrule == 0 :
-        iptables = "iptables -A " + egress_vmchain + " -j RETURN"
-        execute(iptables)
+    if egressrule_v4 == 0 :
+        execute('iptables -A ' + egress_vmchain + ' -j RETURN')
     else:
-        iptables = "iptables -A " + egress_vmchain + " -j DROP"
-        execute(iptables)
+        execute('iptables -A ' + egress_vmchain + ' -j DROP')
 
-    vmchain = vm_name
-    iptables = "iptables -A " + vmchain + " -j DROP"
-    execute(iptables)
+    if egressrule_v6 == 0 :
+        execute('ip6tables -A ' + egress_vmchain + ' -j RETURN')
+    else:
+        execute('ip6tables -A ' + egress_vmchain + ' -j DROP')
+
+    execute('iptables -A ' + vm_name + ' -j DROP')
+    execute('ip6tables -A ' + vm_name + ' -j DROP')
 
     if write_rule_log_for_vm(vmName, vm_id, vm_ip, domId, signature, seqno) == False:
         return 'false'

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/ui/lib/jquery.validate.additional-methods.js
----------------------------------------------------------------------
diff --git a/ui/lib/jquery.validate.additional-methods.js b/ui/lib/jquery.validate.additional-methods.js
index 7491439..811b4f7 100644
--- a/ui/lib/jquery.validate.additional-methods.js
+++ b/ui/lib/jquery.validate.additional-methods.js
@@ -497,6 +497,9 @@ $.validator.addMethod("ipv4", function(value, element) {
 }, "Please enter a valid IP v4 address.");
 
 $.validator.addMethod("ipv6", function(value, element) {
+        if (value == '::')
+            return true;
+
 	return this.optional(element) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value);
 }, "Please enter a valid IP v6 address.");
 
@@ -925,4 +928,4 @@ $.validator.addMethod("ziprange", function(value, element) {
 	return this.optional(element) || /^90[2-5]\d\{2\}-\d{4}$/.test(value);
 }, "Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx");
 
-}));
\ No newline at end of file
+}));

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/ui/scripts/network.js
----------------------------------------------------------------------
diff --git a/ui/scripts/network.js b/ui/scripts/network.js
index dd21cd2..7299a7b 100755
--- a/ui/scripts/network.js
+++ b/ui/scripts/network.js
@@ -4869,7 +4869,7 @@
                                             label: 'label.cidr',
                                             isHidden: true,
                                             validation: {
-                                                ipv4cidr: true
+                                                ipv46cidr: true
                                             }
                                         },
                                         'accountname': {
@@ -5079,7 +5079,7 @@
                                             label: 'label.cidr',
                                             isHidden: true,
                                             validation: {
-                                                ipv4cidr: true
+                                                ipv46cidr: true
                                             }
                                         },
                                         'accountname': {

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/ui/scripts/sharedFunctions.js
----------------------------------------------------------------------
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 88d728e..f845fd8 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -2318,7 +2318,7 @@ $.validator.addMethod("ipv6cidr", function(value, element) {
     if (parts[1] != Number(parts[1]).toString()) //making sure that "", " ", "00", "0 ","2  ", etc. will not pass
         return false;
 
-    if (Number(parts[1]) < 0 || Number(parts[1] > 32))
+    if (Number(parts[1]) < 0 || Number(parts[1] > 128))
         return false;
 
     return true;
@@ -2344,3 +2344,13 @@ $.validator.addMethod("ipv4cidr", function(value, element) {
 
     return true;
 }, "The specified IPv4 CIDR is invalid.");
+
+$.validator.addMethod("ipv46cidr", function(value, element) {
+    if (this.optional(element) && value.length == 0)
+        return true;
+
+    if ($.validator.methods.ipv4cidr.call(this, value, element) || $.validator.methods.ipv6cidr.call(this, value, element))
+        return true;
+
+    return false;
+}, "The specified IPv4/IPv6 CIDR is invalid.");

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/utils/src/main/java/com/cloud/utils/net/NetUtils.java
----------------------------------------------------------------------
diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java
index a2ec411..a014bf7 100644
--- a/utils/src/main/java/com/cloud/utils/net/NetUtils.java
+++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java
@@ -557,6 +557,12 @@ public class NetUtils {
         if (cidr == null || cidr.isEmpty()) {
             return false;
         }
+
+        try {
+            IPv6Network.fromString(cidr);
+            return true;
+        } catch (IllegalArgumentException e) {}
+
         final String[] cidrPair = cidr.split("\\/");
         if (cidrPair.length != 2) {
             return false;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/115d6d5d/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
----------------------------------------------------------------------
diff --git a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
index 4c43751..6d939d5 100644
--- a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
+++ b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
@@ -245,6 +245,10 @@ public class NetUtilsTest {
         assertTrue(NetUtils.isValidCIDR(cidrFirst));
         assertTrue(NetUtils.isValidCIDR(cidrSecond));
         assertTrue(NetUtils.isValidCIDR(cidrThird));
+        assertTrue(NetUtils.isValidCIDR("2001:db8::/64"));
+        assertTrue(NetUtils.isValidCIDR("2001:db8::/48"));
+        assertTrue(NetUtils.isValidCIDR("2001:db8:fff::/56"));
+        assertFalse(NetUtils.isValidCIDR("2001:db8:gggg::/56"));
     }
 
     @Test
@@ -256,6 +260,8 @@ public class NetUtilsTest {
         assertTrue(NetUtils.isValidCidrList(cidrFirst));
         assertTrue(NetUtils.isValidCidrList(cidrSecond));
         assertTrue(NetUtils.isValidCidrList(cidrThird));
+        assertTrue(NetUtils.isValidCidrList("2001:db8::/64,2001:db8:ffff::/48"));
+        assertTrue(NetUtils.isValidCidrList("2001:db8::/64,2001:db8:ffff::/48,192.168.0.0/24"));
     }
 
     @Test