diff --git a/data/templates/ipsec/charon/dhcp.conf.tmpl b/data/templates/ipsec/charon/dhcp.conf.tmpl
new file mode 100644
index 000000000..2879550a8
--- /dev/null
+++ b/data/templates/ipsec/charon/dhcp.conf.tmpl
@@ -0,0 +1,23 @@
+dhcp {
+ load = yes
+
+{% if options is defined and options.remote_access is defined and options.remote_access.dhcp_pool is defined %}
+{% if options.remote_access.dhcp_pool.interface is defined %}
+ interface = {{ options.remote_access.dhcp_pool.interface }}
+{% endif %}
+{% if options.remote_access.dhcp_pool.server is defined %}
+ server = {{ options.remote_access.dhcp_pool.server }}
+{% endif %}
+{% endif %}
+
+ # Always use the configured server address.
+ # force_server_address = no
+
+ # Derive user-defined MAC address from hash of IKE identity and send client
+ # identity DHCP option.
+ # identity_lease = no
+
+ # Use the DHCP server port (67) as source port when a unicast server address
+ # is configured.
+ # use_server_port = no
+}
diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl
index 0eda8479a..00251d44d 100644
--- a/data/templates/ipsec/swanctl.conf.tmpl
+++ b/data/templates/ipsec/swanctl.conf.tmpl
@@ -1,96 +1,106 @@
### Autogenerated by vpn_ipsec.py ###
{% import 'ipsec/swanctl/profile.tmpl' as profile_tmpl %}
{% import 'ipsec/swanctl/peer.tmpl' as peer_tmpl %}
{% import 'ipsec/swanctl/remote_access.tmpl' as remote_access_tmpl %}
connections {
{% if profile is defined %}
{% for name, profile_conf in profile.items() if profile_conf.disable is not defined and profile_conf.bind is defined and profile_conf.bind.tunnel is defined %}
{{ profile_tmpl.conn(name, profile_conf, ike_group, esp_group) }}
{% endfor %}
{% endif %}
{% if site_to_site is defined and site_to_site.peer is defined %}
{% for peer, peer_conf in site_to_site.peer.items() if peer not in dhcp_no_address and peer_conf.disable is not defined %}
{{ peer_tmpl.conn(peer, peer_conf, ike_group, esp_group) }}
{% endfor %}
{% endif %}
{% if remote_access is defined and remote_access is not none %}
{% for rw, rw_conf in remote_access.items() if rw_conf.disable is not defined %}
{{ remote_access_tmpl.conn(rw, rw_conf, ike_group, esp_group) }}
{% endfor %}
{% endif %}
}
pools {
{% if remote_access is defined %}
-{% for ra, ra_conf in remote_access.items() if remote_access is defined %}
+{% for ra, ra_conf in remote_access.items() if ra_conf.pool.dhcp_enable is not defined %}
ra-{{ ra }} {
addrs = {{ ra_conf.pool.prefix }}
dns = {{ ra_conf.pool.name_server | join(",") }}
{% if ra_conf.pool.exclude is defined %}
split_exclude = {{ ra_conf.pool.exclude | join(",") }}
{% endif %}
}
{% endfor %}
{% endif %}
}
secrets {
{% if profile is defined %}
{% for name, profile_conf in profile.items() if profile_conf.disable is not defined and profile_conf.bind is defined and profile_conf.bind.tunnel is defined %}
{% if profile_conf.authentication.mode == 'pre-shared-secret' %}
{% for interface in profile_conf.bind.tunnel %}
ike-dmvpn-{{ interface }} {
secret = {{ profile_conf.authentication.pre_shared_secret }}
}
{% endfor %}
{% endif %}
{% 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' %}
ike_{{ peer_name }} {
{% if peer_conf.local_address is defined %}
id-local = {{ peer_conf.local_address }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }}
{% endif %}
id-remote = {{ peer }}
{% if peer_conf.authentication.id is defined %}
id-localid = {{ peer_conf.authentication.id }}
{% endif %}
{% if peer_conf.authentication.remote_id is defined %}
id-remoteid = {{ peer_conf.authentication.remote_id }}
{% endif %}
secret = "{{ peer_conf.authentication.pre_shared_secret }}"
}
{% elif peer_conf.authentication.mode == 'x509' %}
private_{{ peer_name }} {
file = {{ peer_conf.authentication.x509.certificate }}.pem
{% if peer_conf.authentication.x509.passphrase is defined %}
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 }}
}
{% endif %}
{% endfor %}
{% endif %}
{% if remote_access is defined %}
{% for ra, ra_conf in remote_access.items() if remote_access is defined %}
-{% if ra_conf.authentication is defined and ra_conf.authentication.local_users is defined and ra_conf.authentication.local_users.username is defined %}
+{% if ra_conf.authentication.server_mode == 'pre-shared-secret' %}
+ ike_{{ ra }} {
+{% if ra_conf.authentication.id is defined %}
+ id = "{{ ra_conf.authentication.id }}"
+{% elif ra_conf.local_address is defined %}
+ id = "{{ ra_conf.local_address }}"
+{% endif %}
+ secret = "{{ ra_conf.authentication.pre_shared_secret }}"
+ }
+{% endif %}
+{% if ra_conf.authentication.client_mode == 'eap-mschapv2' and ra_conf.authentication.local_users is defined and ra_conf.authentication.local_users.username is defined %}
{% for user, user_conf in ra_conf.authentication.local_users.username.items() if user_conf.disable is not defined %}
eap-{{ ra }}-{{ user }} {
secret = "{{ user_conf.password }}"
id-{{ ra }}-{{ user }} = "{{ user }}"
}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
}
diff --git a/data/templates/ipsec/swanctl/remote_access.tmpl b/data/templates/ipsec/swanctl/remote_access.tmpl
index a3a1cf0b2..95f2108fb 100644
--- a/data/templates/ipsec/swanctl/remote_access.tmpl
+++ b/data/templates/ipsec/swanctl/remote_access.tmpl
@@ -1,40 +1,48 @@
{% macro conn(name, rw_conf, ike_group, esp_group) %}
{# peer needs to reference the global IKE configuration for certain values #}
{% set ike = ike_group[rw_conf.ike_group] %}
{% set esp = esp_group[rw_conf.esp_group] %}
ra-{{ name }} {
remote_addrs = %any
local_addrs = {{ rw_conf.local_address if rw_conf.local_address is defined else '%any' }}
proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }}
send_certreq = no
rekey_time = {{ ike.lifetime }}s
keyingtries = 0
+{% if rw_conf.pool.dhcp_enable is defined %}
+ pools = dhcp
+{% else %}
pools = ra-{{ name }}
+{% endif %}
local {
- auth = pubkey
-{% if rw_conf.authentication is defined and rw_conf.authentication.id is defined and rw_conf.authentication.use_x509_id is not defined %}
+{% if rw_conf.authentication.id is defined and rw_conf.authentication.use_x509_id is not defined %}
id = "{{ rw_conf.authentication.id }}"
{% endif %}
-{% if rw_conf.authentication is defined and rw_conf.authentication.x509 is defined and rw_conf.authentication.x509.certificate is defined %}
+{% if rw_conf.authentication.server_mode == 'x509' %}
+ auth = pubkey
certs = {{ rw_conf.authentication.x509.certificate }}.pem
+{% elif rw_conf.authentication.server_mode == 'pre-shared-secret' %}
+ auth = psk
{% endif %}
}
remote {
- auth = eap-mschapv2
+ auth = {{ rw_conf.authentication.client_mode }}
+{% if rw_conf.authentication.client_mode.startswith("eap") %}
eap_id = %any
+{% endif %}
}
children {
ikev2-vpn {
esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }}
rekey_time = {{ esp.lifetime }}s
rand_time = 540s
dpd_action = clear
{% set local_prefix = rw_conf.local.prefix if rw_conf.local is defined and rw_conf.local.prefix is defined else ['0.0.0.0/0', '::/0'] %}
{% set local_port = rw_conf.local.port if rw_conf.local is defined and rw_conf.local.port is defined else '' %}
{% set local_suffix = '[%any/{1}]'.format(local_port) if local_port else '' %}
local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }}
}
}
}
{% endmacro %}
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index f6b18d1d5..4425ab02a 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -1,1043 +1,1125 @@
Virtual Private Network (VPN)
VPN IP security (IPsec) parameters
901
Set auto-update interval for IPsec daemon
u32:30-65535
Auto-update interval (s)
Option to disable requirement for unique IDs in the Security Database
Name of Encapsulating Security Payload (ESP) group
ESP compression
disable enable
disable
Disable ESP compression (default)
enable
Enable ESP compression
^(disable|enable)$
ESP lifetime
u32:30-86400
ESP lifetime in seconds (default 3600)
3600
ESP mode
tunnel transport
tunnel
Tunnel mode (default)
transport
Transport mode
^(tunnel|transport)$
tunnel
ESP Perfect Forward Secrecy
enable dh-group1 dh-group2 dh-group5 dh-group14 dh-group15 dh-group16 dh-group17 dh-group18 dh-group19 dh-group20 dh-group21 dh-group22 dh-group23 dh-group24 dh-group25 dh-group26 dh-group27 dh-group28 dh-group29 dh-group30 dh-group31 dh-group32 disable
enable
Use Diffie-Hellman group 2 (modp1024) - default
dh-group1
Use Diffie-Hellman group 1 (modp768)
dh-group2
Use Diffie-Hellman group 2 (modp1024)
dh-group5
Use Diffie-Hellman group 5 (modp1536)
dh-group14
Use Diffie-Hellman group 14 (modp2048)
dh-group15
Use Diffie-Hellman group 15 (modp3072)
dh-group16
Use Diffie-Hellman group 16 (modp4096)
dh-group17
Use Diffie-Hellman group 17 (modp6144)
dh-group18
Use Diffie-Hellman group 18 (modp8192)
dh-group19
Use Diffie-Hellman group 19 (ecp256)
dh-group20
Use Diffie-Hellman group 20 (ecp384)
dh-group21
Use Diffie-Hellman group 21 (ecp521)
dh-group22
Use Diffie-Hellman group 22 (modp1024s160)
dh-group23
Use Diffie-Hellman group 23 (modp2048s224)
dh-group24
Use Diffie-Hellman group 24 (modp2048s256)
dh-group25
Use Diffie-Hellman group 25 (ecp192)
dh-group26
Use Diffie-Hellman group 26 (ecp224)
dh-group27
Use Diffie-Hellman group 27 (ecp224bp)
dh-group28
Use Diffie-Hellman group 28 (ecp256bp)
dh-group29
Use Diffie-Hellman group 29 (ecp384bp)
dh-group30
Use Diffie-Hellman group 30 (ecp512bp)
dh-group31
Use Diffie-Hellman group 31 (curve25519)
dh-group32
Use Diffie-Hellman group 32 (curve448)
disable
Disable PFS
^(enable|dh-group1|dh-group2|dh-group5|dh-group14|dh-group15|dh-group16|dh-group17|dh-group18|dh-group19|dh-group20|dh-group21|dh-group22|dh-group23|dh-group24|dh-group25|dh-group26|dh-group27|dh-group28|dh-group29|dh-group30|dh-group31|dh-group32|disable)$
enable
ESP-group proposal [REQUIRED]
u32:1-65535
ESP-group proposal number
#include
#include
Name of Internet Key Exchange (IKE) group
close-action_help
none hold clear restart
none
Set action to none (default)
hold
Set action to hold
clear
Set action to clear
restart
Set action to restart
^(none|hold|clear|restart)$
Dead Peer Detection (DPD)
Keep-alive failure action
hold clear restart
hold
Set action to hold (default)
clear
Set action to clear
restart
Set action to restart
^(hold|clear|restart)$
Keep-alive interval
u32:2-86400
Keep-alive interval in seconds (default 30)
Dead-Peer-Detection keep-alive timeout (IKEv1 only)
u32:2-86400
Keep-alive timeout in seconds (default 120)
ikev2-reauth_help
yes no
yes
Enable remote host re-authentication during an IKE rekey. Currently broken due to a strongswan bug
no
Disable remote host re-authenticaton during an IKE rekey. (Default)
^(yes|no)$
Key Exchange Version
ikev1 ikev2
ikev1
Use IKEv1 for Key Exchange [DEFAULT]
ikev2
Use IKEv2 for Key Exchange
^(ikev1|ikev2)$
IKE lifetime
u32:30-86400
IKE lifetime in seconds (default 28800)
28800
Enable MOBIKE Support. MOBIKE is only available for IKEv2.
enable disable
enable
Enable MOBIKE (default for IKEv2)
disable
Disable MOBIKE
^(enable|disable)$
IKEv1 Phase 1 Mode Selection
main aggressive
main
Use Main mode for Key Exchanges in the IKEv1 Protocol (Recommended Default)
aggressive
Use Aggressive mode for Key Exchanges in the IKEv1 protocol - We do not recommend users to use aggressive mode as it is much more insecure compared to Main mode.
^(main|aggressive)$
proposal_help
u32:1-65535
IKE-group proposal
2
dh-grouphelp
1 2 5 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
1
Diffie-Hellman group 1 (modp768)
2
Diffie-Hellman group 2 (modp1024)
5
Diffie-Hellman group 5 (modp1536)
14
Diffie-Hellman group 14 (modp2048)
15
Diffie-Hellman group 15 (modp3072)
16
Diffie-Hellman group 16 (modp4096)
17
Diffie-Hellman group 17 (modp6144)
18
Diffie-Hellman group 18 (modp8192)
19
Diffie-Hellman group 19 (ecp256)
20
Diffie-Hellman group 20 (ecp384)
21
Diffie-Hellman group 21 (ecp521)
22
Diffie-Hellman group 22 (modp1024s160)
23
Diffie-Hellman group 23 (modp2048s224)
24
Diffie-Hellman group 24 (modp2048s256)
25
Diffie-Hellman group 25 (ecp192)
26
Diffie-Hellman group 26 (ecp224)
27
Diffie-Hellman group 27 (ecp224bp)
28
Diffie-Hellman group 28 (ecp256bp)
29
Diffie-Hellman group 29 (ecp384bp)
30
Diffie-Hellman group 30 (ecp512bp)
31
Diffie-Hellman group 31 (curve25519)
32
Diffie-Hellman group 32 (curve448)
^(1|2|5|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)$
#include
#include
Sets to include an additional configuration directive file for strongSwan. Use an absolute path to specify the included file
Sets to include an additional secrets file for strongSwan. Use an absolute path to specify the included file.
Interface to use for VPN [REQUIRED]
IPsec interface [REQUIRED]
IPsec logging
strongSwan Logger Level
u32:0
Very basic auditing logs e.g. SA up/SA down (default)
u32:1
Generic control flow with errors, a good default to see whats going on
u32:2
More detailed debugging control flow
0
Subsystem in the daemon the log comes from
dmn mgr ike chd job cfg knl net asn enc lib esp tls tnc imc imv pts any
dmn
Main daemon setup/cleanup/signal handling
mgr
IKE_SA manager, handling synchronization for IKE_SA access
ike
IKE_SA/ISAKMP SA
chd
CHILD_SA/IPsec SA
job
Jobs queuing/processing and thread pool management
cfg
Configuration management and plugins
knl
IPsec/Networking kernel interface
net
IKE network communication
asn
Low-level encoding/decoding (ASN.1, X.509 etc.)
enc
Packet encoding/decoding encryption/decryption operations
lib
libstrongswan library messages
esp
libipsec library messages
tls
libtls library messages
tnc
Trusted Network Connect
imc
Integrity Measurement Collector
imv
Integrity Measurement Verifier
pts
Platform Trust Service
any
Any subsystem
^(dmn|mgr|ike|chd|job|cfg|knl|net|asn|enc|lib|esp|tls|tnc|imc|imv|pts|any)$
Global IPsec settings
Do not automatically install routes to remote networks
+
+
+ remote-access global options
+
+
+
+
+ DHCP pool options for remote-access
+
+
+
+
+ Interface with DHCP server to use
+
+
+
+
+
+
+
+ DHCP server address
+
+ ipv4
+ IPv4 address of the DHCP server
+
+
+
+
+
+
+
VPN IPSec Profile
#include
Authentication [REQUIRED]
Authentication mode
pre-shared-secret
pre-shared-secret
Use pre shared secret key
Pre-shared secret key
txt
Pre-shared secret key
DMVPN crypto configuration
Tunnel interface associated with this configuration profile
interfaces tunnel
txt
Associated interface to this configuration profile
#include
#include
Remote access IKEv2 VPN
Authentication for remote access
#include
#include
+
+
+ Client authentication mode
+
+ eap-tls eap-mschapv2
+
+
+ eap-tls
+ EAP-TLS
+
+
+ eap-mschapv2
+ EAP-MSCHAPv2
+
+
+ ^(eap-tls|eap-mschapv2)$
+
+
+ eap-mschapv2
+
Local user authentication for PPPoE server
User name for authentication
#include
Password for authentication
+
+
+ Server authentication mode
+
+ pre-shared-secret x509
+
+
+ pre-shared-secret
+ pre-shared-secret_description
+
+
+ x509
+ x509_description
+
+
+ ^(pre-shared-secret|x509)$
+
+
+ x509
+
+
+
+ Pre-shared-secret used for server authentication
+
+
#include
#include
#include
#include
#include
#include
IP address pool for remote-access users
+
+
+ Enable DHCP pool for clients on this connection
+
+
+
Local IPv4 or IPv6 pool prefix exclusions
ipv4
Local IPv4 pool prefix exclusion
ipv6
Local IPv6 pool prefix exclusion
Local IPv4 or IPv6 pool prefix
ipv4
Local IPv4 pool prefix
ipv6
Local IPv6 pool prefix
#include
Timeout to close connection if no data is transmitted
u32:10-86400
Timeout in seconds (default 28800)
28800
Site-to-site VPN
VPN peer
ipv4
IPv4 address of the peer
ipv6
IPv6 address of the peer
txt
Hostname of the peer
<@text>
ID of the peer
#include
Peer authentication [REQUIRED]
#include
#include
Authentication mode
pre-shared-secret rsa x509
pre-shared-secret
pre-shared-secret_description
rsa
rsa_description
x509
x509_description
^(pre-shared-secret|rsa|x509)$
Pre-shared secret key
txt
Pre-shared secret key
ID for remote authentication
txt
ID used for peer authentication
RSA key name
Use certificate common name as ID
Connection type
initiate respond
initiate
initiate_description
respond
respond_description
^(initiate|respond)$
Defult ESP group name
vpn ipsec esp-group
#include
DHCP interface to listen on
Force UDP Encapsulation for ESP Payloads
enable disable
enable
This endpoint will force UDP encapsulation for this peer
disable
This endpoint will not force UDP encapsulation for this peer
^(enable|disable)$
#include
Re-authentication of the remote peer during an IKE re-key. IKEv2 option only
yes no inherit
yes
Enable remote host re-autentication during an IKE re-key. Currently broken due to a strong swan bug
no
Disable remote host re-authenticaton during an IKE re-key.
inherit
Inherit the reauth configuration form your IKE-group (Default)
^(yes|no|inherit)$
#include
Peer tunnel [REQUIRED]
u32
Peer tunnel [REQUIRED]
#include
#include
#include
#include
Remote parameters for interesting traffic
#include
Remote IPv4 or IPv6 prefix
ipv4
Remote IPv4 prefix
ipv6
Remote IPv6 prefix
Virtual tunnel interface [REQUIRED]
VTI tunnel interface associated with this configuration
interfaces vti
#include
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 76ee64a20..cf23a89c6 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -1,418 +1,462 @@
#!/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 .
import ipaddress
import os
from sys import exit
from time import sleep
from vyos.config import Config
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 wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_public_key
from vyos.pki import wrap_private_key
from vyos.template import ip_from_cidr
from vyos.template import render
from vyos.validate import is_ipv6_link_local
from vyos.util import call
from vyos.util import dict_search_args
from vyos.util import run
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
dhcp_wait_attempts = 2
dhcp_wait_sleep = 1
swanctl_dir = '/etc/swanctl'
ipsec_conf = '/etc/ipsec.conf'
ipsec_secrets = '/etc/ipsec.secrets'
+charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
interface_conf = '/etc/strongswan.d/interfaces_use.conf'
swanctl_conf = f'{swanctl_dir}/swanctl.conf'
CERT_PATH = f'{swanctl_dir}/x509/'
KEY_PATH = f'{swanctl_dir}/private/'
CA_PATH = f'{swanctl_dir}/x509ca/'
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
else:
conf = Config()
base = ['vpn', 'ipsec']
if not conf.exists(base):
return None
# retrieve common dictionary keys
ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
# XXX: T2665: we must safely remove default values for tag nodes, those are
# added in a more fine grained way later on
del default_values['esp_group']
del default_values['ike_group']
del default_values['remote_access']
ipsec = dict_merge(default_values, ipsec)
if 'esp_group' in ipsec:
default_values = defaults(base + ['esp-group'])
for group in ipsec['esp_group']:
ipsec['esp_group'][group] = dict_merge(default_values,
ipsec['esp_group'][group])
if 'ike_group' in ipsec:
default_values = defaults(base + ['ike-group'])
for group in ipsec['ike_group']:
ipsec['ike_group'][group] = dict_merge(default_values,
ipsec['ike_group'][group])
if 'remote_access' in ipsec:
default_values = defaults(base + ['remote-access'])
for rw in ipsec['remote_access']:
ipsec['remote_access'][rw] = dict_merge(default_values,
ipsec['remote_access'][rw])
ipsec['dhcp_no_address'] = {}
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['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:
return None
for address in addresses:
if not is_ipv6_link_local(address):
return ip_from_cidr(address)
return None
def verify_pki(pki, x509_conf):
if not pki or 'ca' not in pki or 'certificate' not in pki:
raise ConfigError(f'PKI is not configured')
ca_cert_name = x509_conf['ca_certificate']
cert_name = x509_conf['certificate']
- if not dict_search_args(ipsec['pki'], 'ca', ca_cert_name, 'certificate'):
+ if not dict_search_args(pki, 'ca', ca_cert_name, 'certificate'):
raise ConfigError(f'Missing CA certificate on specified PKI CA certificate "{ca_cert_name}"')
- if not dict_search_args(ipsec['pki'], 'certificate', cert_name, 'certificate'):
+ if not dict_search_args(pki, 'certificate', cert_name, 'certificate'):
raise ConfigError(f'Missing certificate on specified PKI certificate "{cert_name}"')
- if not dict_search_args(ipsec['pki'], 'certificate', cert_name, 'private', 'key'):
+ if not dict_search_args(pki, 'certificate', cert_name, 'private', 'key'):
raise ConfigError(f'Missing private key on specified PKI certificate "{cert_name}"')
return True
def verify(ipsec):
if not ipsec:
return None
if 'ipsec_interfaces' in ipsec and 'interface' in ipsec['ipsec_interfaces']:
interfaces = ipsec['ipsec_interfaces']['interface']
if isinstance(interfaces, str):
interfaces = [interfaces]
for ifname in interfaces:
verify_interface_exists(ifname)
if 'profile' in ipsec:
for profile, profile_conf in ipsec['profile'].items():
if 'esp_group' in profile_conf:
if 'esp_group' not in ipsec or profile_conf['esp_group'] not in ipsec['esp_group']:
raise ConfigError(f"Invalid esp-group on {profile} profile")
else:
raise ConfigError(f"Missing esp-group on {profile} profile")
if 'ike_group' in profile_conf:
if 'ike_group' not in ipsec or profile_conf['ike_group'] not in ipsec['ike_group']:
raise ConfigError(f"Invalid ike-group on {profile} profile")
else:
raise ConfigError(f"Missing ike-group on {profile} profile")
if 'authentication' not in profile_conf:
raise ConfigError(f"Missing authentication on {profile} profile")
+ if 'remote_access' in ipsec:
+ for name, ra_conf in ipsec['remote_access'].items():
+ if 'esp_group' in ra_conf:
+ if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']:
+ raise ConfigError(f"Invalid esp-group on {name} remote-access config")
+ else:
+ raise ConfigError(f"Missing esp-group on {name} remote-access config")
+
+ if 'ike_group' in ra_conf:
+ if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']:
+ raise ConfigError(f"Invalid ike-group on {name} remote-access config")
+ else:
+ raise ConfigError(f"Missing ike-group on {name} remote-access config")
+
+ if 'authentication' not in ra_conf:
+ raise ConfigError(f"Missing authentication on {name} remote-access config")
+
+ if ra_conf['authentication']['server_mode'] == 'x509':
+ if 'x509' not in ra_conf['authentication']:
+ raise ConfigError(f"Missing x509 settings on {name} remote-access config")
+
+ x509 = ra_conf['authentication']['x509']
+
+ 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)
+ 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")
+
if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
has_default_esp = False
if 'default_esp_group' in peer_conf:
has_default_esp = True
if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']:
raise ConfigError(f"Invalid esp-group on site-to-site peer {peer}")
if 'ike_group' in peer_conf:
if 'ike_group' not in ipsec or peer_conf['ike_group'] not in ipsec['ike_group']:
raise ConfigError(f"Invalid ike-group on site-to-site peer {peer}")
else:
raise ConfigError(f"Missing ike-group on site-to-site peer {peer}")
if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']:
raise ConfigError(f"Missing authentication on site-to-site peer {peer}")
if peer_conf['authentication']['mode'] == 'x509':
if 'x509' not in peer_conf['authentication']:
raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}")
x509 = peer_conf['authentication']['x509']
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)
if peer_conf['authentication']['mode'] == 'rsa':
if not verify_rsa_local_key(ipsec):
raise ConfigError(f"Invalid key on rsa-keys local-key")
if 'rsa_key_name' not in peer_conf['authentication']:
raise ConfigError(f"Missing rsa-key-name 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 '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}")
if 'dhcp_interface' in peer_conf:
dhcp_interface = peer_conf['dhcp_interface']
verify_interface_exists(dhcp_interface)
if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'):
raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}")
address = get_dhcp_address(dhcp_interface)
count = 0
while not address and count < dhcp_wait_attempts:
address = get_dhcp_address(dhcp_interface)
count += 1
sleep(dhcp_wait_sleep)
if not address:
ipsec['dhcp_no_address'][peer] = dhcp_interface
print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped")
continue
if 'vti' in peer_conf:
if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf:
raise ConfigError(f"A single local-address or dhcp-interface is required when using VTI on site-to-site peer {peer}")
if 'bind' in peer_conf['vti']:
vti_interface = peer_conf['vti']['bind']
if not os.path.exists(f'/sys/class/net/{vti_interface}'):
raise ConfigError(f'VTI interface {vti_interface} for site-to-site peer {peer} does not exist!')
if 'vti' not in peer_conf and 'tunnel' not in peer_conf:
raise ConfigError(f"No VTI or tunnel specified on site-to-site peer {peer}")
if 'tunnel' in peer_conf:
for tunnel, tunnel_conf in peer_conf['tunnel'].items():
if 'esp_group' not in tunnel_conf and not has_default_esp:
raise ConfigError(f"Missing esp-group on tunnel {tunnel} for site-to-site peer {peer}")
esp_group_name = tunnel_conf['esp_group'] if 'esp_group' in tunnel_conf else peer_conf['default_esp_group']
if esp_group_name not in ipsec['esp_group']:
raise ConfigError(f"Invalid esp-group on tunnel {tunnel} for site-to-site peer {peer}")
esp_group = ipsec['esp_group'][esp_group_name]
if 'mode' in esp_group and esp_group['mode'] == 'transport':
if 'protocol' in tunnel_conf and ((peer in ['any', '0.0.0.0']) or ('local_address' not in peer_conf or peer_conf['local_address'] in ['any', '0.0.0.0'])):
raise ConfigError(f"Fixed local-address or peer required when a protocol is defined with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']):
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]:
+ if not os.path.exists(path):
+ continue
+ for file in os.listdir(path):
+ file_path = os.path.join(path, file)
+ if os.path.isfile(file_path):
+ os.unlink(file_path)
+
def generate_pki_files(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 []
crl_index = 1
cert_name = x509_conf['certificate']
cert_data = dict_search_args(pki, 'certificate', cert_name, 'certificate')
key_data = dict_search_args(pki, 'certificate', cert_name, 'private', 'key')
protected = 'passphrase' in x509_conf
with open(os.path.join(CA_PATH, f'{ca_cert_name}.pem'), 'w') as f:
f.write(wrap_certificate(ca_cert_data))
for crl in ca_cert_crls:
with open(os.path.join(CRL_PATH, f'{ca_cert_name}_{crl_index}.pem'), 'w') as f:
f.write(wrap_crl(crl))
crl_index += 1
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:
f.write(wrap_private_key(key_data, protected))
def generate(ipsec):
+ cleanup_pki_files()
+
if not ipsec:
for config_file in [ipsec_conf, ipsec_secrets, interface_conf, swanctl_conf]:
if os.path.isfile(config_file):
os.unlink(config_file)
return
if ipsec['dhcp_no_address']:
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]:
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 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'])
if 'site_to_site' in data and 'peer' in data['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'])
local_ip = ''
if 'local_address' in peer_conf:
local_ip = peer_conf['local_address']
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
if 'tunnel' in peer_conf:
for tunnel, tunnel_conf in peer_conf['tunnel'].items():
local_prefixes = dict_search_args(tunnel_conf, 'local', 'prefix')
remote_prefixes = dict_search_args(tunnel_conf, 'remote', 'prefix')
if not local_prefixes or not remote_prefixes:
continue
passthrough = []
for local_prefix in local_prefixes:
for remote_prefix in remote_prefixes:
local_net = ipaddress.ip_network(local_prefix)
remote_net = ipaddress.ip_network(remote_prefix)
if local_net.overlaps(remote_net):
passthrough.append(local_prefix)
data['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_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)
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
tmp = run('/usr/libexec/vyos/conf_mode/protocols_nhrp.py')
if tmp > 0:
print('ERROR: failed to reapply NHRP settings!')
def apply(ipsec):
if not ipsec:
call('sudo ipsec stop')
else:
args = ''
if 'auto_update' in ipsec:
args = '--auto-update ' + ipsec['auto_update']
call(f'sudo ipsec restart {args}')
call('sudo ipsec rereadall')
call('sudo ipsec reload')
sleep(5) # Give charon enough time to start
call('sudo swanctl -q')
resync_l2tp(ipsec)
resync_nhrp(ipsec)
if __name__ == '__main__':
try:
ipsec = get_config()
verify(ipsec)
generate(ipsec)
apply(ipsec)
except ConfigError as e:
print(e)
exit(1)