diff options
Diffstat (limited to 'src/conf_mode/ipsec-settings.py')
-rwxr-xr-x | src/conf_mode/ipsec-settings.py | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py new file mode 100755 index 000000000..015d1a480 --- /dev/null +++ b/src/conf_mode/ipsec-settings.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 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 re +import os + +from time import sleep +from sys import exit + +from vyos.config import Config +from vyos import ConfigError +from vyos.util import call +from vyos.template import render + +from vyos import airbag +airbag.enable() + +ra_conn_name = "remote-access" +charon_conf_file = "/etc/strongswan.d/charon.conf" +ipsec_secrets_file = "/etc/ipsec.secrets" +ipsec_ra_conn_dir = "/etc/ipsec.d/tunnels/" +ipsec_ra_conn_file = ipsec_ra_conn_dir + ra_conn_name +ipsec_conf_file = "/etc/ipsec.conf" +ca_cert_path = "/etc/ipsec.d/cacerts" +server_cert_path = "/etc/ipsec.d/certs" +server_key_path = "/etc/ipsec.d/private" +delim_ipsec_l2tp_begin = "### VyOS L2TP VPN Begin ###" +delim_ipsec_l2tp_end = "### VyOS L2TP VPN End ###" +charon_pidfile = "/var/run/charon.pid" + +def get_config(): + config = Config() + data = {"install_routes": "yes"} + + if config.exists("vpn ipsec options disable-route-autoinstall"): + data["install_routes"] = "no" + + if config.exists("vpn ipsec ipsec-interfaces interface"): + data["ipsec_interfaces"] = config.return_values("vpn ipsec ipsec-interfaces interface") + + # Init config variables + data["delim_ipsec_l2tp_begin"] = delim_ipsec_l2tp_begin + data["delim_ipsec_l2tp_end"] = delim_ipsec_l2tp_end + data["ipsec_ra_conn_file"] = ipsec_ra_conn_file + data["ra_conn_name"] = ra_conn_name + # Get l2tp ipsec settings + data["ipsec_l2tp"] = False + conf_ipsec_command = "vpn l2tp remote-access ipsec-settings " #last space is useful + if config.exists(conf_ipsec_command): + data["ipsec_l2tp"] = True + + # Authentication params + if config.exists(conf_ipsec_command + "authentication mode"): + data["ipsec_l2tp_auth_mode"] = config.return_value(conf_ipsec_command + "authentication mode") + if config.exists(conf_ipsec_command + "authentication pre-shared-secret"): + data["ipsec_l2tp_secret"] = config.return_value(conf_ipsec_command + "authentication pre-shared-secret") + + # mode x509 + if config.exists(conf_ipsec_command + "authentication x509 ca-cert-file"): + data["ipsec_l2tp_x509_ca_cert_file"] = config.return_value(conf_ipsec_command + "authentication x509 ca-cert-file") + if config.exists(conf_ipsec_command + "authentication x509 crl-file"): + data["ipsec_l2tp_x509_crl_file"] = config.return_value(conf_ipsec_command + "authentication x509 crl-file") + if config.exists(conf_ipsec_command + "authentication x509 server-cert-file"): + data["ipsec_l2tp_x509_server_cert_file"] = config.return_value(conf_ipsec_command + "authentication x509 server-cert-file") + data["server_cert_file_copied"] = server_cert_path+"/"+re.search('\w+(?:\.\w+)*$', config.return_value(conf_ipsec_command + "authentication x509 server-cert-file")).group(0) + if config.exists(conf_ipsec_command + "authentication x509 server-key-file"): + data["ipsec_l2tp_x509_server_key_file"] = config.return_value(conf_ipsec_command + "authentication x509 server-key-file") + data["server_key_file_copied"] = server_key_path+"/"+re.search('\w+(?:\.\w+)*$', config.return_value(conf_ipsec_command + "authentication x509 server-key-file")).group(0) + if config.exists(conf_ipsec_command + "authentication x509 server-key-password"): + data["ipsec_l2tp_x509_server_key_password"] = config.return_value(conf_ipsec_command + "authentication x509 server-key-password") + + # Common l2tp ipsec params + if config.exists(conf_ipsec_command + "ike-lifetime"): + data["ipsec_l2tp_ike_lifetime"] = config.return_value(conf_ipsec_command + "ike-lifetime") + else: + data["ipsec_l2tp_ike_lifetime"] = "3600" + + if config.exists(conf_ipsec_command + "lifetime"): + data["ipsec_l2tp_lifetime"] = config.return_value(conf_ipsec_command + "lifetime") + else: + data["ipsec_l2tp_lifetime"] = "3600" + + if config.exists("vpn l2tp remote-access outside-address"): + data['outside_addr'] = config.return_value('vpn l2tp remote-access outside-address') + + return data + +def write_ipsec_secrets(c): + if c.get("ipsec_l2tp_auth_mode") == "pre-shared-secret": + secret_txt = "{0}\n{1} %any : PSK \"{2}\"\n{3}\n".format(delim_ipsec_l2tp_begin, c['outside_addr'], c['ipsec_l2tp_secret'], delim_ipsec_l2tp_end) + elif c.get("ipsec_l2tp_auth_mode") == "x509": + secret_txt = "{0}\n: RSA {1}\n{2}\n".format(delim_ipsec_l2tp_begin, c['server_key_file_copied'], delim_ipsec_l2tp_end) + + old_umask = os.umask(0o077) + with open(ipsec_secrets_file, 'a+') as f: + f.write(secret_txt) + os.umask(old_umask) + +def write_ipsec_conf(c): + ipsec_confg_txt = "{0}\ninclude {1}\n{2}\n".format(delim_ipsec_l2tp_begin, ipsec_ra_conn_file, delim_ipsec_l2tp_end) + + old_umask = os.umask(0o077) + with open(ipsec_conf_file, 'a+') as f: + f.write(ipsec_confg_txt) + os.umask(old_umask) + +### Remove config from file by delimiter +def remove_confs(delim_begin, delim_end, conf_file): + call("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file) + + +### Checking certificate storage and notice if certificate not in /config directory +def check_cert_file_store(cert_name, file_path, dts_path): + if not re.search('^\/config\/.+', file_path): + print("Warning: \"" + file_path + "\" lies outside of /config/auth directory. It will not get preserved during image upgrade.") + #Checking file existence + if not os.path.isfile(file_path): + raise ConfigError("L2TP VPN configuration error: Invalid "+cert_name+" \""+file_path+"\"") + else: + ### Cpy file to /etc/ipsec.d/certs/ /etc/ipsec.d/cacerts/ + # todo make check + ret = call('cp -f '+file_path+' '+dts_path) + if ret: + raise ConfigError("L2TP VPN configuration error: Cannot copy "+file_path) + +def verify(data): + # l2tp ipsec check + if data["ipsec_l2tp"]: + # Checking dependecies for "authentication mode pre-shared-secret" + if data.get("ipsec_l2tp_auth_mode") == "pre-shared-secret": + if not data.get("ipsec_l2tp_secret"): + raise ConfigError("pre-shared-secret required") + if not data.get("outside_addr"): + raise ConfigError("outside-address not defined") + + # Checking dependecies for "authentication mode x509" + if data.get("ipsec_l2tp_auth_mode") == "x509": + if not data.get("ipsec_l2tp_x509_server_key_file"): + raise ConfigError("L2TP VPN configuration error: \"server-key-file\" not defined.") + else: + check_cert_file_store("server-key-file", data['ipsec_l2tp_x509_server_key_file'], server_key_path) + + if not data.get("ipsec_l2tp_x509_server_cert_file"): + raise ConfigError("L2TP VPN configuration error: \"server-cert-file\" not defined.") + else: + check_cert_file_store("server-cert-file", data['ipsec_l2tp_x509_server_cert_file'], server_cert_path) + + if not data.get("ipsec_l2tp_x509_ca_cert_file"): + raise ConfigError("L2TP VPN configuration error: \"ca-cert-file\" must be defined for X.509") + else: + check_cert_file_store("ca-cert-file", data['ipsec_l2tp_x509_ca_cert_file'], ca_cert_path) + + if not data.get('ipsec_interfaces'): + raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.") + +def generate(data): + render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True) + + if data["ipsec_l2tp"]: + remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_file) + # old_umask = os.umask(0o077) + # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data, trim_blocks=True) + # os.umask(old_umask) + ## Use this method while IPSec CLI handler won't be overwritten to python + write_ipsec_secrets(data) + + old_umask = os.umask(0o077) + + # Create tunnels directory if does not exist + if not os.path.exists(ipsec_ra_conn_dir): + os.makedirs(ipsec_ra_conn_dir) + + render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data, trim_blocks=True) + os.umask(old_umask) + + remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_file) + # old_umask = os.umask(0o077) + # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data, trim_blocks=True) + # os.umask(old_umask) + ## Use this method while IPSec CLI handler won't be overwritten to python + write_ipsec_conf(data) + + else: + if os.path.exists(ipsec_ra_conn_file): + remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_ra_conn_file) + remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_file) + remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_file) + +def restart_ipsec(): + call('ipsec restart >&/dev/null') + # counter for apply swanctl config + counter = 10 + while counter <= 10: + if os.path.exists(charon_pidfile): + call('swanctl -q >&/dev/null') + break + counter -=1 + sleep(1) + if counter == 0: + raise ConfigError('VPN configuration error: IPSec is not running.') + +def apply(data): + # Restart IPSec daemon + restart_ipsec() + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |