summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/ipsec/swanctl.conf.tmpl11
-rw-r--r--data/templates/ipsec/swanctl/peer.tmpl4
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/include/ipsec/authentication-rsa.xml.i30
-rw-r--r--interface-definitions/vpn_ipsec.xml.in6
-rw-r--r--interface-definitions/vpn_rsa-keys.xml.in47
-rw-r--r--op-mode-definitions/vpn-ipsec.xml.in50
-rw-r--r--smoketest/configs/pki-ipsec26
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py122
-rwxr-xr-xsrc/conf_mode/vpn_rsa-keys.py113
-rwxr-xr-xsrc/migration-scripts/ipsec/7-to-8125
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py94
13 files changed, 260 insertions, 370 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index d228ac8a3..a03360bdb 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -67,7 +67,6 @@
"tftp_server.py",
"vpn_l2tp.py",
"vpn_pptp.py",
-"vpn_rsa-keys.py",
"vpn_sstp.py",
"vrf.py",
"vrrp.py",
diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl
index 00251d44d..a6ab73cc2 100644
--- a/data/templates/ipsec/swanctl.conf.tmpl
+++ b/data/templates/ipsec/swanctl.conf.tmpl
@@ -48,7 +48,6 @@ secrets {
{% endfor %}
{% endif %}
{% if site_to_site is defined and site_to_site.peer is defined %}
-{% set ns = namespace(local_key_set=False) %}
{% for peer, peer_conf in site_to_site.peer.items() if peer not in dhcp_no_address and peer_conf.disable is not defined %}
{% set peer_name = peer.replace(".", "-").replace("@", "") %}
{% if peer_conf.authentication.mode == 'pre-shared-secret' %}
@@ -72,10 +71,12 @@ secrets {
secret = "{{ peer_conf.authentication.x509.passphrase }}"
{% endif %}
}
-{% elif peer_conf.authentication.mode == 'rsa' and not ns.local_key_set %}
-{% set ns.local_key_set = True %}
- rsa_local {
- file = {{ rsa_local_key }}
+{% elif peer_conf.authentication.mode == 'rsa' %}
+ rsa_{{ peer_name }}_local {
+ file = {{ peer_conf.authentication.rsa.local_key }}.pem
+{% if peer_conf.authentication.rsa.passphrase is defined %}
+ secret = "{{ peer_conf.authentication.rsa.passphrase }}"
+{% endif %}
}
{% endif %}
{% endfor %}
diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl
index 4ace06701..8e46e8892 100644
--- a/data/templates/ipsec/swanctl/peer.tmpl
+++ b/data/templates/ipsec/swanctl/peer.tmpl
@@ -38,7 +38,7 @@
{% if peer_conf.authentication.mode == 'x509' %}
certs = {{ peer_conf.authentication.x509.certificate }}.pem
{% elif peer_conf.authentication.mode == 'rsa' %}
- pubkeys = localhost.pub
+ pubkeys = {{ peer_conf.authentication.rsa.local_key }}.pem
{% endif %}
}
remote {
@@ -49,7 +49,7 @@
{% endif %}
auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }}
{% if peer_conf.authentication.mode == 'rsa' %}
- pubkeys = {{ peer_conf.authentication.rsa_key_name }}.pub
+ pubkeys = {{ peer_conf.authentication.rsa.remote_key }}.pem
{% endif %}
}
children {
diff --git a/debian/control b/debian/control
index 7ff64e5a3..9ac82cf03 100644
--- a/debian/control
+++ b/debian/control
@@ -111,7 +111,6 @@ Depends:
procps,
python3,
python3-certbot-nginx,
- python3-pycryptodome,
python3-cryptography,
python3-flask,
python3-hurry.filesize,
diff --git a/interface-definitions/include/ipsec/authentication-rsa.xml.i b/interface-definitions/include/ipsec/authentication-rsa.xml.i
new file mode 100644
index 000000000..0a364e838
--- /dev/null
+++ b/interface-definitions/include/ipsec/authentication-rsa.xml.i
@@ -0,0 +1,30 @@
+<!-- include start from ipsec/authentication-rsa.xml.i -->
+<node name="rsa">
+ <properties>
+ <help>RSA keys</help>
+ </properties>
+ <children>
+ <leafNode name="local-key">
+ <properties>
+ <help>Name of PKI key-pair with local private key</help>
+ <completionHelp>
+ <path>pki key-pair</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="passphrase">
+ <properties>
+ <help>Local private key passphrase</help>
+ </properties>
+ </leafNode>
+ <leafNode name="remote-key">
+ <properties>
+ <help>Name of PKI key-pair with remote public key</help>
+ <completionHelp>
+ <path>pki key-pair</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 4425ab02a..147f351f2 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -922,6 +922,7 @@
</properties>
<children>
#include <include/ipsec/authentication-id.xml.i>
+ #include <include/ipsec/authentication-rsa.xml.i>
#include <include/ipsec/authentication-x509.xml.i>
<leafNode name="mode">
<properties>
@@ -964,11 +965,6 @@
</valueHelp>
</properties>
</leafNode>
- <leafNode name="rsa-key-name">
- <properties>
- <help>RSA key name</help>
- </properties>
- </leafNode>
<leafNode name="use-x509-id">
<properties>
<help>Use certificate common name as ID</help>
diff --git a/interface-definitions/vpn_rsa-keys.xml.in b/interface-definitions/vpn_rsa-keys.xml.in
deleted file mode 100644
index 2d8e97f4f..000000000
--- a/interface-definitions/vpn_rsa-keys.xml.in
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="vpn">
- <children>
- <node name="rsa-keys" owner="${vyos_conf_scripts_dir}/vpn_rsa-keys.py">
- <properties>
- <help>RSA keys</help>
- <priority>900</priority>
- </properties>
- <children>
- <node name="local-key">
- <properties>
- <help>Local RSA key</help>
- </properties>
- <children>
- <leafNode name="file">
- <properties>
- <help>Local RSA key file location</help>
- <valueHelp>
- <format>txt</format>
- <description>File in /config/auth or /config/ipsec.d/rsa-keys</description>
- </valueHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <tagNode name="rsa-key-name">
- <properties>
- <help>Name of remote RSA key</help>
- </properties>
- <children>
- <leafNode name="rsa-key">
- <properties>
- <help>Remote RSA key</help>
- <valueHelp>
- <format>txt</format>
- <description>Remote RSA key</description>
- </valueHelp>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in
index fe0597eed..20f275e9b 100644
--- a/op-mode-definitions/vpn-ipsec.xml.in
+++ b/op-mode-definitions/vpn-ipsec.xml.in
@@ -1,49 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<interfaceDefinition>
- <node name="generate">
- <children>
- <node name="vpn">
- <properties>
- <help>VPN key generation utility</help>
- </properties>
- <children>
- <node name="rsa-key">
- <properties>
- <help>Generate local RSA key (default: bits=2192)</help>
- </properties>
- <children>
- <tagNode name="bits">
- <properties>
- <help>Generate local RSA key with specified number of bits</help>
- <completionHelp>
- <list>&lt;16-4096&gt;</list>
- </completionHelp>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="rsa-key" --bits="$5"</command>
- </tagNode>
- </children>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="rsa-key" --bits="2192"</command>
- </node>
- <node name="x509">
- <properties>
- <help>x509 key-pair generation tool</help>
- </properties>
- <children>
- <tagNode name="key-pair">
- <properties>
- <help>Generate x509 key-pair</help>
- <completionHelp>
- <list>&lt;common-name&gt;</list>
- </completionHelp>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="x509" --name="$5"</command>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
<node name="reset">
<children>
<node name="vpn">
@@ -139,12 +95,6 @@
<help>Show Internet Key Exchange (IKE) information</help>
</properties>
<children>
- <node name="rsa-keys">
- <properties>
- <help>Show VPN RSA keys</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="rsa-key-show"</command>
- </node>
<node name="sa">
<properties>
<help>Show all currently active IKE Security Associations (SA)</help>
diff --git a/smoketest/configs/pki-ipsec b/smoketest/configs/pki-ipsec
index 7708a3cdd..5025117f7 100644
--- a/smoketest/configs/pki-ipsec
+++ b/smoketest/configs/pki-ipsec
@@ -85,6 +85,32 @@ vpn {
}
}
}
+ peer 192.168.150.3 {
+ authentication {
+ mode rsa
+ pre-shared-secret MYSECRETKEY
+ rsa-key-name peer2
+ }
+ default-esp-group MyESPGroup
+ ike-group MyIKEGroup
+ local-address 192.168.150.1
+ tunnel 0 {
+ local {
+ prefix 172.20.0.0/24
+ }
+ remote {
+ prefix 172.22.0.0/24
+ }
+ }
+ }
+ }
+ }
+ rsa-keys {
+ local-key {
+ file /config/auth/ovpn_test_server.key
+ }
+ rsa-key-name peer2 {
+ rsa-key 0sAwEAAbudt5WQZSW2plbixjpgx4yVN/WMHdYRIZhyypJWO4ujQ/UQS9j3oTBgV2+RLtQ0YQ7eocwIfkvJVUnnZVMyZ4asQMOarQgbQ5nFGliCcDOMtNXRxHlMsvmjLx4o6FWbGukwgoxsT2x915n0XMn4XJNNSIEQotxj2GWFhEfBSPHyOM++kODk0lkbE7mLeHMMFq02vQhoczzEPWxjUUoY3jywhmHMfb4PdAKLFyt9x40znmPCYh+NSMQmpBXtD3gjGtX62bgrqKuP3BJU44x1gLlv8rJAJ4SY74YKnFUZ8m5GSbnVapwPOrp65lJZFKOGs2XXjAp5leoR+wmSYyqbDJM=
}
}
}
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 53a50fa1e..3fab8e868 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -26,6 +26,8 @@ from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
from vyos.configdict import dict_merge
from vyos.ifconfig import Interface
+from vyos.pki import encode_public_key
+from vyos.pki import load_private_key
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_public_key
@@ -57,6 +59,7 @@ default_install_routes = 'yes'
vici_socket = '/var/run/charon.vici'
CERT_PATH = f'{swanctl_dir}/x509/'
+PUBKEY_PATH = f'{swanctl_dir}/pubkey/'
KEY_PATH = f'{swanctl_dir}/private/'
CA_PATH = f'{swanctl_dir}/x509ca/'
CRL_PATH = f'{swanctl_dir}/x509crl/'
@@ -64,9 +67,6 @@ CRL_PATH = f'{swanctl_dir}/x509crl/'
DHCP_BASE = '/var/lib/dhcp/dhclient'
DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_waiting'
-LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
-X509_PATH = '/config/auth/'
-
def get_config(config=None):
if config:
conf = config
@@ -116,32 +116,9 @@ def get_config(config=None):
ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
- ipsec['rsa_keys'] = conf.get_config_dict(['vpn', 'rsa-keys'],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
return ipsec
-def get_rsa_local_key(ipsec):
- return dict_search_args(ipsec['rsa_keys'], 'local_key', 'file')
-
-def verify_rsa_local_key(ipsec):
- file = get_rsa_local_key(ipsec)
-
- if not file:
- return False
-
- for path in LOCAL_KEY_PATHS:
- full_path = os.path.join(path, file)
- if os.path.exists(full_path):
- return full_path
-
- return False
-
-def verify_rsa_key(ipsec, key_name):
- return dict_search_args(ipsec['rsa_keys'], 'rsa_key_name', key_name, 'rsa_key')
-
def get_dhcp_address(iface):
addresses = Interface(iface).get_addr()
if not addresses:
@@ -151,7 +128,7 @@ def get_dhcp_address(iface):
return ip_from_cidr(address)
return None
-def verify_pki(pki, x509_conf):
+def verify_pki_x509(pki, x509_conf):
if not pki or 'ca' not in pki or 'certificate' not in pki:
raise ConfigError(f'PKI is not configured')
@@ -169,6 +146,21 @@ def verify_pki(pki, x509_conf):
return True
+def verify_pki_rsa(pki, rsa_conf):
+ if not pki or 'key_pair' not in pki:
+ raise ConfigError(f'PKI is not configured')
+
+ local_key = rsa_conf['local_key']
+ remote_key = rsa_conf['remote_key']
+
+ if not dict_search_args(pki, 'key_pair', local_key, 'private', 'key'):
+ raise ConfigError(f'Missing private key on specified local-key "{local_key}"')
+
+ if not dict_search_args(pki, 'key_pair', remote_key, 'public', 'key'):
+ raise ConfigError(f'Missing public key on specified remote-key "{remote_key}"')
+
+ return True
+
def verify(ipsec):
if not ipsec:
return None
@@ -224,7 +216,7 @@ def verify(ipsec):
if 'ca_certificate' not in x509 or 'certificate' not in x509:
raise ConfigError(f"Missing x509 certificates on {name} remote-access config")
- verify_pki(ipsec['pki'], x509)
+ verify_pki_x509(ipsec['pki'], x509)
elif ra_conf['authentication']['server_mode'] == 'pre-shared-secret':
if 'pre_shared_secret' not in ra_conf['authentication']:
raise ConfigError(f"Missing pre-shared-key on {name} remote-access config")
@@ -255,17 +247,20 @@ def verify(ipsec):
if 'ca_certificate' not in x509 or 'certificate' not in x509:
raise ConfigError(f"Missing x509 certificates on site-to-site peer {peer}")
- verify_pki(ipsec['pki'], x509)
+ verify_pki_x509(ipsec['pki'], x509)
+ elif peer_conf['authentication']['mode'] == 'rsa':
+ if 'rsa' not in peer_conf['authentication']:
+ raise ConfigError(f"Missing RSA settings on site-to-site peer {peer}")
- if peer_conf['authentication']['mode'] == 'rsa':
- if not verify_rsa_local_key(ipsec):
- raise ConfigError(f"Invalid key on rsa-keys local-key")
+ rsa = peer_conf['authentication']['rsa']
- if 'rsa_key_name' not in peer_conf['authentication']:
- raise ConfigError(f"Missing rsa-key-name on site-to-site peer {peer}")
+ if 'local_key' not in rsa:
+ raise ConfigError(f"Missing RSA local-key on site-to-site peer {peer}")
- if not verify_rsa_key(ipsec, peer_conf['authentication']['rsa_key_name']):
- raise ConfigError(f"Invalid rsa-key-name on site-to-site peer {peer}")
+ if 'remote_key' not in rsa:
+ raise ConfigError(f"Missing RSA remote-key on site-to-site peer {peer}")
+
+ verify_pki_rsa(ipsec['pki'], rsa)
if 'local_address' not in peer_conf and 'dhcp_interface' not in peer_conf:
raise ConfigError(f"Missing local-address or dhcp-interface on site-to-site peer {peer}")
@@ -322,7 +317,7 @@ def verify(ipsec):
raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
def cleanup_pki_files():
- for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH]:
+ for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH, PUBKEY_PATH]:
if not os.path.exists(path):
continue
for file in os.listdir(path):
@@ -330,7 +325,7 @@ def cleanup_pki_files():
if os.path.isfile(file_path):
os.unlink(file_path)
-def generate_pki_files(pki, x509_conf):
+def generate_pki_files_x509(pki, x509_conf):
ca_cert_name = x509_conf['ca_certificate']
ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate')
ca_cert_crls = dict_search_args(pki, 'ca', ca_cert_name, 'crl') or []
@@ -352,9 +347,27 @@ def generate_pki_files(pki, x509_conf):
with open(os.path.join(CERT_PATH, f'{cert_name}.pem'), 'w') as f:
f.write(wrap_certificate(cert_data))
- with open(os.path.join(KEY_PATH, f'{cert_name}.pem'), 'w') as f:
+ with open(os.path.join(KEY_PATH, f'x509_{cert_name}.pem'), 'w') as f:
f.write(wrap_private_key(key_data, protected))
+def generate_pki_files_rsa(pki, rsa_conf):
+ local_key_name = rsa_conf['local_key']
+ local_key_data = dict_search_args(pki, 'key_pair', local_key_name, 'private', 'key')
+ protected = 'passphrase' in rsa_conf
+ remote_key_name = rsa_conf['remote_key']
+ remote_key_data = dict_search_args(pki, 'key_pair', remote_key_name, 'public', 'key')
+
+ local_key = load_private_key(local_key_data, rsa_conf['passphrase'] if protected else None)
+
+ with open(os.path.join(KEY_PATH, f'rsa_{local_key_name}.pem'), 'w') as f:
+ f.write(wrap_private_key(local_key_data, protected))
+
+ with open(os.path.join(PUBKEY_PATH, f'{local_key_name}.pem'), 'w') as f:
+ f.write(encode_public_key(local_key.public_key()))
+
+ with open(os.path.join(PUBKEY_PATH, f'{remote_key_name}.pem'), 'w') as f:
+ f.write(wrap_public_key(remote_key_data))
+
def generate(ipsec):
cleanup_pki_files()
@@ -369,28 +382,27 @@ def generate(ipsec):
with open(DHCP_HOOK_IFLIST, 'w') as f:
f.write(" ".join(ipsec['dhcp_no_address'].values()))
- data = ipsec
- data['rsa_local_key'] = verify_rsa_local_key(ipsec)
-
- for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH]:
+ for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH, PUBKEY_PATH]:
if not os.path.exists(path):
os.mkdir(path, mode=0o755)
if not os.path.exists(KEY_PATH):
os.mkdir(KEY_PATH, mode=0o700)
- if 'remote_access' in data:
+ 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']:
- generate_pki_files(ipsec['pki'], rw_conf['authentication']['x509'])
+ generate_pki_files_x509(ipsec['pki'], rw_conf['authentication']['x509'])
- if 'site_to_site' in data and 'peer' in data['site_to_site']:
+ if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
if peer in ipsec['dhcp_no_address']:
continue
if peer_conf['authentication']['mode'] == 'x509':
- generate_pki_files(ipsec['pki'], peer_conf['authentication']['x509'])
+ generate_pki_files_x509(ipsec['pki'], peer_conf['authentication']['x509'])
+ elif peer_conf['authentication']['mode'] == 'rsa':
+ generate_pki_files_rsa(ipsec['pki'], peer_conf['authentication']['rsa'])
local_ip = ''
if 'local_address' in peer_conf:
@@ -398,7 +410,7 @@ def generate(ipsec):
elif 'dhcp_interface' in peer_conf:
local_ip = get_dhcp_address(peer_conf['dhcp_interface'])
- data['site_to_site']['peer'][peer]['local_address'] = local_ip
+ ipsec['site_to_site']['peer'][peer]['local_address'] = local_ip
if 'tunnel' in peer_conf:
for tunnel, tunnel_conf in peer_conf['tunnel'].items():
@@ -417,15 +429,15 @@ def generate(ipsec):
if local_net.overlaps(remote_net):
passthrough.append(local_prefix)
- data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
+ ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
- render(ipsec_conf, 'ipsec/ipsec.conf.tmpl', data)
- render(ipsec_secrets, 'ipsec/ipsec.secrets.tmpl', data)
- render(charon_conf, 'ipsec/charon.tmpl', data)
- render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.tmpl', data)
- render(interface_conf, 'ipsec/interfaces_use.conf.tmpl', data)
- render(swanctl_conf, 'ipsec/swanctl.conf.tmpl', data)
+ render(ipsec_conf, 'ipsec/ipsec.conf.tmpl', ipsec)
+ render(ipsec_secrets, 'ipsec/ipsec.secrets.tmpl', ipsec)
+ render(charon_conf, 'ipsec/charon.tmpl', ipsec)
+ render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.tmpl', 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']:
diff --git a/src/conf_mode/vpn_rsa-keys.py b/src/conf_mode/vpn_rsa-keys.py
deleted file mode 100755
index 83de93088..000000000
--- a/src/conf_mode/vpn_rsa-keys.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/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/>.
-
-import base64
-import os
-import struct
-
-from sys import exit
-
-from vyos.config import Config
-from vyos.util import call
-from vyos import ConfigError
-from vyos import airbag
-from Cryptodome.PublicKey.RSA import construct
-
-airbag.enable()
-
-LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
-LOCAL_OUTPUT = '/etc/swanctl/pubkey/localhost.pub'
-LOCAL_KEY_OUTPUT = '/etc/swanctl/private/localhost.key'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['vpn', 'rsa-keys']
- if not conf.exists(base):
- return None
-
- return conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
-
-def verify(conf):
- if not conf:
- return
-
- if 'local_key' in conf and 'file' in conf['local_key']:
- local_key = conf['local_key']['file']
- if not local_key:
- raise ConfigError(f'Invalid local-key')
-
- if not get_local_key(local_key):
- raise ConfigError(f'File not found for local-key: {local_key}')
-
-def get_local_key(local_key):
- for path in LOCAL_KEY_PATHS:
- full_path = os.path.join(path, local_key)
- if os.path.exists(full_path):
- return full_path
- return False
-
-def generate(conf):
- if not conf:
- return
-
- if 'local_key' in conf and 'file' in conf['local_key']:
- local_key = conf['local_key']['file']
- local_key_path = get_local_key(local_key)
- call(f'sudo cp -f {local_key_path} {LOCAL_KEY_OUTPUT}')
- call(f'sudo /usr/bin/openssl rsa -in {local_key_path} -pubout -out {LOCAL_OUTPUT}')
-
- if 'rsa_key_name' in conf:
- for key_name, key_conf in conf['rsa_key_name'].items():
- if 'rsa_key' not in key_conf:
- continue
-
- remote_key = key_conf['rsa_key']
-
- if remote_key[:2] == "0s": # Vyatta format
- remote_key = migrate_from_vyatta_key(remote_key)
- else:
- remote_key = bytes('-----BEGIN PUBLIC KEY-----\n' + remote_key + '\n-----END PUBLIC KEY-----\n', 'utf-8')
-
- with open(f'/etc/swanctl/pubkey/{key_name}.pub', 'wb') as f:
- f.write(remote_key)
-
-def migrate_from_vyatta_key(data):
- data = base64.b64decode(data[2:])
- length = struct.unpack('B', data[:1])[0]
- e = int.from_bytes(data[1:1+length], 'big')
- n = int.from_bytes(data[1+length:], 'big')
- pubkey = construct((n, e))
- return pubkey.exportKey(format='PEM')
-
-def apply(conf):
- if not conf:
- return
-
- call('sudo /usr/sbin/ipsec rereadall')
- call('sudo /usr/sbin/ipsec reload')
-
-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/migration-scripts/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8
new file mode 100755
index 000000000..5d48b2875
--- /dev/null
+++ b/src/migration-scripts/ipsec/7-to-8
@@ -0,0 +1,125 @@
+#!/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/>.
+
+# Migrate rsa keys into PKI configuration
+
+import base64
+import os
+import struct
+
+from cryptography.hazmat.primitives.asymmetric import rsa
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.pki import load_public_key
+from vyos.pki import load_private_key
+from vyos.pki import encode_public_key
+from vyos.pki import encode_private_key
+
+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()
+
+pki_base = ['pki']
+ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer']
+rsa_keys_base = ['vpn', 'rsa-keys']
+
+config = ConfigTree(config_file)
+
+LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
+
+def migrate_from_vyatta_key(data):
+ data = base64.b64decode(data[2:])
+ length = struct.unpack('B', data[:1])[0]
+ e = int.from_bytes(data[1:1+length], 'big')
+ n = int.from_bytes(data[1+length:], 'big')
+ public_numbers = rsa.RSAPublicNumbers(e, n)
+ return public_numbers.public_key()
+
+def wrapped_pem_to_config_value(pem):
+ return "".join(pem.strip().split("\n")[1:-1])
+
+local_key_name = 'localhost'
+
+if config.exists(rsa_keys_base):
+ if not config.exists(pki_base + ['key-pair']):
+ config.set(pki_base + ['key-pair'])
+ config.set_tag(pki_base + ['key-pair'])
+
+ if config.exists(rsa_keys_base + ['local-key', 'file']):
+ local_file = config.return_value(rsa_keys_base + ['local-key', 'file'])
+ local_path = None
+ local_key = None
+
+ for path in LOCAL_KEY_PATHS:
+ full_path = os.path.join(path, local_file)
+ if os.path.exists(full_path):
+ local_path = full_path
+ break
+
+ if local_path:
+ with open(local_path, 'r') as f:
+ local_key_data = f.read()
+ local_key = load_private_key(local_key_data, wrap_tags=False)
+
+ if local_key:
+ local_key_pem = encode_private_key(local_key)
+ config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem))
+ else:
+ print('Failed to migrate local RSA key')
+
+ if config.exists(rsa_keys_base + ['rsa-key-name']):
+ for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']):
+ if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']):
+ continue
+
+ vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key'])
+ public_key = migrate_from_vyatta_key(vyatta_key)
+
+ if public_key:
+ public_key_pem = encode_public_key(public_key)
+ config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem))
+ else:
+ print(f'Failed to migrate rsa-key "{rsa_name}"')
+
+ config.delete(rsa_keys_base)
+
+if config.exists(ipsec_site_base):
+ for peer in config.list_nodes(ipsec_site_base):
+ mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode'])
+
+ if mode != 'rsa':
+ continue
+
+ config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name)
+
+ remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name'])
+ config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name)
+ config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-name'])
+
+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))
+ sys.exit(1)
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index ad7efbf2d..06e227ccf 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -14,91 +14,14 @@
# 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 base64
-import os
import re
-import struct
-import sys
import argparse
from subprocess import TimeoutExpired
-from vyos.util import ask_yes_no, call, cmd, process_named_running
-from Cryptodome.PublicKey.RSA import importKey
+from vyos.util import call
-RSA_LOCAL_KEY_PATH = '/config/ipsec.d/rsa-keys/localhost.key'
-RSA_LOCAL_PUB_PATH = '/etc/ipsec.d/certs/localhost.pub'
-RSA_KEY_PATHS = ['/config/auth', '/config/ipsec.d/rsa-keys']
-
-X509_CONFIG_PATH = '/etc/ipsec.d/key-pair.template'
-X509_PATH = '/config/auth/'
-
-IPSEC_CONF = '/etc/ipsec.conf'
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
-def migrate_to_vyatta_key(path):
- with open(path, 'r') as f:
- key = importKey(f.read())
- e = key.e.to_bytes((key.e.bit_length() + 7) // 8, 'big')
- n = key.n.to_bytes((key.n.bit_length() + 7) // 8, 'big')
- return '0s' + str(base64.b64encode(struct.pack('B', len(e)) + e + n), 'ascii')
- return None
-
-def find_rsa_keys():
- keys = []
- for path in RSA_KEY_PATHS:
- if not os.path.exists(path):
- continue
- for filename in os.listdir(path):
- full_path = os.path.join(path, filename)
- if os.path.isfile(full_path) and full_path.endswith(".key"):
- keys.append(full_path)
- return keys
-
-def show_rsa_keys():
- for key_path in find_rsa_keys():
- print('Private key: ' + os.path.basename(key_path))
- print('Public key: ' + migrate_to_vyatta_key(key_path) + '\n')
-
-def generate_rsa_key(bits = 2192):
- if (bits < 16 or bits > 4096) or bits % 16 != 0:
- print('Invalid bit length')
- return
-
- if os.path.exists(RSA_LOCAL_KEY_PATH):
- if not ask_yes_no("A local RSA key file already exists and will be overwritten. Continue?"):
- return
-
- print(f'Generating rsa-key to {RSA_LOCAL_KEY_PATH}')
-
- directory = os.path.dirname(RSA_LOCAL_KEY_PATH)
- call(f'sudo mkdir -p {directory}')
- result = call(f'sudo /usr/bin/openssl genrsa -out {RSA_LOCAL_KEY_PATH} {bits}')
-
- if result != 0:
- print(f'Could not generate RSA key: {result}')
- return
-
- call(f'sudo /usr/bin/openssl rsa -inform PEM -in {RSA_LOCAL_KEY_PATH} -pubout -out {RSA_LOCAL_PUB_PATH}')
-
- print('Your new local RSA key has been generated')
- print('The public portion of the key is:\n')
- print(migrate_to_vyatta_key(RSA_LOCAL_KEY_PATH))
-
-def generate_x509_pair(name):
- if os.path.exists(X509_PATH + name):
- if not ask_yes_no("A certificate request with this name already exists and will be overwritten. Continue?"):
- return
-
- result = os.system(f'openssl req -new -nodes -keyout {X509_PATH}{name}.key -out {X509_PATH}{name}.csr -config {X509_CONFIG_PATH}')
-
- if result != 0:
- print(f'Could not generate x509 key-pair: {result}')
- return
-
- print('Private key and certificate request has been generated')
- print(f'CSR: {X509_PATH}{name}.csr')
- print(f'Private key: {X509_PATH}{name}.key')
-
def get_peer_connections(peer, tunnel, return_all = False):
search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
matches = []
@@ -183,23 +106,12 @@ def debug_peer(peer, tunnel):
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--action', help='Control action', required=True)
- parser.add_argument('--bits', help='Bits for rsa-key', required=False)
- parser.add_argument('--name', help='Name for x509 key-pair, peer for reset', required=False)
+ parser.add_argument('--name', help='Name for peer reset', required=False)
parser.add_argument('--tunnel', help='Specific tunnel of peer', required=False)
args = parser.parse_args()
- if args.action == 'rsa-key':
- bits = int(args.bits) if args.bits else 2192
- generate_rsa_key(bits)
- elif args.action == 'rsa-key-show':
- show_rsa_keys()
- elif args.action == 'x509':
- if not args.name:
- print('Invalid name for key-pair, aborting.')
- sys.exit(0)
- generate_x509_pair(args.name)
- elif args.action == 'reset-peer':
+ if args.action == 'reset-peer':
reset_peer(args.name, args.tunnel)
elif args.action == "reset-profile":
reset_profile(args.name, args.tunnel)