summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2026-03-04 12:01:50 +0200
committerGitHub <noreply@github.com>2026-03-04 12:01:50 +0200
commit2a4d52b2b8c39f4f77b526ce80686247dd4d0f95 (patch)
treeedcf61a295a71c50d8ca20c22a8bec3378f5e86d /src
parent3030e33b26c1a895179747f2444a0cd79d59f891 (diff)
parent673ec335b885ae1de8391dd8c48a5fe16b282db5 (diff)
downloadvyos-1x-2a4d52b2b8c39f4f77b526ce80686247dd4d0f95.tar.gz
vyos-1x-2a4d52b2b8c39f4f77b526ce80686247dd4d0f95.zip
Merge pull request #4930 from giga1699/T8136
ipsec: T8136: IPSEC PPK support
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py59
-rwxr-xr-xsrc/op_mode/ipsec.py44
2 files changed, 101 insertions, 2 deletions
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index c6492f513..3b5918218 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -54,6 +54,7 @@ from vyos.utils.vti_updown_db import vti_updown_db_exists
from vyos.utils.vti_updown_db import open_vti_updown_db_for_create_or_update
from vyos.utils.vti_updown_db import remove_vti_updown_db
from vyos import ConfigError
+from vyos.base import Warning
from vyos import airbag
airbag.enable()
@@ -261,11 +262,29 @@ def verify(ipsec):
if not ipsec or 'deleted' in ipsec:
return
+ # T8136 PPK support; keep a list of PPK IDs
+ ppk_ids = []
+
if 'authentication' in ipsec:
if 'psk' in ipsec['authentication']:
for psk, psk_config in ipsec['authentication']['psk'].items():
if 'id' not in psk_config or 'secret' not in psk_config:
- raise ConfigError(f'Authentication psk "{psk}" missing "id" or "secret"')
+ raise ConfigError(
+ f'Authentication psk "{psk}" missing "id" or "secret"'
+ )
+ # T8136 PPK Support; Check that PPK has an ID and secret defined, and ID is unique
+ if 'ppk' in ipsec['authentication']:
+ for ppk, ppk_config in ipsec['authentication']['ppk'].items():
+ if 'id' not in ppk_config:
+ raise ConfigError(f'Authentication PPK "{ppk}" missing "id"')
+ if 'secret' not in ppk_config:
+ raise ConfigError(f'Authentication PPK "{ppk}" missing "secret"')
+ for ppk_id in ppk_config['id']:
+ if ppk_id in ppk_ids:
+ raise ConfigError(
+ f'Authentication PPK "{ppk}" has duplicate ID "{ppk_id}" from another PPK. IDs should be unique.'
+ )
+ ppk_ids.append(ppk_id)
if 'interface' in ipsec:
tmp = re.compile(dynamic_interface_pattern)
@@ -445,6 +464,25 @@ def verify(ipsec):
elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']:
raise ConfigError(f'Requested pool "{pool}" does not exist!')
+ # T8136 IPSEC PPK Support
+ # PPKs and Childless only works with IKEv2. Check that ike-group is v2 if either option is enabled. Check that PPK ID was actually defined in authentication. Recommend use of childless when using PPKs if not already configured.
+ if 'ppk' in ra_conf['authentication']:
+ ike = ra_conf['ike_group']
+ if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
+ raise ConfigError(
+ f'Incorrect configuration in IKE group "{ike}": post-quantum pre-shared keys require explicit IKEv2 usage.'
+ )
+ if 'childless' not in ra_conf:
+ Warning(
+ 'It is recommended to use childless IKE SAs when using PPKs'
+ )
+ if 'childless' in ra_conf:
+ ike = ra_conf['ike_group']
+ if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
+ raise ConfigError(
+ f'Incorrect configuration in IKE group "{ike}": childless IKE SAs can only be used with IKEv2.'
+ )
+
if 'pool' in ipsec['remote_access']:
pool_networks = []
for pool, pool_config in ipsec['remote_access']['pool'].items():
@@ -653,6 +691,25 @@ def verify(ipsec):
f'for ESP proposal {proposal} on tunnel {tunnel} for site-to-site peer {peer} with VPP'
)
+ # T8136 IPSEC PPK Support
+ # PPKs and Childless only works with IKEv2. Check that ike-group is v2 if either option is enabled. Check that PPK ID was actually defined in authentication. Recommend use of childless when using PPKs if not already configured.
+ if 'ppk' in peer_conf['authentication']:
+ ike = peer_conf['ike_group']
+ if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
+ raise ConfigError(
+ f'Post-quantum preshared keys must be used with IKEv2! Please configure IKEv2 key-exchange in ike-group "{ike}".'
+ )
+ if 'childless' not in peer_conf:
+ Warning(
+ 'It is recommended to use childless IKE SAs when using PPKs'
+ )
+ if 'childless' in peer_conf:
+ ike = peer_conf['ike_group']
+ if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
+ raise ConfigError(
+ f'Childless IKE SAs be used with IKEv2! Please configure IKEv2 key-exchange in ike-group "{ike}".'
+ )
+
def cleanup_pki_files():
for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH, PUBKEY_PATH]:
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index 4ff035416..9e45f3265 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -229,6 +229,28 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str:
ike_state = 'up'
return ike_state
+def _get_parent_ppk_state(connection_name: str, data: list) -> str:
+ """Get paren PPK state by connection name
+
+ Args:
+ connection_name (str): Connection name
+ data (list): List of current SAs from vici
+
+ Returns:
+ Parent PPK state
+ """
+ ppk_state = 'no'
+ if not data:
+ return ppk_state
+ for sa in data:
+ # check if parent PPK exists
+ for connection, connection_conf in sa.items():
+ if connection_name != connection:
+ continue
+ if 'ppk' in connection_conf and connection_conf['ppk'].lower() == 'yes':
+ ppk_state = 'yes'
+ return ppk_state
+
def _get_child_sa_state(
connection_name: str, tunnel_name: str, data: list, mode: str
@@ -335,6 +357,17 @@ def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
base_list['local_id'] = conn_conf.get('local-1', '').get('id')
base_list['remote_id'] = conn_conf.get('remote-1', '').get('id')
base_list['version'] = conn_conf.get('version', 'IKE')
+ if conn_conf.get('ppk_id'):
+ if conn_conf.get('ppk_required') == 'yes':
+ base_list['ppk'] = 'req/' + _get_parent_ppk_state(
+ connection, list_sas
+ )
+ else:
+ base_list['ppk'] = 'opt/' + _get_parent_ppk_state(
+ connection, list_sas
+ )
+ else:
+ base_list['ppk'] = 'none/' + _get_parent_ppk_state(connection, list_sas)
base_list['children'] = []
children = conn_conf['children']
for tunnel, tun_options in children.items():
@@ -400,6 +433,8 @@ def _get_formatted_output_conections(data):
f'{entry["ike_proposal"]["hash"]}/'
f'{entry["ike_proposal"]["dh"]}'
)
+ ppk = entry['ppk']
+
connections.append(
[
ike_name,
@@ -411,6 +446,7 @@ def _get_formatted_output_conections(data):
local_id,
remote_id,
proposal,
+ ppk,
]
)
for tun in entry['children']:
@@ -428,6 +464,7 @@ def _get_formatted_output_conections(data):
f'{tun["esp_proposal"]["hash"]}/'
f'{tun["esp_proposal"]["dh"]}'
)
+ ppk = '-'
connections.append(
[
tun_name,
@@ -439,6 +476,7 @@ def _get_formatted_output_conections(data):
local_id,
remote_id,
proposal,
+ ppk,
]
)
connection_headers = [
@@ -451,8 +489,12 @@ def _get_formatted_output_conections(data):
'Local id',
'Remote id',
'Proposal',
+ 'PPK',
]
- output = tabulate(connections, connection_headers, numalign='left')
+ output = (
+ 'PPK Codes: none - Not Configured, opt - PPK is Optional, req - PPK is required, no - PPK not negotiated, yes - PPK negotiated\n'
+ + tabulate(connections, connection_headers, numalign='left')
+ )
return output