summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-07-25 00:13:32 +0200
committerChristian Poessinger <christian@poessinger.com>2021-07-25 00:15:08 +0200
commit794fa2206659457ba45c6f476ba8b162460cdaad (patch)
tree5e61ed248198e590586933e7a6b779bf9eb6ec07
parent937f1e43ebb9b844b9b73584c3571a18d56bf529 (diff)
downloadvyos-1x-794fa2206659457ba45c6f476ba8b162460cdaad.tar.gz
vyos-1x-794fa2206659457ba45c6f476ba8b162460cdaad.zip
ipsec: T1210: add RADIUS authentication for remote-access IKEv2 VPN
set vpn ipsec remote-access connection rw authentication client-mode 'eap-radius' set vpn ipsec remote-access connection rw authentication id '192.0.2.1' set vpn ipsec remote-access connection rw authentication server-mode 'x509' set vpn ipsec remote-access connection rw authentication x509 ca-certificate 'CAcert_Class_3_Root' set vpn ipsec remote-access connection rw authentication x509 certificate 'vyos' set vpn ipsec remote-access connection rw esp-group 'ESP-RW' set vpn ipsec remote-access connection rw ike-group 'IKE-RW' set vpn ipsec remote-access connection rw local-address '192.0.2.1' set vpn ipsec remote-access connection rw pool 'ra-rw-ipv4' set vpn ipsec remote-access connection rw unique 'never' set vpn ipsec remote-access pool ra-rw-ipv4 name-server '192.0.2.2' set vpn ipsec remote-access pool ra-rw-ipv4 prefix '192.168.22.0/24' set vpn ipsec remote-access radius nas-identifier 'fooo' set vpn ipsec remote-access radius server 172.16.100.10 key 'secret'
-rw-r--r--Makefile5
-rw-r--r--data/templates/ipsec/charon/eap-radius.conf.tmpl115
-rw-r--r--interface-definitions/vpn_ipsec.xml.in23
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py35
4 files changed, 165 insertions, 13 deletions
diff --git a/Makefile b/Makefile
index 32bc58be0..c8f6ac7c9 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,10 @@ interface_definitions: $(config_xml_obj)
find $(BUILD_DIR)/interface-definitions -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
# XXX: delete top level node.def's that now live in other packages
- rm -f $(TMPL_DIR)/firewall/node.def
+ # IPSec VPN EAP-RADIUS does not support source-address
+ rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address
+ # T3568: firewall is yet not migrated to XML and Python - this is only a dummy
+ rm -rf $(TMPL_DIR)/firewall/node.def
rm -rf $(TMPL_DIR)/nfirewall
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
diff --git a/data/templates/ipsec/charon/eap-radius.conf.tmpl b/data/templates/ipsec/charon/eap-radius.conf.tmpl
new file mode 100644
index 000000000..5ec35c988
--- /dev/null
+++ b/data/templates/ipsec/charon/eap-radius.conf.tmpl
@@ -0,0 +1,115 @@
+eap-radius {
+ # Send RADIUS accounting information to RADIUS servers.
+ # accounting = no
+
+ # Close the IKE_SA if there is a timeout during interim RADIUS accounting
+ # updates.
+ # accounting_close_on_timeout = yes
+
+ # Interval in seconds for interim RADIUS accounting updates, if not
+ # specified by the RADIUS server in the Access-Accept message.
+ # accounting_interval = 0
+
+ # If enabled, accounting is disabled unless an IKE_SA has at least one
+ # virtual IP. Only for IKEv2, for IKEv1 a virtual IP is strictly necessary.
+ # accounting_requires_vip = no
+
+ # If enabled, adds the Class attributes received in Access-Accept message to
+ # the RADIUS accounting messages.
+ # accounting_send_class = no
+
+ # Use class attributes in Access-Accept messages as group membership
+ # information.
+ # class_group = no
+
+ # Closes all IKE_SAs if communication with the RADIUS server times out. If
+ # it is not set only the current IKE_SA is closed.
+ # close_all_on_timeout = no
+
+ # Send EAP-Start instead of EAP-Identity to start RADIUS conversation.
+ # eap_start = no
+
+ # Use filter_id attribute as group membership information.
+ # filter_id = no
+
+ # Prefix to EAP-Identity, some AAA servers use a IMSI prefix to select the
+ # EAP method.
+ # id_prefix =
+
+ # Whether to load the plugin. Can also be an integer to increase the
+ # priority of this plugin.
+ load = yes
+
+ # NAS-Identifier to include in RADIUS messages.
+ nas_identifier = {{ remote_access.radius.nas_identifier if remote_access is defined and remote_access.radius is defined and remote_access.radius.nas_identifier is defined else 'strongSwan' }}
+
+ # Port of RADIUS server (authentication).
+ # port = 1812
+
+ # Base to use for calculating exponential back off.
+ # retransmit_base = 1.4
+
+ # Timeout in seconds before sending first retransmit.
+ # retransmit_timeout = 2.0
+
+ # Number of times to retransmit a packet before giving up.
+ # retransmit_tries = 4
+
+ # Shared secret between RADIUS and NAS. If set, make sure to adjust the
+ # permissions of the config file accordingly.
+ # secret =
+
+ # IP/Hostname of RADIUS server.
+ # server =
+
+ # Number of sockets (ports) to use, increase for high load.
+ # sockets = 1
+
+ # Whether to include the UDP port in the Called- and Calling-Station-Id
+ # RADIUS attributes.
+ # station_id_with_port = yes
+
+ dae {
+ # Enables support for the Dynamic Authorization Extension (RFC 5176).
+ # enable = no
+
+ # Address to listen for DAE messages from the RADIUS server.
+ # listen = 0.0.0.0
+
+ # Port to listen for DAE requests.
+ # port = 3799
+
+ # Shared secret used to verify/sign DAE messages. If set, make sure to
+ # adjust the permissions of the config file accordingly.
+ # secret =
+ }
+
+ forward {
+ # RADIUS attributes to be forwarded from IKEv2 to RADIUS.
+ # ike_to_radius =
+
+ # Same as ike_to_radius but from RADIUS to IKEv2.
+ # radius_to_ike =
+ }
+
+ # Section to specify multiple RADIUS servers.
+ servers {
+{% if remote_access is defined and remote_access.radius is defined and remote_access.radius.server is defined %}
+{% for server, server_options in remote_access.radius.server.items() if server_options.disable is not defined %}
+ {{ server | replace('.', '-') }} {
+ address = {{ server }}
+ secret = {{ server_options.key }}
+ auth_port = {{ server_options.port }}
+{% if server_options.disable_accounting is not defined %}
+ acct_port = {{ server_options.port | int +1 }}
+{% endif %}
+ sockets = 20
+ }
+{% endfor %}
+{% endif %}
+ }
+
+ # Section to configure multiple XAuth authentication rounds via RADIUS.
+ xauth {
+ }
+}
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 858adb13a..165fdfdf3 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -703,18 +703,22 @@
<properties>
<help>Client authentication mode</help>
<completionHelp>
- <list>eap-tls eap-mschapv2</list>
+ <list>eap-tls eap-mschapv2 eap-radius</list>
</completionHelp>
<valueHelp>
<format>eap-tls</format>
- <description>EAP-TLS</description>
+ <description>Client uses EAP-TLS authentication</description>
</valueHelp>
<valueHelp>
<format>eap-mschapv2</format>
- <description>EAP-MSCHAPv2</description>
+ <description>Client uses EAP-MSCHAPv2 authentication</description>
+ </valueHelp>
+ <valueHelp>
+ <format>eap-radius</format>
+ <description>Client uses EAP-RADIUS authentication</description>
</valueHelp>
<constraint>
- <regex>^(eap-tls|eap-mschapv2)$</regex>
+ <regex>^(eap-tls|eap-mschapv2|eap-radius)$</regex>
</constraint>
</properties>
<defaultValue>eap-mschapv2</defaultValue>
@@ -872,6 +876,17 @@
#include <include/accel-ppp/name-server.xml.i>
</children>
</tagNode>
+ #include <include/radius-server-ipv4.xml.i>
+ <node name="radius">
+ <children>
+ #include <include/radius-nas-identifier.xml.i>
+ <tagNode name="server">
+ <children>
+ #include <include/accel-ppp/radius-additions-disable-accounting.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
<node name="site-to-site">
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 123380827..a4cd33e64 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -49,13 +49,14 @@ 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_conf = '/etc/strongswan.d/charon.conf'
-charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
-interface_conf = '/etc/strongswan.d/interfaces_use.conf'
-swanctl_conf = f'{swanctl_dir}/swanctl.conf'
+swanctl_dir = '/etc/swanctl'
+ipsec_conf = '/etc/ipsec.conf'
+ipsec_secrets = '/etc/ipsec.secrets'
+charon_conf = '/etc/strongswan.d/charon.conf'
+charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
+charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf'
+interface_conf = '/etc/strongswan.d/interfaces_use.conf'
+swanctl_conf = f'{swanctl_dir}/swanctl.conf'
default_install_routes = 'yes'
@@ -110,6 +111,12 @@ def get_config(config=None):
ipsec['remote_access']['connection'][rw] = dict_merge(default_values,
ipsec['remote_access']['connection'][rw])
+ if 'remote_access' in ipsec and 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ default_values = defaults(base + ['remote-access', 'radius', 'server'])
+ for server in ipsec['remote_access']['radius']['server']:
+ ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values,
+ ipsec['remote_access']['radius']['server'][server])
+
ipsec['dhcp_no_address'] = {}
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
@@ -263,6 +270,12 @@ def verify(ipsec):
if 'pre_shared_secret' not in ra_conf['authentication']:
raise ConfigError(f"Missing pre-shared-key on {name} remote-access config")
+
+ if 'client_mode' in ra_conf['authentication']:
+ if ra_conf['authentication']['client_mode'] == 'eap-radius':
+ if 'radius' not in ipsec['remote_access'] or 'server' not in ipsec['remote_access']['radius'] or len(ipsec['remote_access']['radius']['server']) == 0:
+ raise ConfigError('RADIUS authentication requires at least one server')
+
if 'pool' in ra_conf:
if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!')
@@ -297,6 +310,10 @@ def verify(ipsec):
if v4_addr_and_exclude or v6_addr_and_exclude:
raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and exclude prefixes!')
+ if 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ for server, server_config in ipsec['remote_access']['radius']['server'].items():
+ if 'key' not in server_config:
+ raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
@@ -449,7 +466,8 @@ def generate(ipsec):
cleanup_pki_files()
if not ipsec:
- for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf, interface_conf, swanctl_conf]:
+ for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf,
+ charon_radius_conf, interface_conf, swanctl_conf]:
if os.path.isfile(config_file):
os.unlink(config_file)
render(charon_conf, 'ipsec/charon.tmpl', {'install_routes': default_install_routes})
@@ -518,6 +536,7 @@ def generate(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(charon_radius_conf, 'ipsec/charon/eap-radius.conf.tmpl', ipsec)
render(interface_conf, 'ipsec/interfaces_use.conf.tmpl', ipsec)
render(swanctl_conf, 'ipsec/swanctl.conf.tmpl', ipsec)