from OvmCommonModule import * from ConfigFileOps import * import os import logging class OvmSecurityGroup(OvmObject): @staticmethod def can_bridge_firewall(): try: execute("which iptables") except: print "iptables was not found on the host" return False try: execute("which ebtables") except: print "ebtables was not found on the host" return False if not os.path.exists('/var/run/cloud'): os.makedirs('/var/run/cloud') return OvmSecurityGroup.cleanup_rules() @staticmethod def cleanup_rules(): try: chainscmd = "iptables-save | grep '^:' | grep -v '.*-def' | 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-'] ]: vm_name = chain else: continue cmd = "xm 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) for vm_name in cleanup: OvmSecurityGroup.delete_all_network_rules_for_vm(vm_name) logging.debug("Cleaned up rules for " + str(len(cleanup)) + " chains") return True except: logging.debug("Failed to cleanup rules !") return False @staticmethod def add_fw_framework(bridge_name): try: cfo = ConfigFileOps("/etc/sysctl.conf") cfo.addEntry("net.bridge.bridge-nf-call-arptables", "1") cfo.addEntry("net.bridge.bridge-nf-call-iptables", "1") cfo.addEntry("net.bridge.bridge-nf-call-ip6tables", "1") cfo.save() execute("sysctl -p /etc/sysctl.conf") except: logging.debug("failed to turn on bridge netfilter") return False brfw = "BF-" + bridge_name try: execute("iptables -L " + brfw) except: execute("iptables -N " + brfw) brfwout = brfw + "-OUT" try: execute("iptables -L " + brfwout) except: execute("iptables -N " + brfwout) brfwin = brfw + "-IN" try: execute("iptables -L " + brfwin) except: execute("iptables -N " + brfwin) try: refs = execute("iptables -n -L " + brfw + " |grep " + brfw + " | cut -d \( -f2 | awk '{print $1}'").strip() if refs == "0": execute("iptables -I FORWARD -i " + bridge_name + " -j DROP") execute("iptables -I FORWARD -o " + bridge_name + " -j DROP") execute("iptables -I FORWARD -i " + bridge_name + " -m physdev --physdev-is-bridged -j " + brfw) execute("iptables -I FORWARD -o " + bridge_name + " -m physdev --physdev-is-bridged -j " + brfw) phydev = execute("brctl show |grep " + bridge_name + " | awk '{print $4}'").strip() execute("iptables -A " + brfw + " -m physdev --physdev-is-bridged --physdev-out " + phydev + " -j ACCEPT") execute("iptables -A " + brfw + " -m state --state RELATED,ESTABLISHED -j ACCEPT") execute("iptables -A " + brfw + " -m physdev --physdev-is-bridged --physdev-is-out -j " + brfwout) execute("iptables -A " + brfw + " -m physdev --physdev-is-bridged --physdev-is-in -j " + brfwin) return True except: try: execute("iptables -F " + brfw) except: return False return False @staticmethod def default_network_rules_user_vm(vm_name, vm_id, vm_ip, vm_mac, vif, bridge_name): if not OvmSecurityGroup.add_fw_framework(bridge_name): return False OvmSecurityGroup.delete_iptables_rules_for_vm(vm_name) OvmSecurityGroup.delete_ebtables_rules_for_vm(vm_name) bridge_firewall_chain = "BF-" + bridge_name vm_chain = vm_name default_vm_chain = '-'.join(vm_chain.split('-')[:-1]) + "-def" dom_id = getDomId(vm_name) try: execute("iptables -N " + vm_chain) except: execute("iptables -F " + vm_chain) try: execute("iptables -N " + default_vm_chain) except: execute("iptables -F " + default_vm_chain) try: execute("iptables -A " + bridge_firewall_chain + "-OUT" + " -m physdev --physdev-is-bridged --physdev-out " + vif + " -j " + default_vm_chain) execute("iptables -A " + bridge_firewall_chain + "-IN" + " -m physdev --physdev-is-bridged --physdev-in " + vif + " -j " + default_vm_chain) execute("iptables -A " + default_vm_chain + " -m state --state RELATED,ESTABLISHED -j ACCEPT") # Allow DHCP execute("iptables -A " + default_vm_chain + " -m physdev --physdev-is-bridged --physdev-in " + vif + " -p udp --dport 67 --sport 68 -j ACCEPT") execute("iptables -A " + default_vm_chain + " -m physdev --physdev-is-bridged --physdev-out " + vif + " -p udp --dport 68 --sport 67 -j ACCEPT") # Don't let a VM spoof its ip address if vm_ip is not None: execute("iptables -A " + default_vm_chain + " -m physdev --physdev-is-bridged --physdev-in " + vif + " --source " + vm_ip + " -j ACCEPT") execute("iptables -A " + default_vm_chain + " -j " + vm_chain) execute("iptables -A " + vm_chain + " -j DROP") except: logging.debug("Failed to program default rules for vm " + vm_name) return False OvmSecurityGroup.default_ebtables_rules(vm_chain, vm_ip, vm_mac, vif) if vm_ip is not None: if (OvmSecurityGroup.write_rule_log_for_vm(vm_name, vm_id, vm_ip, dom_id, '_initial_', '-1') == False): logging.debug("Failed to log default network rules, ignoring") logging.debug("Programmed default rules for vm " + vm_name) return True @staticmethod def default_ebtables_rules(vm_name, vm_ip, vm_mac, vif): vm_chain_in = vm_name + "-in" vm_chain_out = vm_name + "-out" for chain in [vm_chain_in, vm_chain_out]: try: execute("ebtables -t nat -N " + chain) except: execute("ebtables -t nat -F " + chain) try: execute("ebtables -t nat -A PREROUTING -i " + vif + " -j " + vm_chain_in) execute("ebtables -t nat -A POSTROUTING -o " + vif + " -j " + vm_chain_out) except: logging.debug("Failed to program default rules") return False try: execute("ebtables -t nat -A " + vm_chain_in + " -s ! " + vm_mac + " -j DROP") execute("ebtables -t nat -A " + vm_chain_in + " -p ARP -s ! " + vm_mac + " -j DROP") execute("ebtables -t nat -A " + vm_chain_in + " -p ARP --arp-mac-src ! " + vm_mac + " -j DROP") if vm_ip is not None: execute("ebtables -t nat -A " + vm_chain_in + " -p ARP --arp-ip-src ! " + vm_ip + " -j DROP") execute("ebtables -t nat -A " + vm_chain_in + " -p ARP --arp-op Request -j ACCEPT") execute("ebtables -t nat -A " + vm_chain_in + " -p ARP --arp-op Reply -j ACCEPT") execute("ebtables -t nat -A " + vm_chain_in + " -p ARP -j DROP") except: logging.exception("Failed to program default ebtables IN rules") return False try: execute("ebtables -t nat -A " + vm_chain_out + " -p ARP --arp-op Reply --arp-mac-dst ! " + vm_mac + " -j DROP") if vm_ip is not None: execute("ebtables -t nat -A " + vm_chain_out + " -p ARP --arp-ip-dst ! " + vm_ip + " -j DROP") execute("ebtables -t nat -A " + vm_chain_out + " -p ARP --arp-op Request -j ACCEPT") execute("ebtables -t nat -A " + vm_chain_out + " -p ARP --arp-op Reply -j ACCEPT") execute("ebtables -t nat -A " + vm_chain_out + " -p ARP -j DROP") except: logging.debug("Failed to program default ebtables OUT rules") return False return True @staticmethod def add_network_rules(vm_name, vm_id, vm_ip, signature, seqno, vm_mac, rules, vif, bridge_name): try: vm_chain = vm_name dom_id = getDomId(vm_name) changes = [] changes = OvmSecurityGroup.check_rule_log_for_vm(vm_name, vm_id, vm_ip, dom_id, signature, seqno) if not 1 in changes: logging.debug("Rules already programmed for vm " + vm_name) return True if changes[0] or changes[1] or changes[2] or changes[3]: if not OvmSecurityGroup.default_network_rules(vm_name, vm_id, vm_ip, vm_mac, vif, bridge_name): return False if rules == "" or rules == None: lines = [] else: lines = rules.split(';')[:-1] logging.debug("Programming network rules for IP: " + vm_ip + " vmname=" + vm_name) execute("iptables -F " + vm_chain) 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 port_range = start + ":" + end if ips: if protocol == 'all': for ip in ips: execute("iptables -I " + vm_chain + " -m state --state NEW -s " + ip + " -j ACCEPT") elif protocol != 'icmp': for ip in ips: execute("iptables -I " + vm_chain + " -p " + protocol + " -m " + protocol + " --dport " + port_range + " -m state --state NEW -s " + ip + " -j ACCEPT") else: port_range = start + "/" + end if start == "-1": port_range = "any" for ip in ips: execute("iptables -I " + vm_chain + " -p icmp --icmp-type " + port_range + " -s " + ip + " -j ACCEPT") if allow_any and protocol != 'all': if protocol != 'icmp': execute("iptables -I " + vm_chain + " -p " + protocol + " -m " + protocol + " --dport " + port_range + " -m state --state NEW -j ACCEPT") else: port_range = start + "/" + end if start == "-1": port_range = "any" execute("iptables -I " + vm_chain + " -p icmp --icmp-type " + port_range + " -j ACCEPT") iptables = "iptables -A " + vm_chain + " -j DROP" execute(iptables) return OvmSecurityGroup.write_rule_log_for_vm(vm_name, vm_id, vm_ip, dom_id, signature, seqno) except: logging.debug("Failed to network rule !: " + sys.exc_type) return False @staticmethod def delete_all_network_rules_for_vm(vm_name, vif = None): OvmSecurityGroup.delete_iptables_rules_for_vm(vm_name) OvmSecurityGroup.delete_ebtables_rules_for_vm(vm_name) vm_chain = vm_name default_vm_chain = None if vm_name.startswith('i-') or vm_name.startswith('r-'): default_vm_chain = '-'.join(vm_name.split('-')[:-1]) + "-def" try: if default_vm_chain != None: execute("iptables -F " + default_vm_chain) except: logging.debug("Ignoring failure to delete chain " + default_vm_chain) try: if default_vm_chain != None: execute("iptables -X " + vmchain_default) except: logging.debug("Ignoring failure to delete chain " + default_vm_chain) try: execute("iptables -F " + vm_chain) except: logging.debug("Ignoring failure to delete chain " + vm_chain) try: execute("iptables -X " + vm_chain) except: logging.debug("Ignoring failure to delete chain " + vm_chain) if vif is not None: try: dnats = execute("iptables-save -t nat | grep " + vif + " | sed 's/-A/-D/'").split("\n") for dnat in dnats: try: execute("iptables -t nat " + dnat) except: logging.debug("Igoring failure to delete dnat: " + dnat) except: pass OvmSecurityGroup.remove_rule_log_for_vm(vm_name) if 1 in [ vm_name.startswith(c) for c in ['r-', 's-', 'v-'] ]: return True return True @staticmethod def delete_iptables_rules_for_vm(vm_name): vm_name = OvmSecurityGroup.truncate_vm_name(vm_name) vm_chain = vm_name query = "iptables-save | grep " + vm_chain + " | grep physdev-is-bridged | sed 's/-A/-D/'" delete_cmds = execute(query).split('\n') delete_cmds.pop() for cmd in delete_cmds: try: execute("iptables " + cmd) except: logging.exception("Ignoring failure to delete rules for vm " + vm_name) @staticmethod def delete_ebtables_rules_for_vm(vm_name): vm_name = OvmSecurityGroup.truncate_vm_name(vm_name) query = "ebtables -t nat -L --Lx | grep ROUTING | grep " + vm_name + " | sed 's/-A/-D/'" delete_cmds = execute(query).split('\n') delete_cmds.pop() for cmd in delete_cmds: try: execute(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) @staticmethod def truncate_vm_name(vm_name): if vm_name.startswith('i-') or vm_name.startswith('r-'): truncated_vm_name = '-'.join(vm_name.split('-')[:-1]) else: truncated_vm_name = vm_name return truncated_vm_name @staticmethod def write_rule_log_for_vm(vm_name, vm_id, vm_ip, dom_id, signature, seqno): log_file_name = "/var/run/cloud/" + vm_name + ".log" logging.debug("Writing log to " + log_file_name) logf = open(log_file_name, 'w') output = ','.join([vm_name, vm_id, vm_ip, dom_id, signature, seqno]) result = True try: logf.write(output) logf.write('\n') except: logging.debug("Failed to write to rule log file " + log_file_name) result = False logf.close() return result @staticmethod def remove_rule_log_for_vm(vm_name): log_file_name = "/var/run/cloud/" + vm_name +".log" result = True try: os.remove(log_file_name) except: logging.debug("Failed to delete rule log file " + log_file_name) result = False return result @staticmethod def check_rule_log_for_vm(vm_name, vm_id, vm_ip, dom_id, signature, seqno): log_file_name = "/var/run/cloud/" + vm_name + ".log" if not os.path.exists(log_file_name): return [True, True, True, True, True, True] try: lines = (line.rstrip() for line in open(log_file_name)) except: logging.debug("failed to open " + log_file_name) return [True, True, True, True, True, True] [_vm_name, _vm_id, _vm_ip, _dom_id, _signature, _seqno] = ['_', '-1', '_', '-1', '_', '-1'] try: for line in lines: [_vm_name, _vm_id, _vm_ip, _dom_id, _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 [True, True, True, True, True, True] return [(vm_name != _vm_name), (vm_id != _vm_id), (vm_ip != _vm_ip), (dom_id != _dom_id), (signature != _signature), (seqno != _seqno)]