mirror of https://github.com/apache/cloudstack.git
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 <wido@widodh.nl>
This commit is contained in:
parent
84e496b4f9
commit
115d6d5dc7
|
|
@ -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:
|
||||
if protocol == 'all':
|
||||
for ip in ips:
|
||||
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)
|
||||
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)
|
||||
|
||||
if allow_any:
|
||||
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':
|
||||
execute("iptables -I " + vmchain + " -m state --state NEW " + direction + " 0.0.0.0/0 -j "+action)
|
||||
execute('iptables -I ' + vmchain + ' -m state --state NEW ' + direction + ' ' + ip + ' -j ' + action)
|
||||
elif protocol != 'icmp':
|
||||
execute("iptables -I " + vmchain + " -p " + protocol + " -m " + protocol + " --dport " + range + " -m state --state NEW -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"
|
||||
execute("iptables -I " + vmchain + " -p icmp --icmp-type " + range + " -j "+action)
|
||||
execute('iptables -I ' + vmchain + ' -p icmp --icmp-type ' + range + ' ' + direction + ' ' + ip + ' -j ' + action)
|
||||
|
||||
for ip in rule['ipv6']:
|
||||
if protocol == 'all':
|
||||
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:
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
}));
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue