diff options
author | Christian Poessinger <christian@poessinger.com> | 2021-07-25 00:13:32 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2021-07-25 00:15:08 +0200 |
commit | 794fa2206659457ba45c6f476ba8b162460cdaad (patch) | |
tree | 5e61ed248198e590586933e7a6b779bf9eb6ec07 | |
parent | 937f1e43ebb9b844b9b73584c3571a18d56bf529 (diff) | |
download | vyos-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-- | Makefile | 5 | ||||
-rw-r--r-- | data/templates/ipsec/charon/eap-radius.conf.tmpl | 115 | ||||
-rw-r--r-- | interface-definitions/vpn_ipsec.xml.in | 23 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 35 |
4 files changed, 165 insertions, 13 deletions
@@ -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) |