mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Restore iptables at once using iptables-restore instead of calling iptables numerous timesThis makes handling the firewall rules about 50-60 times faster because it is generated in memory and then loaded once. It's work by @borisroman see PR #1400. Reopened it here because I think this is a great improvement. * pr/1482: Resolve conflict as forceencap is already in master Split the cidr lists so we won't hit the iptables-resture limits Check the existence of 'forceencap' parameter before use Do not load previous firewall rules as we replace everyhing anyway Wait for dnsmasq to finish restart Remove duplicate spaces, and thus duplicate rules. Restore iptables at once using iptables-restore instead of calling iptables numerous times Add iptables copnversion script. Signed-off-by: Will Stevens <williamstevens@gmail.com>
1013 lines
40 KiB
Python
Executable File
1013 lines
40 KiB
Python
Executable File
#!/usr/bin/python
|
|
# -- coding: utf-8 --
|
|
# 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.
|
|
import sys
|
|
import base64
|
|
|
|
import re
|
|
from fcntl import flock, LOCK_EX, LOCK_UN
|
|
|
|
from cs.CsDatabag import CsDataBag
|
|
from cs.CsNetfilter import CsNetfilters
|
|
from cs.CsDhcp import CsDhcp
|
|
from cs.CsRedundant import *
|
|
from cs.CsFile import CsFile
|
|
from cs.CsMonitor import CsMonitor
|
|
from cs.CsLoadBalancer import CsLoadBalancer
|
|
from cs.CsConfig import CsConfig
|
|
from cs.CsProcess import CsProcess
|
|
from cs.CsStaticRoutes import CsStaticRoutes
|
|
|
|
|
|
class CsPassword(CsDataBag):
|
|
|
|
TOKEN_FILE="/tmp/passwdsrvrtoken"
|
|
|
|
def process(self):
|
|
for item in self.dbag:
|
|
if item == "id":
|
|
continue
|
|
self.__update(item, self.dbag[item])
|
|
|
|
def __update(self, vm_ip, password):
|
|
token = ""
|
|
try:
|
|
tokenFile = open(self.TOKEN_FILE)
|
|
token = tokenFile.read()
|
|
except IOError:
|
|
logging.debug("File %s does not exist" % self.TOKEN_FILE)
|
|
|
|
ips_cmd = "ip addr show | grep inet | awk '{print $2}'"
|
|
ips = CsHelper.execute(ips_cmd)
|
|
for ip in ips:
|
|
server_ip = ip.split('/')[0]
|
|
proc = CsProcess(['/opt/cloud/bin/passwd_server_ip.py', server_ip])
|
|
if proc.find():
|
|
update_command = 'curl --header "DomU_Request: save_password" "http://{SERVER_IP}:8080/" -F "ip={VM_IP}" -F "password={PASSWORD}" ' \
|
|
'-F "token={TOKEN}" >/dev/null 2>/dev/null &'.format(SERVER_IP=server_ip, VM_IP=vm_ip, PASSWORD=password, TOKEN=token)
|
|
result = CsHelper.execute(update_command)
|
|
logging.debug("Update password server result ==> %s" % result)
|
|
|
|
|
|
class CsAcl(CsDataBag):
|
|
"""
|
|
Deal with Network acls
|
|
"""
|
|
|
|
class AclIP():
|
|
""" For type Virtual Router """
|
|
|
|
def __init__(self, obj, fw):
|
|
self.fw = fw.get_fw()
|
|
self.direction = 'egress'
|
|
if obj['traffic_type'] == 'Ingress':
|
|
self.direction = 'ingress'
|
|
self.device = ''
|
|
self.ip = obj['src_ip']
|
|
self.rule = obj
|
|
self.rule['type'] = obj['protocol']
|
|
# src_port_range
|
|
if 'src_port_range' in obj:
|
|
self.rule['first_port'] = obj['src_port_range'][0]
|
|
self.rule['last_port'] = obj['src_port_range'][1]
|
|
|
|
self.rule['allowed'] = True
|
|
self.rule['action'] = "ACCEPT"
|
|
|
|
if self.rule['type'] == 'all' and not obj['source_cidr_list']:
|
|
self.rule['cidr'] = ['0.0.0.0/0']
|
|
else:
|
|
self.rule['cidr'] = obj['source_cidr_list']
|
|
|
|
logging.debug("AclIP created for rule ==> %s", self.rule)
|
|
|
|
def create(self):
|
|
for cidr in self.rule['cidr']:
|
|
self.add_rule(cidr)
|
|
|
|
def add_rule(self, cidr):
|
|
icmp_type = ''
|
|
rule = self.rule
|
|
icmp_type = "any"
|
|
if "icmp_type" in self.rule.keys() and self.rule['icmp_type'] != -1:
|
|
icmp_type = self.rule['icmp_type']
|
|
if "icmp_code" in self.rule.keys() and rule['icmp_code'] != -1:
|
|
icmp_type = "%s/%s" % (self.rule['icmp_type'], self.rule['icmp_code'])
|
|
rnge = ''
|
|
if "first_port" in self.rule.keys() and \
|
|
self.rule['first_port'] == self.rule['last_port']:
|
|
rnge = self.rule['first_port']
|
|
if "first_port" in self.rule.keys() and \
|
|
self.rule['first_port'] != self.rule['last_port']:
|
|
rnge = "%s:%s" % (rule['first_port'], rule['last_port'])
|
|
if self.direction == 'ingress':
|
|
if rule['protocol'] == "icmp":
|
|
self.fw.append(["mangle", "front",
|
|
" -A FIREWALL_%s" % self.ip +
|
|
" -s %s " % cidr +
|
|
" -p %s " % rule['protocol'] +
|
|
" -m %s " % rule['protocol'] +
|
|
" --icmp-type %s -j %s" % (icmp_type, self.rule['action'])])
|
|
else:
|
|
self.fw.append(["mangle", "front",
|
|
" -A FIREWALL_%s" % self.ip +
|
|
" -s %s " % cidr +
|
|
" -p %s " % rule['protocol'] +
|
|
" -m %s " % rule['protocol'] +
|
|
" --dport %s -j RETURN" % rnge])
|
|
|
|
logging.debug("Current ACL IP direction is ==> %s", self.direction)
|
|
if self.direction == 'egress':
|
|
self.fw.append(["filter", "", " -A FW_OUTBOUND -j FW_EGRESS_RULES"])
|
|
if rule['protocol'] == "icmp":
|
|
self.fw.append(["filter", "front",
|
|
" -A FW_EGRESS_RULES" +
|
|
" -s %s " % cidr +
|
|
" -p %s " % rule['protocol'] +
|
|
" -m %s " % rule['protocol'] +
|
|
" --icmp-type %s -j %s" % (icmp_type, self.rule['action'])])
|
|
else:
|
|
fwr = " -I FW_EGRESS_RULES"
|
|
#In case we have a default rule (accept all or drop all), we have to evaluate the action again.
|
|
if rule['type'] == 'all' and not rule['source_cidr_list']:
|
|
fwr = " -A FW_EGRESS_RULES"
|
|
# For default egress ALLOW or DENY, the logic is inverted.
|
|
# Having default_egress_policy == True, means that the default rule should have ACCEPT,
|
|
# otherwise DROP. The rule should be appended, not inserted.
|
|
if self.rule['default_egress_policy']:
|
|
self.rule['action'] = "ACCEPT"
|
|
else:
|
|
self.rule['action'] = "DROP"
|
|
else:
|
|
# For other rules added, if default_egress_policy == True, following rules should be DROP,
|
|
# otherwise ACCEPT
|
|
if self.rule['default_egress_policy']:
|
|
self.rule['action'] = "DROP"
|
|
else:
|
|
self.rule['action'] = "ACCEPT"
|
|
|
|
if rule['protocol'] != "all":
|
|
fwr += " -s %s " % cidr + \
|
|
" -p %s " % rule['protocol'] + \
|
|
" -m %s " % rule['protocol'] + \
|
|
" --dport %s" % rnge
|
|
|
|
self.fw.append(["filter", "", "%s -j %s" % (fwr, rule['action'])])
|
|
|
|
logging.debug("EGRESS rule configured for protocol ==> %s, action ==> %s", rule['protocol'], rule['action'])
|
|
|
|
class AclDevice():
|
|
""" A little class for each list of acls per device """
|
|
|
|
FIXED_RULES_INGRESS = 3
|
|
FIXED_RULES_EGRESS = 3
|
|
|
|
def __init__(self, obj, config):
|
|
self.ingess = []
|
|
self.egress = []
|
|
self.device = obj['device']
|
|
self.ip = obj['nic_ip']
|
|
self.netmask = obj['nic_netmask']
|
|
self.config = config
|
|
self.cidr = "%s/%s" % (self.ip, self.netmask)
|
|
if "ingress_rules" in obj.keys():
|
|
self.ingress = obj['ingress_rules']
|
|
if "egress_rules" in obj.keys():
|
|
self.egress = obj['egress_rules']
|
|
self.fw = config.get_fw()
|
|
|
|
def create(self):
|
|
self.process("ingress", self.ingress, self.FIXED_RULES_INGRESS)
|
|
self.process("egress", self.egress, self.FIXED_RULES_EGRESS)
|
|
|
|
def process(self, direction, rule_list, base):
|
|
count = base
|
|
rule_list_splitted = []
|
|
for rule in rule_list:
|
|
if ',' in rule['cidr']:
|
|
cidrs = rule['cidr'].split(',')
|
|
for cidr in cidrs:
|
|
new_rule = {
|
|
'cidr': cidr,
|
|
'last_port': rule['last_port'],
|
|
'type': rule['type'],
|
|
'first_port': rule['first_port'],
|
|
'allowed': rule['allowed']
|
|
}
|
|
rule_list_splitted.append(new_rule)
|
|
else:
|
|
rule_list_splitted.append(rule)
|
|
|
|
for i in rule_list_splitted:
|
|
r = self.AclRule(direction, self, i, self.config, count)
|
|
r.create()
|
|
count += 1
|
|
|
|
class AclRule():
|
|
|
|
def __init__(self, direction, acl, rule, config, count):
|
|
self.count = count
|
|
if config.is_vpc():
|
|
self.init_vpc(direction, acl, rule, config)
|
|
|
|
def init_vpc(self, direction, acl, rule, config):
|
|
self.table = ""
|
|
self.device = acl.device
|
|
self.direction = direction
|
|
# acl is an object of the AclDevice type. So, its fw attribute is already a list.
|
|
self.fw = acl.fw
|
|
self.chain = config.get_ingress_chain(self.device, acl.ip)
|
|
self.dest = "-s %s" % rule['cidr']
|
|
if direction == "egress":
|
|
self.table = config.get_egress_table()
|
|
self.chain = config.get_egress_chain(self.device, acl.ip)
|
|
self.dest = "-d %s" % rule['cidr']
|
|
self.type = ""
|
|
self.type = rule['type']
|
|
self.icmp_type = "any"
|
|
self.protocol = self.type
|
|
if "icmp_type" in rule.keys() and rule['icmp_type'] != -1:
|
|
self.icmp_type = rule['icmp_type']
|
|
if "icmp_code" in rule.keys() and rule['icmp_code'] != -1:
|
|
self.icmp_type = "%s/%s" % (self.icmp_type, rule['icmp_code'])
|
|
if self.type == "protocol":
|
|
if rule['protocol'] == 41:
|
|
rule['protocol'] = "ipv6"
|
|
self.protocol = rule['protocol']
|
|
self.action = "DROP"
|
|
self.dport = ""
|
|
if 'allowed' in rule.keys() and rule['allowed']:
|
|
self.action = "ACCEPT"
|
|
if 'first_port' in rule.keys():
|
|
self.dport = "-m %s --dport %s" % (self.protocol, rule['first_port'])
|
|
if 'last_port' in rule.keys() and self.dport and \
|
|
rule['last_port'] != rule['first_port']:
|
|
self.dport = "%s:%s" % (self.dport, rule['last_port'])
|
|
|
|
def create(self):
|
|
rstr = ""
|
|
rstr = "%s -A %s -p %s %s" % (rstr, self.chain, self.protocol, self.dest)
|
|
if self.type == "icmp":
|
|
rstr = "%s -m icmp --icmp-type %s" % (rstr, self.icmp_type)
|
|
rstr = "%s %s -j %s" % (rstr, self.dport, self.action)
|
|
rstr = rstr.replace(" ", " ").lstrip()
|
|
self.fw.append([self.table, "", rstr])
|
|
|
|
def process(self):
|
|
for item in self.dbag:
|
|
if item == "id":
|
|
continue
|
|
if self.config.is_vpc():
|
|
self.AclDevice(self.dbag[item], self.config).create()
|
|
else:
|
|
self.AclIP(self.dbag[item], self.config).create()
|
|
|
|
|
|
class CsVmMetadata(CsDataBag):
|
|
|
|
def process(self):
|
|
for ip in self.dbag:
|
|
if ("id" == ip):
|
|
continue
|
|
logging.info("Processing metadata for %s" % ip)
|
|
for item in self.dbag[ip]:
|
|
folder = item[0]
|
|
file = item[1]
|
|
data = item[2]
|
|
|
|
# process only valid data
|
|
if folder != "userdata" and folder != "metadata":
|
|
continue
|
|
|
|
if file == "":
|
|
continue
|
|
|
|
self.__htaccess(ip, folder, file)
|
|
|
|
if data == "":
|
|
self.__deletefile(ip, folder, file)
|
|
else:
|
|
self.__createfile(ip, folder, file, data)
|
|
|
|
def __deletefile(self, ip, folder, file):
|
|
datafile = "/var/www/html/" + folder + "/" + ip + "/" + file
|
|
|
|
if os.path.exists(datafile):
|
|
os.remove(datafile)
|
|
|
|
def __createfile(self, ip, folder, file, data):
|
|
dest = "/var/www/html/" + folder + "/" + ip + "/" + file
|
|
metamanifestdir = "/var/www/html/" + folder + "/" + ip
|
|
metamanifest = metamanifestdir + "/meta-data"
|
|
|
|
# base64 decode userdata
|
|
if folder == "userdata" or folder == "user-data":
|
|
if data is not None:
|
|
data = base64.b64decode(data)
|
|
|
|
fh = open(dest, "w")
|
|
self.__exflock(fh)
|
|
if data is not None:
|
|
fh.write(data)
|
|
else:
|
|
fh.write("")
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
os.chmod(dest, 0644)
|
|
|
|
if folder == "metadata" or folder == "meta-data":
|
|
try:
|
|
os.makedirs(metamanifestdir, 0755)
|
|
except OSError as e:
|
|
# error 17 is already exists, we do it this way for concurrency
|
|
if e.errno != 17:
|
|
print "failed to make directories " + metamanifestdir + " due to :" + e.strerror
|
|
sys.exit(1)
|
|
if os.path.exists(metamanifest):
|
|
fh = open(metamanifest, "r+a")
|
|
self.__exflock(fh)
|
|
if file not in fh.read():
|
|
fh.write(file + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
else:
|
|
fh = open(metamanifest, "w")
|
|
self.__exflock(fh)
|
|
fh.write(file + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
|
|
if os.path.exists(metamanifest):
|
|
os.chmod(metamanifest, 0644)
|
|
|
|
def __htaccess(self, ip, folder, file):
|
|
entry = "RewriteRule ^" + file + "$ ../" + folder + "/%{REMOTE_ADDR}/" + file + " [L,NC,QSA]"
|
|
htaccessFolder = "/var/www/html/latest"
|
|
htaccessFile = htaccessFolder + "/.htaccess"
|
|
|
|
CsHelper.mkdir(htaccessFolder, 0755, True)
|
|
|
|
if os.path.exists(htaccessFile):
|
|
fh = open(htaccessFile, "r+a")
|
|
self.__exflock(fh)
|
|
if entry not in fh.read():
|
|
fh.write(entry + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
else:
|
|
fh = open(htaccessFile, "w")
|
|
self.__exflock(fh)
|
|
fh.write("Options +FollowSymLinks\nRewriteEngine On\n\n")
|
|
fh.write(entry + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
|
|
entry = "Options -Indexes\nOrder Deny,Allow\nDeny from all\nAllow from " + ip
|
|
htaccessFolder = "/var/www/html/" + folder + "/" + ip
|
|
htaccessFile = htaccessFolder+"/.htaccess"
|
|
|
|
try:
|
|
os.makedirs(htaccessFolder, 0755)
|
|
except OSError as e:
|
|
# error 17 is already exists, we do it this way for sake of concurrency
|
|
if e.errno != 17:
|
|
print "failed to make directories " + htaccessFolder + " due to :" + e.strerror
|
|
sys.exit(1)
|
|
|
|
fh = open(htaccessFile, "w")
|
|
self.__exflock(fh)
|
|
fh.write(entry + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
|
|
if folder == "metadata" or folder == "meta-data":
|
|
entry = "RewriteRule ^meta-data/(.+)$ ../" + folder + "/%{REMOTE_ADDR}/$1 [L,NC,QSA]"
|
|
htaccessFolder = "/var/www/html/latest"
|
|
htaccessFile = htaccessFolder + "/.htaccess"
|
|
|
|
fh = open(htaccessFile, "r+a")
|
|
self.__exflock(fh)
|
|
if entry not in fh.read():
|
|
fh.write(entry + '\n')
|
|
|
|
entry = "RewriteRule ^meta-data/$ ../" + folder + "/%{REMOTE_ADDR}/meta-data [L,NC,QSA]"
|
|
|
|
fh.seek(0)
|
|
if entry not in fh.read():
|
|
fh.write(entry + '\n')
|
|
self.__unflock(fh)
|
|
fh.close()
|
|
|
|
def __exflock(self, file):
|
|
try:
|
|
flock(file, LOCK_EX)
|
|
except IOError as e:
|
|
print "failed to lock file" + file.name + " due to : " + e.strerror
|
|
sys.exit(1) # FIXME
|
|
return True
|
|
|
|
def __unflock(self, file):
|
|
try:
|
|
flock(file, LOCK_UN)
|
|
except IOError:
|
|
print "failed to unlock file" + file.name + " due to : " + e.strerror
|
|
sys.exit(1) # FIXME
|
|
return True
|
|
|
|
|
|
class CsSite2SiteVpn(CsDataBag):
|
|
"""
|
|
Setup any configured vpns (using swan)
|
|
left is the local machine
|
|
right is where the clients connect from
|
|
"""
|
|
|
|
VPNCONFDIR = "/etc/ipsec.d"
|
|
|
|
def process(self):
|
|
self.confips = []
|
|
# collect a list of configured vpns
|
|
for file in os.listdir(self.VPNCONFDIR):
|
|
m = re.search("^ipsec.vpn-(.*).conf", file)
|
|
if m:
|
|
self.confips.append(m.group(1))
|
|
|
|
for vpn in self.dbag:
|
|
if vpn == "id":
|
|
continue
|
|
|
|
local_ip = self.dbag[vpn]['local_public_ip']
|
|
dev = CsHelper.get_device(local_ip)
|
|
|
|
if dev == "":
|
|
logging.error("Request for ipsec to %s not possible because ip is not configured", local_ip)
|
|
continue
|
|
|
|
CsHelper.start_if_stopped("ipsec")
|
|
self.configure_iptables(dev, self.dbag[vpn])
|
|
self.configure_ipsec(self.dbag[vpn])
|
|
|
|
# Delete vpns that are no longer in the configuration
|
|
for ip in self.confips:
|
|
self.deletevpn(ip)
|
|
|
|
def deletevpn(self, ip):
|
|
logging.info("Removing VPN configuration for %s", ip)
|
|
CsHelper.execute("ipsec auto --down vpn-%s" % ip)
|
|
CsHelper.execute("ipsec auto --delete vpn-%s" % ip)
|
|
vpnconffile = "%s/ipsec.vpn-%s.conf" % (self.VPNCONFDIR, ip)
|
|
vpnsecretsfile = "%s/ipsec.vpn-%s.secrets" % (self.VPNCONFDIR, ip)
|
|
os.remove(vpnconffile)
|
|
os.remove(vpnsecretsfile)
|
|
CsHelper.execute("ipsec auto --rereadall")
|
|
|
|
def configure_iptables(self, dev, obj):
|
|
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
|
|
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 4500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
|
|
self.fw.append(["", "front", "-A INPUT -i %s -p esp -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
|
|
self.fw.append(["nat", "front", "-A POSTROUTING -o %s -m mark --mark 0x525 -j ACCEPT" % dev])
|
|
for net in obj['peer_guest_cidr_list'].lstrip().rstrip().split(','):
|
|
self.fw.append(["mangle", "front",
|
|
"-A FORWARD -s %s -d %s -j MARK --set-xmark 0x525/0xffffffff" % (obj['local_guest_cidr'], net)])
|
|
self.fw.append(["mangle", "",
|
|
"-A OUTPUT -s %s -d %s -j MARK --set-xmark 0x525/0xffffffff" % (obj['local_guest_cidr'], net)])
|
|
self.fw.append(["mangle", "front",
|
|
"-A FORWARD -s %s -d %s -j MARK --set-xmark 0x524/0xffffffff" % (net, obj['local_guest_cidr'])])
|
|
self.fw.append(["mangle", "",
|
|
"-A INPUT -s %s -d %s -j MARK --set-xmark 0x524/0xffffffff" % (net, obj['local_guest_cidr'])])
|
|
|
|
def configure_ipsec(self, obj):
|
|
leftpeer = obj['local_public_ip']
|
|
rightpeer = obj['peer_gateway_ip']
|
|
peerlist = obj['peer_guest_cidr_list'].lstrip().rstrip().replace(',', ' ')
|
|
vpnconffile = "%s/ipsec.vpn-%s.conf" % (self.VPNCONFDIR, rightpeer)
|
|
vpnsecretsfile = "%s/ipsec.vpn-%s.secrets" % (self.VPNCONFDIR, rightpeer)
|
|
if rightpeer in self.confips:
|
|
self.confips.remove(rightpeer)
|
|
file = CsFile(vpnconffile)
|
|
file.search("conn ", "conn vpn-%s" % rightpeer)
|
|
file.addeq(" left=%s" % leftpeer)
|
|
file.addeq(" leftsubnet=%s" % obj['local_guest_cidr'])
|
|
file.addeq(" leftnexthop=%s" % obj['local_public_gateway'])
|
|
file.addeq(" right=%s" % rightpeer)
|
|
file.addeq(" rightsubnets={%s}" % peerlist)
|
|
file.addeq(" type=tunnel")
|
|
file.addeq(" authby=secret")
|
|
file.addeq(" keyexchange=ike")
|
|
file.addeq(" ike=%s" % obj['ike_policy'])
|
|
file.addeq(" ikelifetime=%s" % self.convert_sec_to_h(obj['ike_lifetime']))
|
|
file.addeq(" esp=%s" % obj['esp_policy'])
|
|
file.addeq(" salifetime=%s" % self.convert_sec_to_h(obj['esp_lifetime']))
|
|
file.addeq(" pfs=%s" % CsHelper.bool_to_yn(obj['dpd']))
|
|
file.addeq(" keyingtries=2")
|
|
file.addeq(" auto=start")
|
|
if 'encap' not in obj:
|
|
obj['encap']=False
|
|
file.addeq(" forceencaps=%s" % CsHelper.bool_to_yn(obj['encap']))
|
|
if obj['dpd']:
|
|
file.addeq(" dpddelay=30")
|
|
file.addeq(" dpdtimeout=120")
|
|
file.addeq(" dpdaction=restart")
|
|
secret = CsFile(vpnsecretsfile)
|
|
secret.search("%s " % leftpeer, "%s %s: PSK \"%s\"" % (leftpeer, rightpeer, obj['ipsec_psk']))
|
|
if secret.is_changed() or file.is_changed():
|
|
secret.commit()
|
|
file.commit()
|
|
logging.info("Configured vpn %s %s", leftpeer, rightpeer)
|
|
CsHelper.execute("ipsec auto --rereadall")
|
|
CsHelper.execute("ipsec auto --add vpn-%s" % rightpeer)
|
|
if not obj['passive']:
|
|
CsHelper.execute("ipsec auto --up vpn-%s" % rightpeer)
|
|
os.chmod(vpnsecretsfile, 0o400)
|
|
|
|
def convert_sec_to_h(self, val):
|
|
hrs = int(val) / 3600
|
|
return "%sh" % hrs
|
|
|
|
class CsVpnUser(CsDataBag):
|
|
PPP_CHAP='/etc/ppp/chap-secrets'
|
|
|
|
def process(self):
|
|
for user in self.dbag:
|
|
if user == 'id':
|
|
continue
|
|
|
|
userconfig=self.dbag[user]
|
|
if userconfig['add']:
|
|
self.add_l2tp_ipsec_user(user, userconfig)
|
|
else:
|
|
self.del_l2tp_ipsec_user(user, userconfig)
|
|
|
|
def add_l2tp_ipsec_user(self, user, obj):
|
|
userfound = False
|
|
password = obj['password']
|
|
|
|
userSearchEntry = "%s \* %s \*"%(user,password)
|
|
userAddEntry = "%s * %s *" %(user,password)
|
|
logging.debug("Adding vpn user %s" %userSearchEntry)
|
|
|
|
file = CsFile(self.PPP_CHAP)
|
|
userfound = file.searchString(userSearchEntry, '#')
|
|
if not userfound:
|
|
logging.debug("User is not there already, so adding user ")
|
|
self.del_l2tp_ipsec_user(user, obj)
|
|
file.add(userAddEntry)
|
|
file.commit()
|
|
|
|
|
|
def del_l2tp_ipsec_user(self, user, obj):
|
|
userfound = False
|
|
password = obj['password']
|
|
userentry = "%s \* %s \*"%(user,password)
|
|
|
|
logging.debug("Deleting the user %s " % user)
|
|
file = CsFile(self.PPP_CHAP)
|
|
file.deleteLine(userentry)
|
|
file.commit()
|
|
|
|
if not os.path.exists('/var/run/pppd2.tdb'):
|
|
return
|
|
|
|
logging.debug("kiing the PPPD process for the user %s " % user)
|
|
|
|
fileContents = CsHelper.execute("tdbdump /var/run/pppd2.tdb")
|
|
print fileContents
|
|
|
|
for line in fileContents:
|
|
if user in line:
|
|
contentlist = line.split(';')
|
|
for str in contentlist:
|
|
print 'in del_l2tp str = '+ str
|
|
pppd = str.split('=')[0]
|
|
if pppd == 'PPPD_PID':
|
|
pid = str.split('=')[1]
|
|
if pid:
|
|
logging.debug("killing process %s" %pid)
|
|
CsHelper.execute('kill -9 %s' % pid)
|
|
|
|
|
|
|
|
class CsRemoteAccessVpn(CsDataBag):
|
|
VPNCONFDIR = "/etc/ipsec.d"
|
|
|
|
def process(self):
|
|
self.confips = []
|
|
|
|
logging.debug(self.dbag)
|
|
for public_ip in self.dbag:
|
|
if public_ip == "id":
|
|
continue
|
|
vpnconfig=self.dbag[public_ip]
|
|
|
|
#Enable remote access vpn
|
|
if vpnconfig['create']:
|
|
logging.debug("Enabling remote access vpn on "+ public_ip)
|
|
self.configure_l2tpIpsec(public_ip, self.dbag[public_ip])
|
|
logging.debug("Remote accessvpn data bag %s", self.dbag)
|
|
self.remoteaccessvpn_iptables(public_ip, self.dbag[public_ip])
|
|
|
|
CsHelper.execute("ipsec auto --rereadall")
|
|
CsHelper.execute("service xl2tpd stop")
|
|
CsHelper.execute("service xl2tpd start")
|
|
CsHelper.execute("ipsec auto --rereadsecrets")
|
|
CsHelper.execute("ipsec auto --replace L2TP-PSK")
|
|
else:
|
|
logging.debug("Disabling remote access vpn .....")
|
|
#disable remote access vpn
|
|
CsHelper.execute("ipsec auto --down L2TP-PSK")
|
|
CsHelper.execute("service xl2tpd stop")
|
|
|
|
|
|
def configure_l2tpIpsec(self, left, obj):
|
|
vpnconffile="%s/l2tp.conf" % (self.VPNCONFDIR)
|
|
vpnsecretfilte="%s/ipsec.any.secrets" % (self.VPNCONFDIR)
|
|
xl2tpdconffile="/etc/xl2tpd/xl2tpd.conf"
|
|
xl2tpoptionsfile='/etc/ppp/options.xl2tpd'
|
|
|
|
file = CsFile(vpnconffile)
|
|
localip=obj['local_ip']
|
|
localcidr=obj['local_cidr']
|
|
publicIface=obj['public_interface']
|
|
iprange=obj['ip_range']
|
|
psk=obj['preshared_key']
|
|
|
|
#left
|
|
file.addeq(" left=%s" % left)
|
|
file.commit()
|
|
|
|
|
|
secret = CsFile(vpnsecretfilte)
|
|
secret.addeq(": PSK \"%s\"" %psk)
|
|
secret.commit()
|
|
|
|
xl2tpdconf = CsFile(xl2tpdconffile)
|
|
xl2tpdconf.addeq("ip range = %s" %iprange)
|
|
xl2tpdconf.addeq("local ip = %s" %localip)
|
|
xl2tpdconf.commit()
|
|
|
|
xl2tpoptions=CsFile(xl2tpoptionsfile)
|
|
xl2tpoptions.search("ms-dns ", "ms-dns %s" %localip)
|
|
xl2tpoptions.commit()
|
|
|
|
def remoteaccessvpn_iptables(self, publicip, obj):
|
|
publicdev=obj['public_interface']
|
|
localcidr=obj['local_cidr']
|
|
local_ip=obj['local_ip']
|
|
|
|
|
|
self.fw.append(["", "", "-A INPUT -i %s --dst %s -p udp -m udp --dport 500 -j ACCEPT" % (publicdev, publicip)])
|
|
self.fw.append(["", "", "-A INPUT -i %s --dst %s -p udp -m udp --dport 4500 -j ACCEPT" % (publicdev, publicip)])
|
|
self.fw.append(["", "", "-A INPUT -i %s --dst %s -p udp -m udp --dport 1701 -j ACCEPT" % (publicdev, publicip)])
|
|
self.fw.append(["", "", "-A INPUT -i %s -p ah -j ACCEPT" % publicdev])
|
|
self.fw.append(["", "", "-A INPUT -i %s -p esp -j ACCEPT" % publicdev])
|
|
|
|
if self.config.is_vpc():
|
|
self.fw.append(["", ""," -N VPN_FORWARD"])
|
|
self.fw.append(["", "","-I FORWARD -i ppp+ -j VPN_FORWARD"])
|
|
self.fw.append(["", "","-I FORWARD -o ppp+ -j VPN_FORWARD"])
|
|
self.fw.append(["", "","-I FORWARD -o ppp+ -j VPN_FORWARD"])
|
|
self.fw.append(["", "","-A VPN_FORWARD -s %s -j RETURN" %localcidr])
|
|
self.fw.append(["", "","-A VPN_FORWARD -i ppp+ -d %s -j RETURN" %localcidr])
|
|
self.fw.append(["", "","-A VPN_FORWARD -i ppp+ -o ppp+ -j RETURN"])
|
|
else:
|
|
self.fw.append(["", "","-A FORWARD -i ppp+ -o ppp+ -j ACCEPT"])
|
|
self.fw.append(["", "","-A FORWARD -s %s -o ppp+ -j ACCEPT" % localcidr])
|
|
self.fw.append(["", "","-A FORWARD -i ppp+ -d %s -j ACCEPT" % localcidr])
|
|
|
|
|
|
self.fw.append(["", "","-A INPUT -i ppp+ -m udp -p udp --dport 53 -j ACCEPT"])
|
|
self.fw.append(["", "","-A INPUT -i ppp+ -m tcp -p tcp --dport 53 -j ACCEPT"])
|
|
self.fw.append(["nat", "","-I PREROUTING -i ppp+ -m tcp --dport 53 -j DNAT --to-destination %s" % local_ip])
|
|
|
|
if self.config.is_vpc():
|
|
return
|
|
|
|
self.fw.append(["mangle", "","-N VPN_%s " %publicip])
|
|
self.fw.append(["mangle", "","-A VPN_%s -j RETURN " % publicip])
|
|
self.fw.append(["mangle", "","-I VPN_%s -p ah -j ACCEPT " % publicip])
|
|
self.fw.append(["mangle", "","-I VPN_%s -p esp -j ACCEPT " % publicip])
|
|
self.fw.append(["mangle", "","-I PREROUTING -d %s -j VPN_%s " % (publicip, publicip)])
|
|
|
|
|
|
class CsForwardingRules(CsDataBag):
|
|
|
|
def process(self):
|
|
for public_ip in self.dbag:
|
|
if public_ip == "id":
|
|
continue
|
|
for rule in self.dbag[public_ip]:
|
|
if rule["type"] == "forward":
|
|
self.processForwardRule(rule)
|
|
elif rule["type"] == "staticnat":
|
|
self.processStaticNatRule(rule)
|
|
|
|
#return the VR guest interface ip
|
|
def getGuestIp(self):
|
|
interfaces = []
|
|
ipAddr = None
|
|
for interface in self.config.address().get_interfaces():
|
|
if interface.is_guest():
|
|
interfaces.append(interface)
|
|
if len(interfaces) > 0:
|
|
ipAddr = sorted(interfaces)[-1]
|
|
if ipAddr:
|
|
return ipAddr.get_ip()
|
|
|
|
return None
|
|
|
|
def getDeviceByIp(self, ipa):
|
|
for interface in self.config.address().get_interfaces():
|
|
if interface.ip_in_subnet(ipa):
|
|
return interface.get_device()
|
|
return None
|
|
|
|
def getNetworkByIp(self, ipa):
|
|
for interface in self.config.address().get_interfaces():
|
|
if interface.ip_in_subnet(ipa):
|
|
return interface.get_network()
|
|
return None
|
|
|
|
def getGatewayByIp(self, ipa):
|
|
for interface in self.config.address().get_interfaces():
|
|
if interface.ip_in_subnet(ipa):
|
|
return interface.get_gateway()
|
|
return None
|
|
|
|
def portsToString(self, ports, delimiter):
|
|
ports_parts = ports.split(":", 2)
|
|
if ports_parts[0] == ports_parts[1]:
|
|
return str(ports_parts[0])
|
|
else:
|
|
return "%s%s%s" % (ports_parts[0], delimiter, ports_parts[1])
|
|
|
|
def processForwardRule(self, rule):
|
|
if self.config.is_vpc():
|
|
self.forward_vpc(rule)
|
|
else:
|
|
self.forward_vr(rule)
|
|
|
|
def forward_vr(self, rule):
|
|
#prefetch iptables variables
|
|
public_fwinterface = self.getDeviceByIp(rule['public_ip'])
|
|
internal_fwinterface = self.getDeviceByIp(rule['internal_ip'])
|
|
public_fwports = self.portsToString(rule['public_ports'], ':')
|
|
internal_fwports = self.portsToString(rule['internal_ports'], '-')
|
|
fw1 = "-A PREROUTING -d %s/32 -i %s -p %s -m %s --dport %s -j DNAT --to-destination %s:%s" % \
|
|
(
|
|
rule['public_ip'],
|
|
public_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
public_fwports,
|
|
rule['internal_ip'],
|
|
internal_fwports
|
|
)
|
|
fw2 = "-A PREROUTING -d %s/32 -i %s -p %s -m %s --dport %s -j DNAT --to-destination %s:%s" % \
|
|
(
|
|
rule['public_ip'],
|
|
internal_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
public_fwports,
|
|
rule['internal_ip'],
|
|
internal_fwports
|
|
)
|
|
fw3 = "-A OUTPUT -d %s/32 -p %s -m %s --dport %s -j DNAT --to-destination %s:%s" % \
|
|
(
|
|
rule['public_ip'],
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
public_fwports,
|
|
rule['internal_ip'],
|
|
internal_fwports
|
|
)
|
|
fw4 = "-A POSTROUTING -j SNAT --to-source %s -s %s -d %s/32 -o %s -p %s -m %s --dport %s" % \
|
|
(
|
|
self.getGuestIp(),
|
|
self.getNetworkByIp(rule['internal_ip']),
|
|
rule['internal_ip'],
|
|
internal_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
self.portsToString(rule['internal_ports'], ':')
|
|
)
|
|
fw5 = "-A PREROUTING -d %s/32 -i %s -p %s -m %s --dport %s -j MARK --set-xmark %s/0xffffffff" % \
|
|
(
|
|
rule['public_ip'],
|
|
public_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
public_fwports,
|
|
hex(int(public_fwinterface[3:]))
|
|
)
|
|
fw6 = "-A PREROUTING -d %s/32 -i %s -p %s -m %s --dport %s -m state --state NEW -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff" % \
|
|
(
|
|
rule['public_ip'],
|
|
public_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
public_fwports,
|
|
)
|
|
fw7 = "-A FORWARD -i %s -o %s -p %s -m %s --dport %s -m state --state NEW,ESTABLISHED -j ACCEPT" % \
|
|
(
|
|
public_fwinterface,
|
|
internal_fwinterface,
|
|
rule['protocol'],
|
|
rule['protocol'],
|
|
self.portsToString(rule['internal_ports'], ':')
|
|
)
|
|
self.fw.append(["nat", "", fw1])
|
|
self.fw.append(["nat", "", fw2])
|
|
self.fw.append(["nat", "", fw3])
|
|
self.fw.append(["nat", "", fw4])
|
|
self.fw.append(["nat", "", fw5])
|
|
self.fw.append(["nat", "", fw6])
|
|
self.fw.append(["filter", "", fw7])
|
|
|
|
def forward_vpc(self, rule):
|
|
fw_prerout_rule = "-A PREROUTING -d %s/32 -i %s" % (rule["public_ip"], self.getDeviceByIp(rule['public_ip']))
|
|
if not rule["protocol"] == "any":
|
|
fw_prerout_rule += " -m %s -p %s" % (rule["protocol"], rule["protocol"])
|
|
if not rule["public_ports"] == "any":
|
|
fw_prerout_rule += " --dport %s" % self.portsToString(rule["public_ports"], ":")
|
|
fw_prerout_rule += " -j DNAT --to-destination %s" % rule["internal_ip"]
|
|
if not rule["internal_ports"] == "any":
|
|
fw_prerout_rule += ":" + self.portsToString(rule["internal_ports"], "-")
|
|
|
|
fw_postrout_rule = "-A POSTROUTING -d %s/32 " % rule["public_ip"]
|
|
if not rule["protocol"] == "any":
|
|
fw_postrout_rule += " -m %s -p %s" % (rule["protocol"], rule["protocol"])
|
|
if not rule["public_ports"] == "any":
|
|
fw_postrout_rule += " --dport %s" % self.portsToString(rule["public_ports"], ":")
|
|
fw_postrout_rule += " -j SNAT --to-source %s" % rule["internal_ip"]
|
|
if not rule["internal_ports"] == "any":
|
|
fw_postrout_rule += ":" + self.portsToString(rule["internal_ports"], "-")
|
|
|
|
fw_output_rule = "-A OUTPUT -d %s/32" % rule["public_ip"]
|
|
if not rule["protocol"] == "any":
|
|
fw_output_rule += " -m %s -p %s" % (rule["protocol"], rule["protocol"])
|
|
if not rule["public_ports"] == "any":
|
|
fw_output_rule += " --dport %s" % self.portsToString(rule["public_ports"], ":")
|
|
fw_output_rule += " -j DNAT --to-destination %s" % rule["internal_ip"]
|
|
if not rule["internal_ports"] == "any":
|
|
fw_output_rule += ":" + self.portsToString(rule["internal_ports"], "-")
|
|
|
|
self.fw.append(["nat", "", fw_prerout_rule])
|
|
self.fw.append(["nat", "", fw_postrout_rule])
|
|
self.fw.append(["nat", "", fw_output_rule])
|
|
|
|
def processStaticNatRule(self, rule):
|
|
# FIXME this needs ordering with the VPN no nat rule
|
|
device = self.getDeviceByIp(rule["public_ip"])
|
|
if device is None:
|
|
raise Exception("Ip address %s has no device in the ips databag" % rule["public_ip"])
|
|
self.fw.append(["nat", "front",
|
|
"-A PREROUTING -d %s/32 -j DNAT --to-destination %s" % (rule["public_ip"], rule["internal_ip"])])
|
|
self.fw.append(["nat", "front",
|
|
"-A POSTROUTING -o %s -s %s/32 -j SNAT --to-source %s" % (device, rule["internal_ip"], rule["public_ip"])])
|
|
self.fw.append(["nat", "front",
|
|
"-A OUTPUT -d %s/32 -j DNAT --to-destination %s" % (rule["public_ip"], rule["internal_ip"])])
|
|
self.fw.append(["filter", "",
|
|
"-A FORWARD -i %s -o eth0 -d %s -m state --state NEW -j ACCEPT " % (device, rule["internal_ip"])])
|
|
|
|
#configure the hairpin nat
|
|
self.fw.append(["nat", "front",
|
|
"-A PREROUTING -d %s -i eth0 -j DNAT --to-destination %s" % (rule["public_ip"], rule["internal_ip"])])
|
|
|
|
self.fw.append(["nat", "front", "-A POSTROUTING -s %s -d %s -j SNAT -o eth0 --to-source %s" % (self.getNetworkByIp(rule['internal_ip']),rule["internal_ip"], self.getGuestIp())])
|
|
|
|
|
|
def main(argv):
|
|
# The file we are currently processing, if it is "cmd_line.json" everything will be processed.
|
|
process_file = argv[1]
|
|
|
|
# process_file can be None, if so assume cmd_line.json
|
|
if process_file is None:
|
|
process_file = "cmd_line.json"
|
|
|
|
# Track if changes need to be committed to NetFilter
|
|
iptables_change = False
|
|
|
|
# The "GLOBAL" Configuration object
|
|
config = CsConfig()
|
|
|
|
logging.basicConfig(filename=config.get_logger(),
|
|
level=config.get_level(),
|
|
format=config.get_format())
|
|
|
|
# Load stored ip adresses from disk to CsConfig()
|
|
config.set_address()
|
|
|
|
logging.debug("Configuring ip addresses")
|
|
config.address().compare()
|
|
config.address().process()
|
|
|
|
if process_file in ["cmd_line.json", "guest_network.json"]:
|
|
logging.debug("Configuring Guest Network")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "vm_password.json"]:
|
|
logging.debug("Configuring vmpassword")
|
|
password = CsPassword("vmpassword", config)
|
|
password.process()
|
|
|
|
if process_file in ["cmd_line.json", "vm_metadata.json"]:
|
|
logging.debug("Configuring vmdata")
|
|
metadata = CsVmMetadata('vmdata', config)
|
|
metadata.process()
|
|
|
|
if process_file in ["cmd_line.json", "network_acl.json"]:
|
|
logging.debug("Configuring networkacl")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "firewall_rules.json"]:
|
|
logging.debug("Configuring firewall rules")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "forwarding_rules.json", "staticnat_rules.json"]:
|
|
logging.debug("Configuring PF rules")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "site_2_site_vpn.json"]:
|
|
logging.debug("Configuring s2s vpn")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "remote_access_vpn.json"]:
|
|
logging.debug("Configuring remote access vpn")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "vpn_user_list.json"]:
|
|
logging.debug("Configuring vpn users list")
|
|
vpnuser = CsVpnUser("vpnuserlist", config)
|
|
vpnuser.process()
|
|
|
|
if process_file in ["cmd_line.json", "vm_dhcp_entry.json", "dhcp.json"]:
|
|
logging.debug("Configuring dhcp entry")
|
|
dhcp = CsDhcp("dhcpentry", config)
|
|
dhcp.process()
|
|
|
|
if process_file in ["cmd_line.json", "load_balancer.json"]:
|
|
logging.debug("Configuring load balancer")
|
|
iptables_change = True
|
|
|
|
if process_file in ["cmd_line.json", "monitor_service.json"]:
|
|
logging.debug("Configuring monitor service")
|
|
mon = CsMonitor("monitorservice", config)
|
|
mon.process()
|
|
|
|
# If iptable rules have changed, apply them.
|
|
if iptables_change:
|
|
acls = CsAcl('networkacl', config)
|
|
acls.process()
|
|
|
|
acls = CsAcl('firewallrules', config)
|
|
acls.process()
|
|
|
|
fwd = CsForwardingRules("forwardingrules", config)
|
|
fwd.process()
|
|
|
|
vpns = CsSite2SiteVpn("site2sitevpn", config)
|
|
vpns.process()
|
|
|
|
rvpn = CsRemoteAccessVpn("remoteaccessvpn", config)
|
|
rvpn.process()
|
|
|
|
lb = CsLoadBalancer("loadbalancer", config)
|
|
lb.process()
|
|
|
|
logging.debug("Configuring iptables rules")
|
|
nf = CsNetfilters(False)
|
|
nf.compare(config.get_fw())
|
|
|
|
logging.debug("Configuring iptables rules done ...saving rules")
|
|
|
|
# Save iptables configuration - will be loaded on reboot by the iptables-restore that is configured on /etc/rc.local
|
|
CsHelper.save_iptables("iptables-save", "/etc/iptables/router_rules.v4")
|
|
CsHelper.save_iptables("ip6tables-save", "/etc/iptables/router_rules.v6")
|
|
|
|
red = CsRedundant(config)
|
|
red.set()
|
|
|
|
if process_file in ["cmd_line.json", "static_routes.json"]:
|
|
logging.debug("Configuring static routes")
|
|
static_routes = CsStaticRoutes("staticroutes", config)
|
|
static_routes.process()
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv)
|