Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T5873: ipsec remote access VPN: support VTI interfaces. #3221

Draft
wants to merge 1 commit into
base: current
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/templates/ipsec/swanctl.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pools {
{{ pool }} {
{% if pool_config.prefix is vyos_defined %}
addrs = {{ pool_config.prefix }}
{% elif pool_config.range is vyos_defined %}
addrs = {{ pool_config.range.start }}-{{ pool_config.range.stop }}
{% endif %}
{% if pool_config.name_server is vyos_defined %}
dns = {{ pool_config.name_server | join(',') }}
Expand Down
7 changes: 7 additions & 0 deletions data/templates/ipsec/swanctl/remote_access.j2
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
{% set local_port = rw_conf.local.port if rw_conf.local.port is vyos_defined else '' %}
{% set local_suffix = '[%any/{1}]'.format(local_port) if local_port else '' %}
local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }}
{% if rw_conf.bind is vyos_defined %}
{# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #}
{# Thus we simply shift the key by one to also support a vti0 interface #}
{% set if_id = rw_conf.bind | replace('vti', '') | int + 1 %}
if_id_in = {{ if_id }}
if_id_out = {{ if_id }}
{% endif %}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions interface-definitions/include/ipsec/bind.xml.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- include start from ipsec/bind.xml.i -->
<leafNode name="bind">
<properties>
<help>VTI tunnel interface associated with this configuration</help>
<completionHelp>
<path>interfaces vti</path>
</completionHelp>
</properties>
</leafNode>
<!-- include end -->
49 changes: 41 additions & 8 deletions interface-definitions/vpn_ipsec.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,7 @@
#include <include/dhcp-interface.xml.i>
#include <include/ipsec/local-traffic-selector.xml.i>
#include <include/ipsec/replay-window.xml.i>
#include <include/ipsec/bind.xml.i>
<leafNode name="timeout">
<properties>
<help>Timeout to close connection if no data is transmitted</help>
Expand Down Expand Up @@ -952,6 +953,45 @@
</constraint>
</properties>
</leafNode>
<node name="range">
<properties>
<help>Local IPv4 or IPv6 pool range</help>
</properties>
<children>
<leafNode name="start">
<properties>
<help>First IP address for local pool range</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 start address of pool</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
<description>IPv6 start address of pool</description>
</valueHelp>
<constraint>
<validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
<leafNode name="stop">
<properties>
<help>Last IP address for local pool range</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 end address of pool</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
<description>IPv6 end address of pool</description>
</valueHelp>
<constraint>
<validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
#include <include/name-server-ipv4-ipv6.xml.i>
</children>
</tagNode>
Expand Down Expand Up @@ -1175,14 +1215,7 @@
<help>Virtual tunnel interface</help>
</properties>
<children>
<leafNode name="bind">
<properties>
<help>VTI tunnel interface associated with this configuration</help>
<completionHelp>
<path>interfaces vti</path>
</completionHelp>
</properties>
</leafNode>
#include <include/ipsec/bind.xml.i>
#include <include/ipsec/esp-group.xml.i>
</children>
</node>
Expand Down
258 changes: 258 additions & 0 deletions smoketest/scripts/cli/test_vpn_ipsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,5 +960,263 @@ def test_remote_access_dhcp_fail_handling(self):

self.tearDownPKI()

def test_remote_access_pool_range(self):
# Same as test_remote_access but using an IP pool range instead of prefix
self.setupPKI()

ike_group = 'IKE-RW'
esp_group = 'ESP-RW'

conn_name = 'vyos-rw'
local_address = '192.0.2.1'
ip_pool_name = 'ra-rw-ipv4'
username = 'vyos'
password = 'secret'
ike_lifetime = '7200'
eap_lifetime = '3600'
local_id = 'ipsec.vyos.net'

name_servers = ['172.16.254.100', '172.16.254.101']
range_start = '172.16.250.2'
range_stop = '172.16.250.254'

# IKE
self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])

# ESP
self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
# verify() - CA cert required for x509 auth
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])

for ns in name_servers:
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'start', range_start])
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'stop', range_stop])

self.cli_commit()

# verify applied configuration
swanctl_conf = read_file(swanctl_file)
swanctl_lines = [
f'{conn_name}',
f'remote_addrs = %any',
f'local_addrs = {local_address}',
f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
f'version = 2',
f'send_certreq = no',
f'rekey_time = {ike_lifetime}s',
f'keyingtries = 0',
f'pools = {ip_pool_name}',
f'id = "{local_id}"',
f'auth = pubkey',
f'certs = peer1.pem',
f'auth = eap-mschapv2',
f'eap_id = %any',
f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
f'rekey_time = {eap_lifetime}s',
f'rand_time = 540s',
f'dpd_action = clear',
f'replay_window = 32',
f'inactivity = 28800',
f'local_ts = 0.0.0.0/0,::/0',
]
for line in swanctl_lines:
self.assertIn(line, swanctl_conf)

swanctl_secrets_lines = [
f'eap-{conn_name}-{username}',
f'secret = "{password}"',
f'id-{conn_name}-{username} = "{username}"',
]
for line in swanctl_secrets_lines:
self.assertIn(line, swanctl_conf)

swanctl_pool_lines = [
f'{ip_pool_name}',
f'addrs = {range_start}-{range_stop}',
f'dns = {",".join(name_servers)}',
]
for line in swanctl_pool_lines:
self.assertIn(line, swanctl_conf)

# Check Root CA, Intermediate CA and Peer cert/key pair is present
self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem')))
self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))

self.tearDownPKI()

def test_remote_access_vti(self):
# Set up and use a VTI interface for the remote access VPN
self.setupPKI()

ike_group = 'IKE-RW'
esp_group = 'ESP-RW'

conn_name = 'vyos-rw'
local_address = '192.0.2.1'
vti = 'vti10'
ip_pool_name = 'ra-rw-ipv4'
username = 'vyos'
password = 'secret'
ike_lifetime = '7200'
eap_lifetime = '3600'
local_id = 'ipsec.vyos.net'

name_servers = ['10.1.1.1']
range_start = '10.1.1.10'
range_stop = '10.1.1.254'

# VTI interface
self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24'])

# IKE
self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])

# ESP
self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
# verify() - CA cert required for x509 auth
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])

self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'bind', vti])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])

for ns in name_servers:
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'start', range_start])
self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'stop', range_stop])

self.cli_commit()

# verify applied configuration
swanctl_conf = read_file(swanctl_file)

if_id = vti.lstrip('vti')
# The key defaults to 0 and will match any policies which similarly do
# not have a lookup key configuration - thus we shift the key by one
# to also support a vti0 interface
if_id = str(int(if_id) +1)

swanctl_lines = [
f'{conn_name}',
f'remote_addrs = %any',
f'local_addrs = {local_address}',
f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
f'version = 2',
f'send_certreq = no',
f'rekey_time = {ike_lifetime}s',
f'keyingtries = 0',
f'pools = {ip_pool_name}',
f'id = "{local_id}"',
f'auth = pubkey',
f'certs = peer1.pem',
f'auth = eap-mschapv2',
f'eap_id = %any',
f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
f'rekey_time = {eap_lifetime}s',
f'rand_time = 540s',
f'dpd_action = clear',
f'replay_window = 32',
f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one
f'if_id_out = {if_id}',
f'inactivity = 28800',
f'local_ts = 0.0.0.0/0,::/0',
]
for line in swanctl_lines:
self.assertIn(line, swanctl_conf)

swanctl_secrets_lines = [
f'eap-{conn_name}-{username}',
f'secret = "{password}"',
f'id-{conn_name}-{username} = "{username}"',
]
for line in swanctl_secrets_lines:
self.assertIn(line, swanctl_conf)

swanctl_pool_lines = [
f'{ip_pool_name}',
f'addrs = {range_start}-{range_stop}',
f'dns = {",".join(name_servers)}',
]
for line in swanctl_pool_lines:
self.assertIn(line, swanctl_conf)

# Check Root CA, Intermediate CA and Peer cert/key pair is present
self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem')))
self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))

self.tearDownPKI()

if __name__ == '__main__':
unittest.main(verbosity=2)