mirror of https://github.com/apache/cloudstack.git
467 lines
17 KiB
Python
467 lines
17 KiB
Python
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)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|