From 19431d31d39e00563d62dc3428fa4553f360b17b Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 16 Dec 2010 17:59:51 -0800 Subject: [PATCH] add security group support for kvm --- .../computing/LibvirtComputingResource.java | 304 +++++---- scripts/vm/network/security_group.py | 602 ++++++++++++++++++ 2 files changed, 748 insertions(+), 158 deletions(-) create mode 100755 scripts/vm/network/security_group.py diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java index 6aba9d27ffa..45e9637ab4d 100644 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java @@ -113,6 +113,7 @@ import com.cloud.agent.api.SecurityIngressRuleAnswer; import com.cloud.agent.api.SecurityIngressRulesCmd; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.PingRoutingWithNwGroupsCommand; import com.cloud.agent.api.PingTestCommand; import com.cloud.agent.api.PrepareForMigrationAnswer; import com.cloud.agent.api.PrepareForMigrationCommand; @@ -227,6 +228,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String _manageSnapshotPath; private String _createTmplPath; private String _heartBeatPath; + private String _securityGroupPath; private String _host; private String _dcId; private String _pod; @@ -616,6 +618,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv throw new ConfigurationException("Unable to find the createtmplt.sh"); } + _securityGroupPath = Script.findScript(networkScriptsDir, "security_group.py"); + if (_securityGroupPath == null) { + throw new ConfigurationException("Unable to find the security_group.py"); + } + String value = (String)params.get("developer"); boolean isDeveloper = Boolean.parseBoolean(value); @@ -754,8 +761,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } } - //_can_bridge_firewall = can_bridge_firewall(); - Network vmopsNw = null; try { vmopsNw = _conn.networkLookupByName(_privNwName); @@ -802,6 +807,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } s_logger.debug("Found pif: " + _pifs.first() + " on " + _privBridgeName + ", pif: " + _pifs.second() + " on " + _publicBridgeName); + + _can_bridge_firewall = can_bridge_firewall(_pifs.second()); + _localGateway = Script.runSimpleBashScript("ip route |grep default|awk '{print $3}'"); if (_localGateway == null) { s_logger.debug("Failed to found the local gateway"); @@ -1776,8 +1784,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } return answer; } + private Answer execute(SecurityIngressRulesCmd cmd) { - return new SecurityIngressRuleAnswer(cmd); + boolean result = add_network_rules(cmd.getVmName(), + Long.toString(cmd.getVmId()), + cmd.getGuestIp(),cmd.getSignature(), + Long.toString(cmd.getSeqNum()), + cmd.getGuestMac(), + cmd.stringifyRules()); + + if (!result) { + s_logger.warn("Failed to program network rules for vm " + cmd.getVmName()); + return SecurityIngressRuleAnswer(cmd, false, "programming network rules failed"); + } else { + s_logger.info("Programmed network rules for vm " + cmd.getVmName() + " guestIp=" + cmd.getGuestIp() + ", numrules=" + cmd.getRuleSet().length); + return new SecurityIngressRuleAnswer(cmd); + } } protected GetVncPortAnswer execute(GetVncPortCommand cmd) { @@ -2194,8 +2216,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv _vms.put(vmName, State.Stopping); } try { - /*if (isDirectAttachedNetwork(cmd.getVnet())) - destroy_network_rules_for_vm(vmName);*/ + destroy_network_rules_for_vm(vmName); String result = stopVM(vmName, defineOps.UNDEFINE_VM); answer = new StopAnswer(cmd, null, 0, bytesSent, bytesReceived); @@ -2411,24 +2432,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv s_logger.debug("starting " + vmName + ": " + vm.toString()); startDomain(vmName, vm.toString()); - - Monitor monitor = vmSpec.getMonitor(); - if (monitor != null && monitor instanceof SshMonitor) { - SshMonitor sshMon = (SshMonitor)monitor; - String privateIp = sshMon.getIp(); - int cmdPort = sshMon.getPort(); - - if (s_logger.isDebugEnabled()) { - s_logger.debug("Ping command port, " + privateIp + ":" + cmdPort); - } - - String result = _virtRouterResource.connect(privateIp, cmdPort); - if (result != null) { - throw new CloudRuntimeException("Can not ping System vm " + vmName + "due to:" + result); - } - if (s_logger.isDebugEnabled()) { - s_logger.debug("Ping command port succeeded for vm " + vmName); - } + + if (vmSpec.getType() != VirtualMachine.Type.User) { + default_network_rules_for_systemvm(vmName); + } else { + default_network_rules(vmName, vmSpec.getNics()[0].getIp(), vmSpec.getId(), vmSpec.getNics()[0].getMac()); } // Attach each data volume to the VM, if there is a deferred attached disk @@ -2701,8 +2709,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv @Override public PingCommand getCurrentStatus(long id) { - final HashMap newStates = sync(); - return new PingRoutingCommand(com.cloud.host.Host.Type.Routing, id, newStates); + final HashMap newStates = sync(); + if (!_can_bridge_firewall) { + return new PingRoutingCommand(com.cloud.host.Host.Type.Routing, id, newStates); + } else { + HashMap> nwGrpStates = syncNetworkGroups(id); + return new PingRoutingWithNwGroupsCommand(getType(), id, newStates, nwGrpStates); + } } @Override @@ -3464,57 +3477,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return disks; } - private boolean can_bridge_firewall() { - String command = "iptables -N BRIDGE-FIREWALL; " + - "iptables -I BRIDGE-FIREWALL -m state --state RELATED,ESTABLISHED -j ACCEPT"; - - String result = executeBashScript(command); - if (result != null) { - s_logger.debug("Chain BRIDGE-FIREWALL already exists"); - } - - boolean enabled = true; - - result = executeBashScript("iptables -n -L FORWARD|grep BRIDGE-FIREWALL");; - if (result != null) { - result = executeBashScript("iptables -I FORWARD -m physdev --physdev-is-bridged -j BRIDGE-FIREWALL");; - if (result != null) { - enabled = false; - } - } - - File logPath = new File("/var/run/cloud"); - if (!logPath.exists()) { - logPath.mkdirs(); - } - - cleanup_rules_for_dead_vms(); - cleanup_rules(); - return enabled; - } - - private void cleanup_rules_for_dead_vms() { - - } - - private void cleanup_rules() { - - } - - /* private void ipset(String ipsetname, String proto, String start, String end, List ips) { - Script command = new Script("/bin/bash", _timeout, s_logger); - command.add("-c"); - command.add("ipset -N " + ipsetname + " iptreemap"); - String result = command.execute(); - if (result != null) { - s_logger.debug("ipset chain already exists: " + ipsetname); - } - boolean success = true; - String ipsettmp = ipsetname + - - } - */ - private Domain getDomain(String vmName) throws LibvirtException { return _conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName.getBytes())); } @@ -3559,88 +3521,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return command.execute(parser); } - private boolean default_network_rules_for_systemvm(String vmName) { - String command; - - List nics = getInterfaces(vmName); - for (String vif : nics) { - command = "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + vmName + ";" + - "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + vmName; - String result = executeBashScript(command); - if (result != null) { - s_logger.debug("Ingnoring failure to delete old rules"); - } - command = "iptables -N " + vmName; - result = executeBashScript(command); - if (result != null) { - command = "iptables -F " + vmName; - result = executeBashScript(command); - } - command = "iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + vmName + ";" + - "iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + vmName; - result = executeBashScript(command); - if (result != null) { - s_logger.debug("Failed to program default rules"); - return false; - } - } - command = "iptables -A " + vmName + " -j ACCEPT"; - executeBashScript(command); - return true; - } - - private boolean default_network_rules(String vmName, String vmIP) { - String command; - - List nics = getInterfaces(vmName); - for (String vif : nics) { - command = "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + vmName + ";" + - "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + vmName + ";" + - "iptables -F " + vmName + ";" + - "iptables -X " + vmName + ";"; - String result = executeBashScript(command); - if (result != null) { - s_logger.debug("Ignoring failure to delete old rules"); - } - - result = executeBashScript("iptables -N " + vmName); - if (result != null) { - executeBashScript("iptables -F " + vmName); - } - - command = "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + vmName + ";" + - "iptables -D BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + vmName + ";" + - "iptables -A " + vmName + " -m state --state RELATED,ESTABLISHED -j ACCEPT" + ";" + - "iptables -A " + vmName + " -p udp --dport 67:68 --sport 67:68 -j ACCEPT" + ";" + - "iptables -A " + vmName + " -m physdev --physdev-is-bridged --physdev-in " + vif + " --source " + vmIP + " -j RETURN" + ";" + - "iptables -A " + vmName + " -j DROP"; - result = executeBashScript(command); - if (result != null) { - s_logger.debug("Failed to program default rules for vm:" + vmName); - return false; - } - } - return true; - } - - private boolean destroy_network_rules_for_vm(String vmName) { - String command = "iptables-save |grep BRIDGE-FIREWALL |grep " + vmName + " | sed 's/-A/-D/'"; - OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); - String result = executeBashScript(command, parser); - - if (result == null && parser.getLines() != null) { - String[] lines = parser.getLines().split("\\n"); - for (String cmd : lines) { - command = "iptables " + cmd; - executeBashScript(command); - } - } - - executeBashScript("iptables -F " + vmName); - executeBashScript("iptables -X " + vmName); - return true; - } - private void deletExitingLinkLocalRoutTable(String linkLocalBr) { Script command = new Script("/bin/bash", _timeout); command.add("-c"); @@ -3852,4 +3732,112 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } } } + + private boolean can_bridge_firewall(String prvNic) { + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("can_bridge_firewall"); + cmd.add(prvNic); + String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + private boolean destroy_network_rules_for_vm(String vmName) { + if (!_can_bridge_firewall) + return false; + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("destroy_network_rules_for_vm"); + cmd.add("--vmname"); + cmd.add(vmName); + String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + private boolean default_network_rules(String vmName, String pubIP, long vmId, String mac) { + if (!_can_bridge_firewall) + return false; + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("default_network_rules"); + cmd.add("--vmname", vmName); + cmd.add("--vmip", pubIP); + cmd.add("--vmid", Long.toString(vmId)); + cmd.add("--vmmac", mac); + + String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + private boolean default_network_rules_for_systemvm(String vmName) { + if (!_can_bridge_firewall) + return false; + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("default_network_rules_systemvm"); + cmd.add("--vmname"); + cmd.add(vmName); + String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + private boolean add_network_rules(String vmName, String vmId, String guestIP, String sig, String seq, String mac, String rules) { + if (!_can_bridge_firewall) + return false; + String newRules = rules.replace(" ", ";"); + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("add_network_rules"); + cmd.add("--vmname", vmName); + cmd.add("--vmid", vmId); + cmd.add("--vmip", guestIP); + cmd.add("--sig", sig); + cmd.add("--seq", seq); + cmd.add("--vmmac", mac); + cmd.add("--rules", newRules); + String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + private String get_rule_logs_for_vms() { + Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("get_rule_logs_for_vms"); + OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); + String result = cmd.execute(parser); + if (result == null) { + return parser.getLine(); + } + return null; + } + + private HashMap> syncNetworkGroups(long id) { + HashMap> states = new HashMap>(); + + String result = get_rule_logs_for_vms(); + s_logger.trace("syncNetworkGroups: id=" + id + " got: " + result); + String [] rulelogs = result != null ?result.split(";"): new String [0]; + for (String rulesforvm: rulelogs){ + String [] log = rulesforvm.split(","); + if (log.length != 6) { + continue; + } + try { + states.put(log[0], new Pair(Long.parseLong(log[1]), Long.parseLong(log[5]))); + } catch (NumberFormatException nfe) { + states.put(log[0], new Pair(-1L, -1L)); + } + } + return states; + } + } diff --git a/scripts/vm/network/security_group.py b/scripts/vm/network/security_group.py new file mode 100755 index 00000000000..188536fb7ff --- /dev/null +++ b/scripts/vm/network/security_group.py @@ -0,0 +1,602 @@ +#!/usr/bin/python +import cloud_utils +from cloud_utils import Command +import logging +import sys +import os +import xml.dom.minidom +from optparse import OptionParser, OptionGroup, OptParseError, BadOptionError, OptionError, OptionConflictError, OptionValueError +iptables = Command("iptables") +bash = Command("/bin/bash") +virsh = Command("virsh") +ebtablessave = Command("ebtables-save") +ebtables = Command("ebtables") +augtool = Command("augtool") +def execute(cmd): + logging.debug(cmd) + return bash("-c", cmd).stdout +def can_bridge_firewall(privnic): + try: + execute("which iptables") + except: + print "no iptables on your host machine" + exit(1) + + try: + execute("which ebtables") + except: + print "no ebtables on your host machine" + exit(2) + + try: + alreadySetup = augtool.match("/files/etc/sysctl.conf/net.bridge.bridge-nf-call-arptables", "1").stdout.strip() + if len(alreadySetup) == 0: + script = """ + set /files/etc/sysctl.conf/net.bridge.bridge-nf-call-arptables 1 + save""" + augtool < script + + alreadySetup = augtool.match("/files/etc/sysctl.conf/net.bridge.bridge-nf-call-iptables", "1").stdout.strip() + if len(alreadySetup) == 0: + script = """ + set /files/etc/sysctl.conf/net.bridge.bridge-nf-call-iptables 1 + save""" + augtool < script + + alreadySetup = augtool.match("/files/etc/sysctl.conf/net.bridge.bridge-nf-call-ip6tables", "1").stdout.strip() + if len(alreadySetup) == 0: + script = """ + set /files/etc/sysctl.conf/net.bridge.bridge-nf-call-ip6tables 1 + save""" + augtool < script + execute("sysctl -p /etc/sysctl.conf") + except: + print "failed to turn on bridge netfilter" + exit(3) + + try: + execute("iptables -N BRIDGE-FIREWALL") + execute("iptables -N BRIDGE-FIREWALL") + execute("iptables -I BRIDGE-FIREWALL -m state --state RELATED,ESTABLISHED -j ACCEPT") + execute("iptables -D FORWARD -j RH-Firewall-1-INPUT") + except: + logging.exception('Chain BRIDGE-FIREWALL already exists: ') + + result = 0 + try: + execute("iptables -n -L FORWARD | grep BRIDGE-FIREWALL") + except: + try: + execute("iptables -I FORWARD -m physdev --physdev-is-bridged -j BRIDGE-FIREWALL") + execute("iptables -A FORWARD -m physdev --physdev-is-bridged --physdev-out " + privnic + " -j ACCEPT") + execute("iptables -A FORWARD -j DROP") + except: + result = 1 + + + if result == 1: + print "br firewall is not supported" + if not os.path.exists('/var/run/cloud'): + os.makedirs('/var/run/cloud') + + cleanup_rules_for_dead_vms() + cleanup_rules() + + return result +''' +def ipset(ipsetname, proto, start, end, ips): + try: + check_call(['ipset', '-N', ipsetname, 'iptreemap']) + except: + logging.debug("ipset chain already exists" + ipsetname) + + result = True + ipsettmp = ''.join(''.join(ipsetname.split('-')).split('_')) + str(int(time.time()) % 1000) + + try: + check_call(['ipset', '-N', ipsettmp, 'iptreemap']) + for ip in ips: + try: + check_call(['ipset', '-A', ipsettmp, ip]) + except CommandException, cex: + if cex.reason.rfind('already in set') == -1: + raise + check_call(['ipset', '-W', ipsettmp, ipsetname]) + check_call(['ipset', '-X', ipsettmp]) + except: + logging.debug("Failed to program ipset " + ipsetname) + result = False + + return result +''' + +def destroy_network_rules_for_vm(vm_name): + vmchain = vm_name + + delete_rules_for_vm_in_bridge_firewall_chain(vm_name) + if vm_name.startswith('i-') or vm_name.startswith('r-'): + vmchain = '-'.join(vm_name.split('-')[:-1]) + vmchain_default = '-'.join(vm_name.split('-')[:-2]) + "-def" + + destroy_ebtables_rules(vmchain) + + try: + execute("iptables -F " + vmchain_default) + execute("iptables -X " + vmchain_default) + except: + logging.exception("Ignoring failure to delete chain " + vmchain_default) + + try: + execute("iptables -F " + vmchain) + execute("iptables -X " + vmchain) + except: + logging.debug("Ignoring failure to delete chain " + vmchain) + + remove_rule_log_for_vm(vm_name) + + if 1 in [ vm_name.startswith(c) for c in ['r-', 's-', 'v-'] ]: + return 'true' + + return 'true' + +def destroy_ebtables_rules(vm_name): + delcmd = "ebtables-save | grep ROUTING | grep " + vm_name + " | sed 's/-A/-D/'" + delcmds = execute(delcmd).split('\n') + delcmds.pop() + for cmd in delcmds: + try: + execute("ebtables -t nat " + cmd) + except: + logging.debug("Ignoring failure to delete ebtables rules for vm " + vm_name) + chains = [vm_name+"-in", vm_name+"-out"] + for chain in chains: + try: + execute("ebtables -t nat -F " + chain) + execute("ebtables -t nat -X " + chain) + except: + logging.debug("Ignoring failure to delete ebtables chain for vm " + vm_name) + +def default_ebtables_rules(vm_name, vif, vm_ip, vm_mac): + vmchain_in = vm_name + "-in" + vmchain_out = vm_name + "-out" + + for chain in [vmchain_in, vmchain_out]: + try: + execute("ebtables -t nat -N " + chain) + except: + execute("ebtables -t nat -F " + chain) + + try: + # -s ! 52:54:0:56:44:32 -j DROP + execute("ebtables -t nat -A PREROUTING -i " + vif + " -j " + vmchain_in) + execute("ebtables -t nat -A POSTROUTING -o " + vif + " -j " + vmchain_out) + except: + logging.debug("Failed to program default rules") + return 'false' + + try: + execute("ebtables -t nat -A " + vmchain_in + " -i " + vif + " -s ! " + vm_mac + " -j DROP") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP -s ! " + vm_mac + " -j DROP") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP --arp-mac-src ! " + vm_mac + " -j DROP") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP --arp-ip-src ! " + vm_ip + " -j DROP") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP --arp-op Request -j ACCEPT") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP --arp-op Reply -j ACCEPT") + execute("ebtables -t nat -A " + vmchain_in + " -p ARP -j DROP") + except: + logging.exception("Failed to program default ebtables IN rules") + return 'false' + + try: + execute("ebtables -t nat -A " + vmchain_out + " -p ARP --arp-op Reply --arp-mac-dst ! " + vm_mac + " -j DROP") + execute("ebtables -t nat -A " + vmchain_out + " -p ARP --arp-ip-dst ! " + vm_ip + " -j DROP") + execute("ebtables -t nat -A " + vmchain_out + " -p ARP --arp-op Request -j ACCEPT") + execute("ebtables -t nat -A " + vmchain_out + " -p ARP --arp-op Reply -j ACCEPT") + execute("ebtables -t nat -A " + vmchain_out + " -p ARP -j DROP") + except: + logging.debug("Failed to program default ebtables OUT rules") + return 'false' + + +def default_network_rules_systemvm(vm_name): + vifs = getVifs(vm_name) + domid = getvmId(vm_name) + vmchain = vm_name + if vm_name.startswith('r-'): + vmchain = '-'.join(vm_name.split('-')[:-1]) + + delete_rules_for_vm_in_bridge_firewall_chain(vm_name) + + try: + execute("iptables -N " + vmchain) + except: + execute("iptables -F " + vmchain) + + + for vif in vifs: + try: + execute("iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + vmchain) + execute("iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + vmchain) + except: + logging.debug("Failed to program default rules") + return 'false' + + execute("iptables -A " + vmchain + " -j ACCEPT") + + if write_rule_log_for_vm(vm_name, '-1', '_ignore_', domid, '_initial_', '-1') == False: + logging.debug("Failed to log default network rules for systemvm, ignoring") + return 'true' + + +def default_network_rules(vm_name, vm_ip, vm_id, vm_mac): + print vm_name + print vm_ip + print vm_mac + vmName = vm_name + domID = getvmId(vm_name) + delete_rules_for_vm_in_bridge_firewall_chain(vmName) + vm_name = '-'.join(vm_name.split('-')[:-1]) + vmchain = vm_name + vmchain_default = '-'.join(vmchain.split('-')[:-1]) + "-def" + + destroy_ebtables_rules(vmName) + + vifs = getVifs(vmName) + + try: + execute("iptables -N " + vmchain) + except: + execute("iptables -F " + vmchain) + + try: + execute("iptables -N " + vmchain_default) + except: + execute("iptables -F " + vmchain_default) + + try: + for v in vifs: + execute("iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-out " + v + " -j " + vmchain_default) + execute("iptables -A BRIDGE-FIREWALL -m physdev --physdev-is-bridged --physdev-in " + v + " -j " + vmchain_default) + execute("iptables -A " + vmchain_default + " -m state --state RELATED,ESTABLISHED -j ACCEPT") + #allow dhcp + for v in vifs: + execute("iptables -A " + vmchain_default + " -m physdev --physdev-is-bridged --physdev-in " + v + " -p udp --dport 67 --sport 68 -j ACCEPT") + execute("iptables -A " + vmchain_default + " -m physdev --physdev-is-bridged --physdev-out " + v + " -p udp --dport 68 --sport 67 -j ACCEPT") + + #don't let vm spoof its ip address + for v in vifs: + execute("iptables -A " + vmchain_default + " -m physdev --physdev-is-bridged --physdev-in " + v + " --source " + vm_ip + " -j RETURN") + execute("iptables -A " + vmchain_default + " -j " + vmchain) + except: + logging.debug("Failed to program default rules for vm " + vm_name) + return 'false' + + for v in vifs: + default_ebtables_rules(vmchain, v, vm_ip, vm_mac) + + if write_rule_log_for_vm(vmName, vm_id, vm_ip, domID, '_initial_', '-1') == False: + logging.debug("Failed to log default network rules, ignoring") + + logging.debug("Programmed default rules for vm " + vm_name) + return 'true' + +def delete_rules_for_vm_in_bridge_firewall_chain(vmName): + vm_name = vmName + if vm_name.startswith('i-') or vm_name.startswith('r-'): + vm_name = '-'.join(vm_name.split('-')[:-2]) + + vmchain = vm_name + + delcmd = "iptables -S BRIDGE-FIREWALL | grep " + vmchain + " | sed 's/-A/-D/'" + delcmds = execute(delcmd).split('\n') + delcmds.pop() + for cmd in delcmds: + try: + execute("iptables " + cmd) + except: + logging.exception("Ignoring failure to delete rules for vm " + vmName) + +''' +def network_rules_for_rebooted_vm(vmName): + vm_name = vmName + vifs = getVifs(vmName) + logging.debug("Found a rebooted VM -- reprogramming rules for " + vmName) + + delete_rules_for_vm_in_bridge_firewall_chain(vmName) + if 1 in [ vm_name.startswith(c) for c in ['r-', 's-', 'v-'] ]: + default_network_rules_systemvm(session, {"vmName":vmName}) + return True + + vmchain = '-'.join(vm_name.split('-')[:-1]) + vmchain_default = '-'.join(vm_name.split('-')[:-2]) + "-def" + + for v in vifs: + iptables('-A', 'BRIDGE-FIREWALL', '-m', 'physdev', '--physdev-is-bridged', '--physdev-out', v, '-j', vmchain_default) + iptables('-A', 'BRIDGE-FIREWALL', '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '-j', vmchain_default) + + #change antispoof rule in vmchain + try: + delcmd = "iptables -S " + vmchain_default + " | grep physdev-in | sed 's/-A/-D/'" + inscmd = "iptables -S " + vmchain_default + " | grep physdev-in | grep vif | sed -r 's/vif[0-9]+.0/" + vif + "/' | sed 's/-A/-I/'" + inscmd2 = "iptables -S " + vmchain_default + " | grep physdev-in | grep tap | sed -r 's/tap[0-9]+.0/" + tap + "/' | sed 's/-A/-I/'" + + ipts = [] + for cmd in [delcmd, inscmd, inscmd2]: + cmds = bash('-c', cmd.split(' ')).split('\n') + cmds.pop() + for c in cmds: + ipt = c.split(' ') + ipt.pop() + ipts.append(ipt) + + for ipt in ipts: + try: + iptables(ipt) + except: + logging.debug("Failed to rewrite antispoofing rules for vm " + vmName) + except: + logging.debug("No rules found for vm " + vmchain) + + + rewrite_rule_log_for_vm(vmName, curr_domid) + return True +''' + +def rewrite_rule_log_for_vm(vm_name, new_domid): + logfilename = "/var/run/cloud/" + vm_name +".log" + if not os.path.exists(logfilename): + return + lines = (line.rstrip() for line in open(logfilename)) + + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = ['_', '-1', '_', '-1', '_', '-1'] + for line in lines: + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = line.split(',') + break + + write_rule_log_for_vm(_vmName, _vmID, '0.0.0.0', new_domid, _signature, '-1') + +def get_rule_log_for_vm(vmName): + vm_name = vmName; + logfilename = "/var/run/cloud/" + vm_name +".log" + if not os.path.exists(logfilename): + return '' + + lines = (line.rstrip() for line in open(logfilename)) + + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = ['_', '-1', '_', '-1', '_', '-1'] + for line in lines: + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = line.split(',') + break + + return ','.join([_vmName, _vmID, _vmIP, _domID, _signature, _seqno]) + +def get_rule_logs_for_vms(): + cmd = "virsh list|grep running |awk '{print $2}'" + vms = bash("-c", cmd).stdout.split("\n") + + result = [] + try: + for name in vms: + name = name.rstrip() + if 1 not in [ name.startswith(c) for c in ['r-', 's-', 'v-', 'i-'] ]: + continue + #network_rules_for_rebooted_vm(session, name) + if name.startswith('i-'): + log = get_rule_log_for_vm(name) + result.append(log) + except: + logging.debug("Failed to get rule logs, better luck next time!") + + print ";".join(result) + +def cleanup_rules_for_dead_vms(): + return True + + +def cleanup_rules(): + try: + + chainscmd = "iptables-save | grep '^:' | awk '{print $1}' | cut -d':' -f2" + chains = execute(chainscmd).split('\n') + cleaned = 0 + cleanup = [] + for chain in chains: + if 1 in [ chain.startswith(c) for c in ['r-', 'i-', 's-', 'v-'] ]: + if chain.startswith('i-') or chain.startswith('r-'): + vm_name = chain + '-untagged' + else: + vm_name = chain + + cmd = "virsh list |grep " + vm_name + try: + result = execute(cmd) + except: + result = None + + if result == None or len(result) == 0: + logging.debug("chain " + chain + " does not correspond to a vm, cleaning up") + cleanup.append(vm_name) + continue + if result.find("running") == -1: + logging.debug("vm " + vm_name + " is not running, cleaning up") + cleanup.append(vm_name) + + for vmname in cleanup: + destroy_network_rules_for_vm({'vmName':vmname}) + + logging.debug("Cleaned up rules for " + str(len(cleanup)) + " chains") + except: + logging.debug("Failed to cleanup rules !") + +def check_rule_log_for_vm(vmName, vmId, vmIP, domID, signature, seqno): + vm_name = vmName; + logfilename = "/var/run/cloud/" + vm_name +".log" + if not os.path.exists(logfilename): + return [True, True, True, True, True, True] + + try: + lines = (line.rstrip() for line in open(logfilename)) + except: + logging.debug("failed to open " + logfilename) + return False + + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = ['_', '-1', '_', '-1', '_', '-1'] + try: + for line in lines: + [_vmName,_vmID,_vmIP,_domID,_signature,_seqno] = line.split(',') + break + except: + logging.debug("Failed to parse log file for vm " + vm_name) + remove_rule_log_for_vm(vm_name) + return False + + return [(vm_name != _vmName), (vmId != _vmID), (vmIP != _vmIP), (domID != _domID), (signature != _signature),(seqno != _seqno)] + +def write_rule_log_for_vm(vmName, vmID, vmIP, domID, signature, seqno): + vm_name = vmName + logfilename = "/var/run/cloud/" + vm_name +".log" + logging.debug("Writing log to " + logfilename) + logf = open(logfilename, 'w') + output = ','.join([vmName, vmID, vmIP, domID, signature, seqno]) + result = True + try: + logf.write(output) + logf.write('\n') + except: + logging.debug("Failed to write to rule log file " + logfilename) + result = False + + logf.close() + + return result + +def remove_rule_log_for_vm(vmName): + vm_name = vmName + logfilename = "/var/run/cloud/" + vm_name +".log" + + result = True + try: + os.remove(logfilename) + except: + logging.debug("Failed to delete rule log file " + logfilename) + result = False + + return result + +def add_network_rules(vm_name, vm_id, vm_ip, signature, seqno, vmMac, rules): + try: + vmName = vm_name + domId = getvmId(vmName) + vm_name = '-'.join(vm_name.split('-')[:-1]) + vmchain = vm_name + + changes = check_rule_log_for_vm(vmName, vm_id, vm_ip, domId, signature, seqno) + + if not 1 in changes: + logging.debug("Rules already programmed for vm " + vm_name) + return 'true' + + if changes[1] or changes[2] or changes[3]: + logging.debug("Change detected in vmId or vmIp or domId, resetting default rules") + default_network_rules(vmName, vm_ip, vm_id, vmMac) + + lines = rules.split(';') + + print lines + logging.debug(" programming network rules for IP: " + vm_ip + " vmname=" + vm_name) + #iptables('-F', vmchain) + print lines + + for line in lines: + tokens = line.split(':') + if len(tokens) != 4: + continue + protocol = tokens[0] + start = tokens[1] + end = tokens[2] + cidrs = tokens.pop(); + ips = cidrs.split(",") + ips.pop() + allow_any = False + 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: + iptables = "iptables -I " + vmchain + " -m state --state NEW -m iprange --src-range " + ip + " -j ACCEPT" + elif protocol != 'icmp': + for ip in ips: + iptables = "iptables -I " + vmchain + " -p " + protocol + " -m " + protocol + " --dport " + range + " -m state --state NEW -m iprange --src-range " + ip + " -j ACCEPT" + else: + range = start + "/" + end + if start == "-1": + range = "any" + for ip in ips: + iptables = "iptables -I " + vmchain + " -p icmp --icmp-type " + range + " -m iprange --src-range " + ip + " -j ACCEPT" + execute(iptables) + + if allow_any and protocol != 'all': + if protocol != 'icmp': + iptables = "iptables -I " + vmchain + " -p " + protocol + " -m " + protocol + " --dport " + range + " -m state --state NEW -j ACCEPT" + else: + range = start + "/" + end + if start == "-1": + range = "any" + iptables = "iptables -I " + vmchain + " -p icmp --icmp-type " + range + " -j ACCEPT" + execute(iptables) + + iptables = "iptables -A " + vmchain + " -j DROP" + execute(iptables) + + if write_rule_log_for_vm(vmName, vm_id, vm_ip, domId, signature, seqno) == False: + return 'false' + + return 'true' + except: + logging.debug("Failed to network rule !: " + sys.exc_type) + +def getVifs(vmName): + vifs = [] + try: + xmlfile = virsh("dumpxml", vmName).stdout + except: + return vifs + + dom = xml.dom.minidom.parseString(xmlfile) + vifs = [] + for network in dom.getElementsByTagName("interface"): + target = network.getElementsByTagName('target')[0] + nicdev = target.getAttribute("dev").strip() + vifs.append(nicdev) + return vifs +def getvmId(vmName): + cmd = "virsh list |grep " + vmName + " | awk '{print $1}'" + return bash("-c", cmd).stdout.strip() + +if __name__ == '__main__': + logging.basicConfig(filename="/var/log/cloud/security_group.log", format="%(asctime)s - %(message)s", level=logging.DEBUG) + parser = OptionParser() + parser.add_option("--vmname", dest="vmName") + parser.add_option("--vmip", dest="vmIP") + parser.add_option("--vmid", dest="vmID") + parser.add_option("--vmmac", dest="vmMAC") + parser.add_option("--vif", dest="vif") + parser.add_option("--sig", dest="sig") + parser.add_option("--seq", dest="seq") + parser.add_option("--rules", dest="rules") + (option, args) = parser.parse_args() + cmd = args[0] + if cmd == "can_bridge_firewall": + can_bridge_firewall(args[1]) + elif cmd == "default_network_rules": + default_network_rules(option.vmName, option.vmIP, option.vmID, option.vmMAC) + elif cmd == "destroy_network_rules_for_vm": + destroy_network_rules_for_vm(option.vmName) + elif cmd == "default_network_rules_systemvm": + default_network_rules_systemvm(option.vmName) + elif cmd == "get_rule_logs_for_vms": + get_rule_logs_for_vms() + elif cmd == "add_network_rules": + add_network_rules(option.vmName, option.vmID, option.vmIP, option.sig, option.seq, option.vmMAC, option.rules)