diff options
Diffstat (limited to 'scripts/vyatta-ipsec-dhcp.py')
-rwxr-xr-x | scripts/vyatta-ipsec-dhcp.py | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/scripts/vyatta-ipsec-dhcp.py b/scripts/vyatta-ipsec-dhcp.py new file mode 100755 index 0000000..5aaa86e --- /dev/null +++ b/scripts/vyatta-ipsec-dhcp.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import re +import sys +import subprocess +import argparse +import syslog +import time +import vici + +import vyos.config + +config_file = "/etc/ipsec.conf"; +secrets_file = "/etc/ipsec.secrets"; + + +def parse_cli_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--interface", type=str) + parser.add_argument("--new_ip", type=str) + parser.add_argument("--old_ip", type=str) + parser.add_argument("--reason", type=str) + args = parser.parse_args() + return args + + +def ipsec_conf_r(): + header = '' + footer = '' + finheader = 0 + connlist = list() + conndict = dict() + curconn = '' + + with open(config_file) as f: + for line in f: + if re.search('^\s*$', line) and finheader: + continue + if re.search('\#conn.*', line): + curconn = '' + continue + if re.search('(peer-.*-tunnel.*)', line): + finheader = 1 + connid = re.search(r'(peer-.*-tunnel.*)', line).group(1) + curconn = connid + if connid not in connlist: + conndict[connid] = dict() + conndict[connid]['_dhcp_iface'] = None + conndict[connid]['_lip'] = None + conndict[connid]['_lines'] = list() + elif re.search('dhcp-interface=(.*)', line) and curconn != '': + conndict[connid]['_dhcp_iface'] = re.search(r'dhcp-interface=(.*)', line).group(1) + elif re.search('left=(.*)', line) and curconn != '': + conndict[connid]['_lip'] = re.search(r'left=(.*)', line).group(1) + elif not finheader: + header = header + line + elif curconn != '': + conndict[connid]['_lines'].append(line) + elif curconn == '': + footer = footer + line; + connlist.append(conndict) + return connlist, header, footer + + +def ipsec_conf_w(connlist, header, footer, interface, new_ip): + try: + with open(config_file, 'w') as f: + f.write('{0}\n'.format(header)) + for connid in connlist: + connname = next(iter(connid)) + f.write('conn {0}\n'.format(connname)) + if connid[connname]['_dhcp_iface']: + if connid[connname]['_dhcp_iface'] == interface: + if not new_ip: + new_ip = '' + connid[connname]['_lip'] = new_ip + f.write('\t#dhcp-interface={0}\n'.format(connid[connname]['_dhcp_iface'])) + f.write('\tleft={0}\n'.format(connid[connname]['_lip'])) + for line in connid[connname]['_lines']: + f.write('{0}'.format(line)) + f.write('#conn {0}\n\n'.format(connname)) + f.write('{0}\n'.format(footer)) + except EnvironmentError as e: + sys.exit('Can\'t open {0}: {1}'.format(config_file, e)) + + +def ipsec_sec_r(): + lines = [] + + with open(secrets_file) as f: + lines = [line for line in f] + return lines + + +def ipsec_sec_w(lines, interface, new_ip): + try: + with open(secrets_file, 'w') as f: + for line in lines: + if re.search('(.*)\#dhcp-interface=(.*)\#', line) and \ + re.search('(.*)\#dhcp-interface=(.*)\#', line).group(2) == interface: + secretline = re.search('(.*)\#dhcp-interface=(.*)\#', line).group(1) + if not new_ip: + new_ip = "#" + secline = re.search('(.*?) (.*?) : PSK (.*?) #dhcp', line) + line = '{0} {1} : PSK {2} #dhcp-interface={3}#\n'.format(new_ip, + secline.group(2), secline.group(3), interface) + f.write('{0}'.format(line)) + except EnvironmentError as e: + sys.exit('Can\'t open {0}: {1}'.format(config_file, e)) + + +def conn_list(): + v = vici.Session() + config = vyos.config.Config() + config_conns = config.list_effective_nodes("vpn ipsec site-to-site peer") + connup = [] + + + v = vici.Session() + for conn in v.list_sas(): + for key in conn: + for c_conn in config_conns: + if c_conn in key: + if config.return_effective_value("vpn ipsec site-to-site peer {0} dhcp-interface".format(c_conn)): + connup.append(key) + + return connup + + +def run(*popenargs, input=None, check=False, **kwargs): + if input is not None: + if 'stdin' in kwargs: + raise ValueError('stdin and input arguments may not both be used.') + kwargs['stdin'] = subprocess.PIPE + + process = subprocess.Popen(*popenargs, **kwargs) + try: + stdout, stderr = process.communicate(input) + except: + process.kill() + process.wait() + raise + retcode = process.poll() + if check and retcode: + raise subprocess.CalledProcessError( + retcode, process.args, output=stdout, stderr=stderr) + return retcode, stdout, stderr + + +def term_conn(active_conn): + v = vici.Session() + for conn in active_conn: + try: + list(v.terminate({"ike": conn, "force": "true"})) + except: + pass + + +def reload_conn(): + run(["/usr/sbin/ipsec", "rereadall"], stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + run(["/usr/sbin/ipsec", "update"], stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + + +def init_conn(active_conn, updated_conn): + v = vici.Session() + for conn in active_conn: + if conn not in updated_conn: + list(v.initiate({"child": conn, "timeout": "10000"})) + + +def main(): + args = parse_cli_args() + syslog.openlog('ipsec-dhclient-hook') + + syslog.syslog(syslog.LOG_NOTICE, 'Receive DHCP address updated to {0} from {1}' + ', reason: {2}.'.format(args.new_ip, args.old_ip, args.reason)) + + if args.old_ip == args.new_ip and args.reason != 'BOUND' or args.reason == 'REBOOT' or args.reason == 'EXPIRE': + syslog.syslog(syslog.LOG_NOTICE, 'No ipsec update needed.') + sys.exit(0) + + syslog.syslog(syslog.LOG_NOTICE, 'DHCP address updated to {0} from {1}: ' + ' Updating ipsec configuration, reason: {2}.'.format(args.new_ip, args.old_ip, args.reason)) + + connlist, header, footer = ipsec_conf_r() + ipsec_conf_w(connlist, header, footer, args.interface, args.new_ip) + + lines = ipsec_sec_r() + ipsec_sec_w(lines, args.interface, args.new_ip) + + if args.new_ip: + active_conn = conn_list() + term_conn(active_conn) + reload_conn() + time.sleep(5) + updated_conn = conn_list() + init_conn(active_conn, updated_conn) + + +if __name__ == '__main__': + main() |