Skip to content

Commit bceaa75

Browse files
committed
T8136: IPSEC PPK Support
1 parent 0a61967 commit bceaa75

File tree

9 files changed

+337
-4
lines changed

9 files changed

+337
-4
lines changed

data/templates/ipsec/swanctl.conf.j2

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,36 @@ secrets {
9191
{% endif %}
9292
{% if psk_config.secret_type is vyos_defined('base64') %}
9393
secret = 0s{{ psk_config.secret }}
94+
{% elif psk_config.secret_type is vyos_defined('hex') %}
95+
secret = 0x{{ psk_config.secret }}
9496
{% elif psk_config.secret_type is vyos_defined('plaintext') %}
9597
secret = "{{ psk_config.secret }}"
9698
{% endif %}
9799
}
98100
{% endfor %}
99101
{% endif %}
100102

103+
{% if authentication.ppk is vyos_defined %}
104+
{% for ppk, ppk_config in authentication.ppk.items() %}
105+
ppk-{{ ppk }} {
106+
{% if ppk_config.id is vyos_defined %}
107+
# ID's from auth ppk <tag> id xxx
108+
{% for id in ppk_config.id %}
109+
{% set gen_uuid = '' | generate_uuid4 %}
110+
id-{{ gen_uuid }} = "{{ id }}"
111+
{% endfor %}
112+
{% endif %}
113+
{% if ppk_config.secret_type is vyos_defined('base64') %}
114+
secret = 0s{{ ppk_config.secret }}
115+
{% elif ppk_config.secret_type is vyos_defined('hex') %}
116+
secret = 0x{{ ppk_config.secret }}
117+
{% elif ppk_config.secret_type is vyos_defined('plaintext') %}
118+
secret = "{{ ppk_config.secret }}"
119+
{% endif %}
120+
}
121+
{% endfor %}
122+
{% endif %}
123+
101124
{% if remote_access.connection is vyos_defined %}
102125
{% for ra, ra_conf in remote_access.connection.items() if ra_conf.disable is not vyos_defined %}
103126
{% if ra_conf.authentication.server_mode is vyos_defined('pre-shared-secret') %}

data/templates/ipsec/swanctl/peer.j2

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
{# peer needs to reference the global IKE configuration for certain values #}
44
{% set ike = ike_group[peer_conf.ike_group] %}
55
{{ name }} {
6+
{% if peer_conf.authentication.ppk.id is vyos_defined %}
7+
ppk_id = {{ peer_conf.authentication.ppk.id }}
8+
{% endif %}
9+
{% if peer_conf.authentication.ppk.required is vyos_defined %}
10+
ppk_required = yes
11+
{% endif %}
12+
{% if peer_conf.childless is vyos_defined %}
13+
childless = {{ peer_conf.childless }}
14+
{% endif %}
615
proposals = {{ ike | get_esp_ike_cipher | join(',') }}
716
version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
817
{% if peer_conf.virtual_address is vyos_defined %}

data/templates/ipsec/swanctl/remote_access.j2

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
{% set ike = ike_group[rw_conf.ike_group] %}
44
{% set esp = esp_group[rw_conf.esp_group] %}
55
ra-{{ name }} {
6+
{% if rw_conf.authentication.ppk.id is vyos_defined %}
7+
ppk_id = {{ rw_conf.authentication.ppk.id }}
8+
{% endif %}
9+
{% if rw_conf.authentication.ppk.required is vyos_defined %}
10+
ppk_required = yes
11+
{% endif %}
12+
{% if rw_conf.childless is vyos_defined %}
13+
childless = {{ rw_conf.childless }}
14+
{% endif %}
615
remote_addrs = %any
716
local_addrs = {{ rw_conf.local_address if rw_conf.local_address is not vyos_defined('any') else '%any' }} # dhcp:{{ rw_conf.dhcp_interface if rw_conf.dhcp_interface is vyos_defined else 'no' }}
817
proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!-- include start from ipsec/childless.xml.i -->
2+
<leafNode name="childless">
3+
<properties>
4+
<help>Enable support for childless IKE SA initiation</help>
5+
<completionHelp>
6+
<list>allow prefer force never</list>
7+
</completionHelp>
8+
<valueHelp>
9+
<format>allow</format>
10+
<description>Accept childless IKE SA in responder mode. Create regular IKE SA in initiator mode</description>
11+
</valueHelp>
12+
<valueHelp>
13+
<format>prefer</format>
14+
<description>In both responder and initiator modes, accept and create childless IKE SA correspondingly</description>
15+
</valueHelp>
16+
<valueHelp>
17+
<format>force</format>
18+
<description>Require the use of childless IKE SA in both responder and initiator modes</description>
19+
</valueHelp>
20+
<valueHelp>
21+
<format>never</format>
22+
<description>Disable support for childless IKE SAs when acting as a responder</description>
23+
</valueHelp>
24+
<constraint>
25+
<regex>(allow|prefer|force|never)</regex>
26+
</constraint>
27+
</properties>
28+
</leafNode>
29+
<!-- include end -->
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!-- include start from ipsec/ppk.xml.i -->
2+
<node name="ppk">
3+
<properties>
4+
<help>Post-quantum preshared key</help>
5+
</properties>
6+
<children>
7+
<leafNode name="id">
8+
<properties>
9+
<help>Post-quantum preshared key for this connection</help>
10+
<valueHelp>
11+
<format>txt</format>
12+
<description>ID used for PPK</description>
13+
</valueHelp>
14+
</properties>
15+
</leafNode>
16+
<leafNode name="required">
17+
<properties>
18+
<help>Require a valid PPK for connection to establish</help>
19+
<valueless/>
20+
</properties>
21+
</leafNode>
22+
</children>
23+
</node>
24+
<!-- include end -->

interface-definitions/vpn_ipsec.xml.in

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,48 @@
4545
<properties>
4646
<help>Secret type</help>
4747
<completionHelp>
48-
<list>base64 plaintext</list>
48+
<list>base64 hex plaintext</list>
4949
</completionHelp>
5050
<constraint>
51-
<regex>(base64|plaintext)</regex>
51+
<regex>(base64|hex|plaintext)</regex>
52+
</constraint>
53+
</properties>
54+
<defaultValue>plaintext</defaultValue>
55+
</leafNode>
56+
</children>
57+
</tagNode>
58+
<tagNode name="ppk">
59+
<properties>
60+
<help>Post-quantum preshared key name</help>
61+
</properties>
62+
<children>
63+
<leafNode name="id">
64+
<properties>
65+
<help>ID for PPK</help>
66+
<valueHelp>
67+
<format>txt</format>
68+
<description>ID used for PPK</description>
69+
</valueHelp>
70+
<multi/>
71+
</properties>
72+
</leafNode>
73+
<leafNode name="secret">
74+
<properties>
75+
<help>Post-quantum preshared secret key</help>
76+
<valueHelp>
77+
<format>txt</format>
78+
<description>Post-quantum preshared secret key</description>
79+
</valueHelp>
80+
</properties>
81+
</leafNode>
82+
<leafNode name="secret-type">
83+
<properties>
84+
<help>Secret type</help>
85+
<completionHelp>
86+
<list>base64 hex plaintext</list>
87+
</completionHelp>
88+
<constraint>
89+
<regex>(base64|hex|plaintext)</regex>
5290
</constraint>
5391
</properties>
5492
<defaultValue>plaintext</defaultValue>
@@ -896,9 +934,11 @@
896934
</properties>
897935
<defaultValue>x509</defaultValue>
898936
</leafNode>
937+
#include <include/ipsec/ppk.xml.i>
899938
#include <include/ipsec/authentication-pre-shared-secret.xml.i>
900939
</children>
901940
</node>
941+
#include <include/ipsec/childless.xml.i>
902942
#include <include/generic-description.xml.i>
903943
#include <include/generic-disable-node.xml.i>
904944
#include <include/ipsec/esp-group.xml.i>
@@ -1113,6 +1153,7 @@
11131153
</properties>
11141154
<children>
11151155
#include <include/ipsec/authentication-id.xml.i>
1156+
#include <include/ipsec/ppk.xml.i>
11161157
#include <include/ipsec/authentication-rsa.xml.i>
11171158
#include <include/ipsec/authentication-x509.xml.i>
11181159
<leafNode name="mode">
@@ -1156,6 +1197,7 @@
11561197
</leafNode>
11571198
</children>
11581199
</node>
1200+
#include <include/ipsec/childless.xml.i>
11591201
<leafNode name="connection-type">
11601202
<properties>
11611203
<help>Connection type</help>

smoketest/scripts/cli/test_vpn_ipsec.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
esp_group = 'MyESPGroup'
4848
ike_group = 'MyIKEGroup'
4949
secret = 'MYSECRETKEY'
50+
ppk_secret_hex = '55c2ebca1bada7ac0e4e1390a8dbb563cefea0c7bd59f4f2c86a627f5927fb90'
5051
PROCESS_NAME = 'charon-systemd'
5152
regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}'
5253

@@ -668,6 +669,139 @@ def test_site_to_site_vti_ts_afi(self):
668669
for line in swanctl_conf_lines:
669670
self.assertIn(line, swanctl_conf)
670671

672+
def test_site_to_site_nist_800_77_cnsa_1_with_ppk(self):
673+
# Setup IKE group
674+
self.cli_set(base_path + ['ike-group', 'cnsa1-ike', 'key-exchange', 'ikev2'])
675+
self.cli_set(base_path + ['ike-group', 'cnsa1-ike', 'lifetime', '86400'])
676+
self.cli_set(
677+
base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'dh-group', '20']
678+
)
679+
self.cli_set(
680+
base_path
681+
+ ['ike-group', 'cnsa1-ike', 'proposal', '10', 'encryption', 'aes256gcm128']
682+
)
683+
self.cli_set(
684+
base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'hash', 'sha384']
685+
)
686+
self.cli_set(
687+
base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'prf', 'prfsha384']
688+
)
689+
690+
# Setup ESP group
691+
self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'lifetime', '28800'])
692+
self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'mode', 'tunnel'])
693+
self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'pfs', 'dh-group20'])
694+
self.cli_set(
695+
base_path
696+
+ ['esp-group', 'cnsa1-esp', 'proposal', '10', 'encryption', 'aes256gcm128']
697+
)
698+
self.cli_set(
699+
base_path + ['esp-group', 'cnsa1-esp', 'proposal', '10', 'hash', 'sha384']
700+
)
701+
702+
local_address = '192.0.2.10'
703+
704+
# vpn ipsec auth psk <tag> id <x.x.x.x>
705+
self.cli_set(
706+
base_path + ['authentication', 'psk', connection_name, 'id', local_id]
707+
)
708+
self.cli_set(
709+
base_path + ['authentication', 'psk', connection_name, 'id', remote_id]
710+
)
711+
self.cli_set(
712+
base_path + ['authentication', 'psk', connection_name, 'id', local_address]
713+
)
714+
self.cli_set(
715+
base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]
716+
)
717+
self.cli_set(
718+
base_path + ['authentication', 'psk', connection_name, 'secret', secret]
719+
)
720+
721+
# vpn ipsec auth ppk <tag> id <name>
722+
self.cli_set(
723+
base_path + ['authentication', 'ppk', connection_name, 'id', 'ppk-test']
724+
)
725+
self.cli_set(
726+
base_path
727+
+ ['authentication', 'ppk', connection_name, 'secret', ppk_secret_hex]
728+
)
729+
self.cli_set(
730+
base_path + ['authentication', 'ppk', connection_name, 'secret-type', 'hex']
731+
)
732+
733+
# Site to site
734+
peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
735+
736+
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
737+
738+
# Require use of valid PPK
739+
self.cli_set(peer_base_path + ['authentication', 'ppk', 'id', 'ppk-test'])
740+
self.cli_set(peer_base_path + ['authentication', 'ppk', 'required'])
741+
742+
# Set childless IKE_INIT to prefer
743+
self.cli_set(peer_base_path + ['childless', 'prefer'])
744+
745+
self.cli_set(peer_base_path + ['default-esp-group', 'cnsa1-esp'])
746+
self.cli_set(peer_base_path + ['ike-group', 'cnsa1-ike'])
747+
self.cli_set(peer_base_path + ['local-address', local_address])
748+
749+
self.cli_set(peer_base_path + ['remote-address', peer_ip])
750+
self.cli_set(
751+
peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']
752+
)
753+
self.cli_set(
754+
peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']
755+
)
756+
757+
self.cli_commit()
758+
759+
# Verify strongSwan configuration
760+
swanctl_conf = read_file(swanctl_file)
761+
swanctl_conf_lines = [
762+
f'ppk_id = ppk-test',
763+
f'ppk_required = yes',
764+
f'childless = prefer',
765+
f'version = 2',
766+
f'auth = psk',
767+
f'rekey_time = 86400s',
768+
f'proposals = aes256gcm128-sha384-prfsha384-ecp384',
769+
f'esp_proposals = aes256gcm128-sha384-ecp384',
770+
f'life_time = 28800s', # default value
771+
f'local_addrs = {local_address} # dhcp:no',
772+
f'remote_addrs = {peer_ip}',
773+
f'mode = tunnel',
774+
f'{connection_name}-tunnel-1',
775+
f'local_ts = 172.16.10.0/24',
776+
f'remote_ts = 172.17.10.0/24',
777+
f'mode = tunnel',
778+
f'replay_window = 32',
779+
]
780+
for line in swanctl_conf_lines:
781+
self.assertIn(line, swanctl_conf)
782+
783+
# if dpd is not specified it should not be enabled (see T6599)
784+
swanctl_unexpected_lines = [
785+
'dpd_timeout',
786+
'dpd_delay',
787+
]
788+
789+
for unexpected_line in swanctl_unexpected_lines:
790+
self.assertNotIn(unexpected_line, swanctl_conf)
791+
792+
swanctl_secrets_lines = [
793+
f'id-{regex_uuid4} = "{local_id}"',
794+
f'id-{regex_uuid4} = "{remote_id}"',
795+
f'id-{regex_uuid4} = "{local_address}"',
796+
f'id-{regex_uuid4} = "{peer_ip}"',
797+
f'secret = "{secret}"',
798+
f'ppk-{connection_name}',
799+
f'id-{regex_uuid4} = "ppk-test"',
800+
f'secret = 0x{ppk_secret_hex}',
801+
]
802+
for line in swanctl_secrets_lines:
803+
self.assertRegex(swanctl_conf, fr'{line}')
804+
671805

672806
def test_dmvpn(self):
673807
ike_lifetime = '3600'

0 commit comments

Comments
 (0)