summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-07-07 17:32:00 +0200
committerGitHub <noreply@github.com>2021-07-07 17:32:00 +0200
commit17dff308810497aab19428a6340a4261d91893e4 (patch)
tree8bde3ac286bc552bea9322efcdda33e05e3a86e9 /src/conf_mode
parent511253635a9b67396788d24bacafd237594e0e12 (diff)
parent5a7c46016a23387312b2c9e18528ad7bb20e8366 (diff)
downloadvyos-1x-17dff308810497aab19428a6340a4261d91893e4.tar.gz
vyos-1x-17dff308810497aab19428a6340a4261d91893e4.zip
Merge pull request #912 from sarthurdev/pki_ipsec_rsa
pki: T3642: Migrate rsa-keys to PKI configuration
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py122
-rwxr-xr-xsrc/conf_mode/vpn_rsa-keys.py113
2 files changed, 67 insertions, 168 deletions
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)