summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-07-17 07:24:32 +0200
committerGitHub <noreply@github.com>2021-07-17 07:24:32 +0200
commitb04c4e21a07126d40702123e1122cd200883ccda (patch)
treeff58714ca75a7439e7fdfa3b9cb751294673c0fe /src
parenta2ff17e46ec030168de044ac06ffd5023a3d6a22 (diff)
parent3af38a4d673c37ed46d7d8d43ad03a94799ad09d (diff)
downloadvyos-1x-b04c4e21a07126d40702123e1122cd200883ccda.tar.gz
vyos-1x-b04c4e21a07126d40702123e1122cd200883ccda.zip
Merge pull request #924 from sarthurdev/ipsec_l2tp_pki
pki: ipsec: l2tp: T2816: T3642: Move IPSec/L2TP code into vpn_ipsec.py and update to use PKI.
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py222
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py59
-rwxr-xr-xsrc/migration-scripts/l2tp/3-to-4169
3 files changed, 217 insertions, 233 deletions
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
deleted file mode 100755
index a9885f3d3..000000000
--- a/src/conf_mode/ipsec-settings.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/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 sys import exit
-
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call, wait_for_file_write_complete
-from vyos.template import render
-
-from vyos import airbag
-airbag.enable()
-
-ra_conn_name = "remote-access"
-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=None):
- if config:
- config = config
- else:
- config = Config()
-
- data = {}
- 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 'ipsec_l2tp' in data:
- # 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):
- if 'ipsec_l2tp' in data:
- 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)
- # 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)
- 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)
- # 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():
- try:
- wait_for_file_write_complete(charon_pidfile,
- pre_hook=(lambda: call('ipsec restart >&/dev/null')),
- timeout=10)
-
- # Force configuration load
- call('swanctl -q >&/dev/null')
-
- except OSError:
- raise ConfigError('VPN configuration error: IPSec process did not start.')
-
-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)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 3fab8e868..645108a8f 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -73,6 +73,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['vpn', 'ipsec']
+ l2tp_base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings']
if not conf.exists(base):
return None
@@ -110,13 +111,21 @@ def get_config(config=None):
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces',
'interface'])
- ipsec['l2tp_exists'] = conf.exists(['vpn', 'l2tp', 'remote-access',
- 'ipsec-settings'])
ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel'])
ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
+ ipsec['l2tp'] = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ if ipsec['l2tp']:
+ l2tp_defaults = defaults(l2tp_base)
+ ipsec['l2tp'] = dict_merge(l2tp_defaults, ipsec['l2tp'])
+ ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address'])
+ ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024'
+ ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1'
+
return ipsec
def get_dhcp_address(iface):
@@ -173,6 +182,39 @@ def verify(ipsec):
for ifname in interfaces:
verify_interface_exists(ifname)
+ if ipsec['l2tp']:
+ if 'esp_group' in ipsec['l2tp']:
+ if 'esp_group' not in ipsec or ipsec['l2tp']['esp_group'] not in ipsec['esp_group']:
+ raise ConfigError(f"Invalid esp-group on L2TP remote-access config")
+
+ if 'ike_group' in ipsec['l2tp']:
+ if 'ike_group' not in ipsec or ipsec['l2tp']['ike_group'] not in ipsec['ike_group']:
+ raise ConfigError(f"Invalid ike-group on L2TP remote-access config")
+
+ if 'authentication' not in ipsec['l2tp']:
+ raise ConfigError(f'Missing authentication settings on L2TP remote-access config')
+
+ if 'mode' not in ipsec['l2tp']['authentication']:
+ raise ConfigError(f'Missing authentication mode on L2TP remote-access config')
+
+ if not ipsec['l2tp_outside_address']:
+ raise ConfigError(f'Missing outside-address on L2TP remote-access config')
+
+ if ipsec['l2tp']['authentication']['mode'] == 'pre-shared-secret':
+ if 'pre_shared_secret' not in ipsec['l2tp']['authentication']:
+ raise ConfigError(f'Missing pre shared secret on L2TP remote-access config')
+
+ if ipsec['l2tp']['authentication']['mode'] == 'x509':
+ if 'x509' not in ipsec['l2tp']['authentication']:
+ raise ConfigError(f'Missing x509 settings on L2TP remote-access config')
+
+ x509 = ipsec['l2tp']['authentication']['x509']
+
+ if 'ca_certificate' not in x509 or 'certificate' not in x509:
+ raise ConfigError(f'Missing x509 certificates on L2TP remote-access config')
+
+ verify_pki_x509(ipsec['pki'], x509)
+
if 'profile' in ipsec:
for profile, profile_conf in ipsec['profile'].items():
if 'esp_group' in profile_conf:
@@ -389,6 +431,10 @@ def generate(ipsec):
if not os.path.exists(KEY_PATH):
os.mkdir(KEY_PATH, mode=0o700)
+ if ipsec['l2tp']:
+ if 'authentication' in ipsec['l2tp'] and 'x509' in ipsec['l2tp']['authentication']:
+ generate_pki_files_x509(ipsec['pki'], ipsec['l2tp']['authentication']['x509'])
+
if 'remote_access' in ipsec:
for rw, rw_conf in ipsec['remote_access'].items():
if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']:
@@ -439,14 +485,6 @@ def generate(ipsec):
render(interface_conf, 'ipsec/interfaces_use.conf.tmpl', ipsec)
render(swanctl_conf, 'ipsec/swanctl.conf.tmpl', ipsec)
-def resync_l2tp(ipsec):
- if ipsec and not ipsec['l2tp_exists']:
- return
-
- tmp = run('/usr/libexec/vyos/conf_mode/ipsec-settings.py')
- if tmp > 0:
- print('ERROR: failed to reapply L2TP IPSec settings!')
-
def resync_nhrp(ipsec):
if ipsec and not ipsec['nhrp_exists']:
return
@@ -480,7 +518,6 @@ def apply(ipsec):
if wait_for_vici_socket():
call('sudo swanctl -q')
- resync_l2tp(ipsec)
resync_nhrp(ipsec)
if __name__ == '__main__':
diff --git a/src/migration-scripts/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4
new file mode 100755
index 000000000..18eabadec
--- /dev/null
+++ b/src/migration-scripts/l2tp/3-to-4
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+# - remove primary/secondary identifier from nameserver
+# - TODO: remove radius server req-limit
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+from vyos.pki import load_certificate
+from vyos.pki import load_crl
+from vyos.pki import load_private_key
+from vyos.pki import encode_certificate
+from vyos.pki import encode_private_key
+from vyos.util import run
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings']
+pki_base = ['pki']
+
+if not config.exists(base):
+ exit(0)
+
+AUTH_DIR = '/config/auth'
+
+def wrapped_pem_to_config_value(pem):
+ return "".join(pem.strip().split("\n")[1:-1])
+
+if not config.exists(base + ['authentication', 'x509']):
+ exit(0)
+
+x509_base = base + ['authentication', 'x509']
+pki_name = 'l2tp_remote_access'
+
+if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+if not config.exists(pki_base + ['certificate']):
+ config.set(pki_base + ['certificate'])
+ config.set_tag(pki_base + ['certificate'])
+
+if config.exists(x509_base + ['ca-cert-file']):
+ cert_file = config.return_value(x509_base + ['ca-cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+ cert = None
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if cert:
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['ca-certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate CA certificate on l2tp remote-access config')
+
+ config.delete(x509_base + ['ca-cert-file'])
+
+if config.exists(x509_base + ['crl-file']):
+ crl_file = config.return_value(x509_base + ['crl-file'])
+ crl_path = os.path.join(AUTH_DIR, crl_file)
+ crl = None
+
+ if os.path.isfile(crl_path):
+ if not os.access(crl_path, os.R_OK):
+ run(f'sudo chmod 644 {crl_path}')
+
+ with open(crl_path, 'r') as f:
+ crl_data = f.read()
+ crl = load_certificate(crl_data, wrap_tags=False)
+
+ if crl:
+ crl_pem = encode_certificate(crl)
+ config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
+ else:
+ print(f'Failed to migrate CRL on l2tp remote-access config')
+
+ config.delete(x509_base + ['crl-file'])
+
+if config.exists(x509_base + ['server-cert-file']):
+ cert_file = config.return_value(x509_base + ['server-cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+ cert = None
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if cert:
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate certificate on l2tp remote-access config')
+
+ config.delete(x509_base + ['server-cert-file'])
+
+if config.exists(x509_base + ['server-key-file']):
+ key_file = config.return_value(x509_base + ['server-key-file'])
+ key_passphrase = None
+
+ if config.exists(x509_base + ['server-key-password']):
+ key_passphrase = config.return_value(x509_base + ['server-key-password'])
+
+ key_path = os.path.join(AUTH_DIR, key_file)
+ key = None
+
+ if os.path.isfile(key_path):
+ if not os.access(key_path, os.R_OK):
+ run(f'sudo chmod 644 {key_path}')
+
+ with open(key_path, 'r') as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False)
+
+ if key:
+ key_pem = encode_private_key(key, passphrase=key_passphrase)
+ config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
+
+ if key_passphrase:
+ config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected'])
+ config.set(x509_base + ['private-key-passphrase'], value=key_passphrase)
+ else:
+ print(f'Failed to migrate private key on l2tp remote-access config')
+
+ config.delete(x509_base + ['server-key-file'])
+ if config.exists(x509_base + ['server-key-password']):
+ config.delete(x509_base + ['server-key-password'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)