mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Using Source NAT option on Private Gateway does not work This fixes #2680 ## Description <!--- Describe your changes in detail --> When you use the Source NAT feature of Private Gateways on a VPC. This should Source NAT all traffic from CloudStack VMs going towards IPs reachable through Private Gateways. This change in this PR, stops adding the Source CIDR to SNAT rules. This should be discussed/reviewed, but i can see no reason why the Source CIDR is needed. There can only be one SNAT IP per interface, except for Static (one-to-one) NATs, which still work with this change in place. The outbound interface is what matters in the rule. <!-- For new features, provide link to FS, dev ML discussion etc. --> <!-- In case of bug fix, the expected and actual behaviours, steps to reproduce. --> ##### SUMMARY <!-- Explain the problem/feature briefly --> There is a bug in the Private Gateway functionality, when Source NAT is enabled for the Private Gateway. When the SNAT is added to iptables, it has the source CIDR of the private gateway subnet. Since no VMs live in that private gateway subnet, the SNAT doesn’t work. ##### STEPS TO REPRODUCE <!-- For bugs, show exactly how to reproduce the problem, using a minimal test-case. Use Screenshots if accurate. For new features, show how the feature would be used. --> <!-- Paste example playbooks or commands between quotes below --> Below is an example: - VMs have IP addresses in the 10.0.0.0/24 subnet. - The Private Gateway address is 10.101.141.2/30 In the outputs below, the SOURCE field for the new SNAT (eth3) only matches if the source is 10.101.141.0/30. Since the VM has an IP address in 10.0.0.0/24, the VMs don’t get SNAT’d as they should when talking across the private gateway. The SOURCE should be set to ANYWHERE. ##### BEFORE ADDING PRIVATE GATEWAY ~~~ Chain POSTROUTING (policy ACCEPT 1 packets, 52 bytes) pkts bytes target prot opt in out source destination 2 736 SNAT all -- any eth2 10.0.0.0/24 anywhere to:10.0.0.1 16 1039 SNAT all -- any eth1 anywhere anywhere to:46.99.52.18 ~~~ <!-- You can also paste gist.github.com links for larger files --> ##### EXPECTED RESULTS <!-- What did you expect to happen when running the steps above? --> ~~~ Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 SNAT all -- any eth3 anywhere anywhere to:10.101.141.2 2 736 SNAT all -- any eth2 anywhere anywhere to:10.0.0.1 23 1515 SNAT all -- any eth1 anywhere anywhere to:46.99.52.18 ~~~ ##### ACTUAL RESULTS <!-- What actually happened? --> <!-- Paste verbatim command output between quotes below --> ~~~ Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 SNAT all -- any eth3 10.101.141.0/30 anywhere to:10.101.141.2 2 736 SNAT all -- any eth2 10.0.0.0/24 anywhere to:10.0.0.1 23 1515 SNAT all -- any eth1 anywhere anywhere to:46.99.52.18 ~~~ ## Types of changes <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] New feature (non-breaking change which adds functionality) - [X] Bug fix (non-breaking change which fixes an issue) - [ ] Enhancement (improves an existing feature and functionality) - [ ] Cleanup (Code refactoring and cleanup, that may add test cases) ## GitHub Issue/PRs <!-- If this PR is to fix an issue or another PR on GH, uncomment the section and provide the id of issue/PR --> <!-- When "Fixes: #<id>" is specified, the issue/PR will automatically be closed when this PR gets merged --> <!-- For addressing multiple issues/PRs, use multiple "Fixes: #<id>" --> Fixes: #2680 ## Screenshots (if appropriate): ## How Has This Been Tested? <!-- Please describe in detail how you tested your changes. --> <!-- Include details of your testing environment, and the tests you ran to --> <!-- see how your change affects other areas of the code, etc. --> ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [x] I have read the [CONTRIBUTING](https://github.com/apache/cloudstack/blob/master/CONTRIBUTING.md) document. - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. Testing - [ ] I have added tests to cover my changes. - [ ] All relevant new and existing integration tests have passed. - [ ] A full integration testsuite with all test that can run on my environment has passed.
1056 lines
44 KiB
Python
Executable File
1056 lines
44 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 base64
|
|
import logging
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
|
|
from collections import OrderedDict
|
|
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 obj['source_cidr_list']:
|
|
self.rule['cidr'] = []
|
|
else:
|
|
self.rule['cidr'] = obj['source_cidr_list']
|
|
|
|
if self.direction == 'egress':
|
|
try:
|
|
if not obj['dest_cidr_list']:
|
|
self.rule['dcidr'] = []
|
|
else:
|
|
self.rule['dcidr'] = obj['dest_cidr_list']
|
|
except Exception:
|
|
self.rule['dcidr'] = []
|
|
|
|
logging.debug("AclIP created for rule ==> %s", self.rule)
|
|
|
|
def create(self):
|
|
self.add_rule()
|
|
|
|
def add_rule(self):
|
|
CIDR_ALL = '0.0.0.0/0'
|
|
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 = " --dport %s " % self.rule['first_port']
|
|
if "first_port" in self.rule.keys() and \
|
|
self.rule['first_port'] != self.rule['last_port']:
|
|
rnge = " --dport %s:%s" % (rule['first_port'], rule['last_port'])
|
|
|
|
logging.debug("Current ACL IP direction is ==> %s", self.direction)
|
|
|
|
if self.direction == 'ingress':
|
|
for cidr in self.rule['cidr']:
|
|
if rule['protocol'] == "icmp":
|
|
self.fw.append(["mangle", "front",
|
|
" -A FIREWALL_%s" % self.ip +
|
|
" -s %s " % cidr +
|
|
" -p %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'] +
|
|
" %s -j %s" % (rnge, self.rule['action'])])
|
|
|
|
sflag = False
|
|
dflag = False
|
|
if self.direction == 'egress':
|
|
ruleId = self.rule['id']
|
|
sourceIpsetName = 'sourceCidrIpset-%d' % ruleId
|
|
destIpsetName = 'destCidrIpset-%d' % ruleId
|
|
|
|
# Create source cidr ipset
|
|
srcIpset = 'ipset create '+sourceIpsetName + ' hash:net '
|
|
dstIpset = 'ipset create '+destIpsetName + ' hash:net '
|
|
|
|
CsHelper.execute(srcIpset)
|
|
CsHelper.execute(dstIpset)
|
|
for cidr in self.rule['cidr']:
|
|
ipsetAddCmd = 'ipset add ' + sourceIpsetName + ' ' + cidr
|
|
CsHelper.execute(ipsetAddCmd)
|
|
sflag = True
|
|
|
|
logging.debug("egress rule ####==> %s", self.rule)
|
|
for cidr in self.rule['dcidr']:
|
|
if cidr == CIDR_ALL:
|
|
continue
|
|
ipsetAddCmd = 'ipset add ' + destIpsetName + ' ' + cidr
|
|
CsHelper.execute(ipsetAddCmd)
|
|
dflag = True
|
|
|
|
self.fw.append(["filter", "", " -A FW_OUTBOUND -j FW_EGRESS_RULES"])
|
|
|
|
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"
|
|
|
|
egressIpsetStr = ''
|
|
if sflag and dflag:
|
|
egressIpsetStr = " -m set --match-set %s src " % sourceIpsetName + \
|
|
" -m set --match-set %s dst " % destIpsetName
|
|
elif sflag:
|
|
egressIpsetStr = " -m set --match-set %s src " % sourceIpsetName
|
|
elif dflag:
|
|
egressIpsetStr = " -m set --match-set %s dst " % destIpsetName
|
|
|
|
if rule['protocol'] == "icmp":
|
|
fwr += egressIpsetStr + " -p %s " % rule['protocol'] + " -m %s " % rule['protocol'] + \
|
|
" --icmp-type %s" % icmp_type
|
|
elif rule['protocol'] != "all":
|
|
fwr += egressIpsetStr + " -p %s " % rule['protocol'] + " -m %s " % rule['protocol'] + \
|
|
" %s" % rnge
|
|
elif rule['protocol'] == "all":
|
|
fwr += egressIpsetStr
|
|
|
|
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
|
|
for i in rule_list:
|
|
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, self.count, rstr])
|
|
|
|
def flushAllowAllEgressRules(self):
|
|
logging.debug("Flush allow 'all' egress firewall rule")
|
|
# Ensure that FW_EGRESS_RULES chain exists
|
|
CsHelper.execute("iptables-save | grep '^:FW_EGRESS_RULES' || iptables -t filter -N FW_EGRESS_RULES")
|
|
CsHelper.execute("iptables-save | grep '^-A FW_EGRESS_RULES -j ACCEPT$' | sed 's/^-A/iptables -t filter -D/g' | bash")
|
|
CsHelper.execute("iptables -F FW_EGRESS_RULES")
|
|
CsHelper.execute("ipset -L | grep Name: | awk {'print $2'} | ipset flush")
|
|
CsHelper.execute("ipset -L | grep Name: | awk {'print $2'} | ipset destroy")
|
|
|
|
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:
|
|
# need to pad data if it is not valid base 64
|
|
if len(data) % 4 != 0:
|
|
data += (4 - (len(data) % 4)) * "="
|
|
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 as e:
|
|
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 down vpn-%s" % ip)
|
|
CsHelper.execute("ipsec down 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 reload")
|
|
|
|
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 -t nat -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'].replace(' ', '')
|
|
vpnconffile = "%s/ipsec.vpn-%s.conf" % (self.VPNCONFDIR, rightpeer)
|
|
vpnsecretsfile = "%s/ipsec.vpn-%s.secrets" % (self.VPNCONFDIR, rightpeer)
|
|
ikepolicy = obj['ike_policy'].replace(';', '-')
|
|
esppolicy = obj['esp_policy'].replace(';', '-')
|
|
|
|
if rightpeer in self.confips:
|
|
self.confips.remove(rightpeer)
|
|
file = CsFile(vpnconffile)
|
|
file.add("#conn for vpn-%s" % rightpeer, 0)
|
|
file.search("conn ", "conn vpn-%s" % rightpeer)
|
|
file.addeq(" left=%s" % leftpeer)
|
|
file.addeq(" leftsubnet=%s" % obj['local_guest_cidr'])
|
|
file.addeq(" right=%s" % rightpeer)
|
|
file.addeq(" rightsubnet=%s" % peerlist)
|
|
file.addeq(" type=tunnel")
|
|
file.addeq(" authby=secret")
|
|
file.addeq(" keyexchange=ike")
|
|
file.addeq(" ike=%s" % ikepolicy)
|
|
file.addeq(" ikelifetime=%s" % self.convert_sec_to_h(obj['ike_lifetime']))
|
|
file.addeq(" esp=%s" % esppolicy)
|
|
file.addeq(" lifetime=%s" % self.convert_sec_to_h(obj['esp_lifetime']))
|
|
file.addeq(" keyingtries=2")
|
|
file.addeq(" auto=route")
|
|
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 rereadsecrets")
|
|
|
|
# This will load the new config
|
|
CsHelper.execute("ipsec reload")
|
|
os.chmod(vpnsecretsfile, 0400)
|
|
|
|
for i in xrange(3):
|
|
result = CsHelper.execute('ipsec status vpn-%s | grep "%s"' % (rightpeer, peerlist.split(",", 1)[0]))
|
|
if len(result) > 0:
|
|
break
|
|
time.sleep(1)
|
|
|
|
# With 'auto=route', connections are established on an attempt to
|
|
# communicate over the S2S VPN. This uses ping to initialize the connection.
|
|
CsHelper.execute("timeout 5 ping -c 3 %s" % (peerlist.split("/", 1)[0].replace(".0", ".1")))
|
|
|
|
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']
|
|
|
|
userAddEntry = "%s * %s *" % (user, password)
|
|
logging.debug("Adding vpn user '%s'" % user)
|
|
|
|
file = CsFile(self.PPP_CHAP)
|
|
userfound = file.searchString(userAddEntry, '#')
|
|
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("killing the PPPD process for the user '%s'" % user)
|
|
|
|
fileContents = CsHelper.execute("tdbdump /var/run/pppd2.tdb")
|
|
for line in fileContents:
|
|
if user in line:
|
|
contentlist = line.split(';')
|
|
for str in contentlist:
|
|
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)
|
|
|
|
CsHelper.start_if_stopped("ipsec")
|
|
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 update")
|
|
CsHelper.execute("systemctl start xl2tpd")
|
|
CsHelper.execute("ipsec rereadsecrets")
|
|
else:
|
|
logging.debug("Disabling remote access vpn .....")
|
|
CsHelper.execute("ipsec down L2TP-PSK")
|
|
CsHelper.execute("systemctl stop xl2tpd")
|
|
|
|
def configure_l2tpIpsec(self, left, obj):
|
|
l2tpconffile = "%s/l2tp.conf" % (self.VPNCONFDIR)
|
|
vpnsecretfilte = "%s/ipsec.any.secrets" % (self.VPNCONFDIR)
|
|
xl2tpdconffile = "/etc/xl2tpd/xl2tpd.conf"
|
|
xl2tpoptionsfile = "/etc/ppp/options.xl2tpd"
|
|
|
|
localip = obj['local_ip']
|
|
localcidr = obj['local_cidr']
|
|
publicIface = obj['public_interface']
|
|
iprange = obj['ip_range']
|
|
psk = obj['preshared_key']
|
|
|
|
# Left
|
|
l2tpfile = CsFile(l2tpconffile)
|
|
l2tpfile.addeq(" left=%s" % left)
|
|
l2tpfile.commit()
|
|
|
|
secret = CsFile(vpnsecretfilte)
|
|
secret.empty()
|
|
secret.addeq("%s %%any : PSK \"%s\"" % (left, 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])
|
|
self.fw.append(["", "", "-A OUTPUT -p ah -j ACCEPT"])
|
|
self.fw.append(["", "", "-A OUTPUT -p esp -j ACCEPT"])
|
|
|
|
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 = "-j SNAT --to-source %s -A POSTROUTING -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 " % (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(["mangle", "",
|
|
"-I PREROUTING -s %s/32 -m state --state NEW -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff" %
|
|
rule["internal_ip"]])
|
|
self.fw.append(["mangle", "",
|
|
"-I PREROUTING -s %s/32 -m state --state NEW -j MARK --set-xmark %s/0xffffffff" %
|
|
(rule["internal_ip"], hex(int(device[len("eth"):])))])
|
|
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 snat
|
|
self.fw.append(["nat", "front", "-A POSTROUTING -s %s -d %s -j SNAT -o %s --to-source %s" %
|
|
(self.getNetworkByIp(rule['internal_ip']), rule["internal_ip"], self.getDeviceByIp(rule["internal_ip"]), self.getGuestIp())])
|
|
|
|
|
|
class IpTablesExecutor:
|
|
|
|
config = None
|
|
|
|
def __init__(self, config):
|
|
self.config = config
|
|
|
|
def process(self):
|
|
acls = CsAcl('networkacl', self.config)
|
|
acls.process()
|
|
|
|
acls = CsAcl('firewallrules', self.config)
|
|
acls.flushAllowAllEgressRules()
|
|
acls.process()
|
|
|
|
fwd = CsForwardingRules("forwardingrules", self.config)
|
|
fwd.process()
|
|
|
|
vpns = CsSite2SiteVpn("site2sitevpn", self.config)
|
|
vpns.process()
|
|
|
|
rvpn = CsRemoteAccessVpn("remoteaccessvpn", self.config)
|
|
rvpn.process()
|
|
|
|
lb = CsLoadBalancer("loadbalancer", self.config)
|
|
lb.process()
|
|
|
|
logging.debug("Configuring iptables rules")
|
|
nf = CsNetfilters()
|
|
nf.compare(self.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/rules.v4")
|
|
CsHelper.save_iptables("ip6tables-save", "/etc/iptables/rules.v6")
|
|
|
|
|
|
def main(argv):
|
|
# The file we are currently processing, if it is "cmd_line.json" everything will be processed.
|
|
process_file = argv[1]
|
|
|
|
if process_file is None:
|
|
logging.debug("No file was received, do not go on processing the other actions. Just leave for now.")
|
|
return
|
|
|
|
json_type = os.path.basename(process_file).split('.json')[0]
|
|
|
|
# The "GLOBAL" Configuration object
|
|
config = CsConfig()
|
|
|
|
logging.basicConfig(filename=config.get_logger(),
|
|
level=config.get_level(),
|
|
format=config.get_format())
|
|
|
|
# Load stored ip addresses from disk to CsConfig()
|
|
config.set_address()
|
|
|
|
logging.debug("Configuring ip addresses")
|
|
config.address().compare()
|
|
config.address().process()
|
|
|
|
databag_map = OrderedDict([("guest_network", {"process_iptables": True, "executor": []}),
|
|
("vm_password", {"process_iptables": False, "executor": [CsPassword("vmpassword", config)]}),
|
|
("vm_metadata", {"process_iptables": False, "executor": [CsVmMetadata('vmdata', config)]}),
|
|
("network_acl", {"process_iptables": True, "executor": []}),
|
|
("firewall_rules", {"process_iptables": True, "executor": []}),
|
|
("forwarding_rules", {"process_iptables": True, "executor": []}),
|
|
("staticnat_rules", {"process_iptables": True, "executor": []}),
|
|
("site_2_site_vpn", {"process_iptables": True, "executor": []}),
|
|
("remote_access_vpn", {"process_iptables": True, "executor": []}),
|
|
("vpn_user_list", {"process_iptables": False, "executor": [CsVpnUser("vpnuserlist", config)]}),
|
|
("vm_dhcp_entry", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}),
|
|
("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}),
|
|
("load_balancer", {"process_iptables": True, "executor": []}),
|
|
("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}),
|
|
("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]})
|
|
])
|
|
|
|
def execDatabag(key, db):
|
|
if key not in db.keys() or 'executor' not in db[key]:
|
|
logging.warn("Unable to find config or executor(s) for the databag type %s" % key)
|
|
return
|
|
for executor in db[key]['executor']:
|
|
logging.debug("Processing for databag type: %s" % key)
|
|
executor.process()
|
|
|
|
def execIptables(config):
|
|
logging.debug("Processing iptables rules")
|
|
iptables_executor = IpTablesExecutor(config)
|
|
iptables_executor.process()
|
|
|
|
if json_type == "cmd_line":
|
|
logging.debug("cmd_line.json changed. All other files will be processed as well.")
|
|
for key in databag_map.keys():
|
|
execDatabag(key, databag_map)
|
|
execIptables(config)
|
|
elif json_type in databag_map.keys():
|
|
execDatabag(json_type, databag_map)
|
|
if databag_map[json_type]['process_iptables']:
|
|
execIptables(config)
|
|
else:
|
|
logging.warn("Unable to find and process databag for file: %s, for json type=%s" % (process_file, json_type))
|
|
|
|
red = CsRedundant(config)
|
|
red.set()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv)
|