| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| 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: |
| execute("sysctl -w net.bridge.bridge-nf-call-arptables=1") |
| execute("sysctl -w net.bridge.bridge-nf-call-iptables=1") |
| execute("sysctl -w net.bridge.bridge-nf-call-ip6tables=1") |
| 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)] |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |