summaryrefslogtreecommitdiff
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
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
-rw-r--r--data/templates/ipsec/swanctl.conf.j223
-rw-r--r--data/templates/ipsec/swanctl/peer.j29
-rw-r--r--data/templates/ipsec/swanctl/remote_access.j29
-rw-r--r--interface-definitions/include/ipsec/childless.xml.i29
-rw-r--r--interface-definitions/include/ipsec/ppk.xml.i24
-rw-r--r--interface-definitions/vpn_ipsec.xml.in46
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py134
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py59
-rwxr-xr-xsrc/op_mode/ipsec.py44
9 files changed, 373 insertions, 4 deletions
diff --git a/data/templates/ipsec/swanctl.conf.j2 b/data/templates/ipsec/swanctl.conf.j2
index e4e0b373d..6e143d5be 100644
--- a/data/templates/ipsec/swanctl.conf.j2
+++ b/data/templates/ipsec/swanctl.conf.j2
@@ -91,6 +91,8 @@ secrets {
{% endif %}
{% if psk_config.secret_type is vyos_defined('base64') %}
secret = 0s{{ psk_config.secret }}
+{% elif psk_config.secret_type is vyos_defined('hex') %}
+ secret = 0x{{ psk_config.secret }}
{% elif psk_config.secret_type is vyos_defined('plaintext') %}
secret = "{{ psk_config.secret }}"
{% endif %}
@@ -98,6 +100,27 @@ secrets {
{% endfor %}
{% endif %}
+{% if authentication.ppk is vyos_defined %}
+{% for ppk, ppk_config in authentication.ppk.items() %}
+ ppk-{{ ppk }} {
+{% if ppk_config.id is vyos_defined %}
+ # ID's from auth ppk <tag> id xxx
+{% for id in ppk_config.id %}
+{% set gen_uuid = '' | generate_uuid4 %}
+ id-{{ gen_uuid }} = "{{ id }}"
+{% endfor %}
+{% endif %}
+{% if ppk_config.secret_type is vyos_defined('base64') %}
+ secret = 0s{{ ppk_config.secret }}
+{% elif ppk_config.secret_type is vyos_defined('hex') %}
+ secret = 0x{{ ppk_config.secret }}
+{% elif ppk_config.secret_type is vyos_defined('plaintext') %}
+ secret = "{{ ppk_config.secret }}"
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
+
{% if remote_access.connection is vyos_defined %}
{% for ra, ra_conf in remote_access.connection.items() if ra_conf.disable is not vyos_defined %}
{% if ra_conf.authentication.server_mode is vyos_defined('pre-shared-secret') %}
diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2
index 15b5f5956..24d037fe1 100644
--- a/data/templates/ipsec/swanctl/peer.j2
+++ b/data/templates/ipsec/swanctl/peer.j2
@@ -3,6 +3,15 @@
{# peer needs to reference the global IKE configuration for certain values #}
{% set ike = ike_group[peer_conf.ike_group] %}
{{ name }} {
+{% if peer_conf.authentication.ppk.id is vyos_defined %}
+ ppk_id = {{ peer_conf.authentication.ppk.id }}
+{% endif %}
+{% if peer_conf.authentication.ppk.required is vyos_defined %}
+ ppk_required = yes
+{% endif %}
+{% if peer_conf.childless is vyos_defined %}
+ childless = {{ peer_conf.childless }}
+{% endif %}
proposals = {{ ike | get_esp_ike_cipher | join(',') }}
version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
{% if peer_conf.virtual_address is vyos_defined %}
diff --git a/data/templates/ipsec/swanctl/remote_access.j2 b/data/templates/ipsec/swanctl/remote_access.j2
index d06b3d74b..77731e827 100644
--- a/data/templates/ipsec/swanctl/remote_access.j2
+++ b/data/templates/ipsec/swanctl/remote_access.j2
@@ -3,6 +3,15 @@
{% set ike = ike_group[rw_conf.ike_group] %}
{% set esp = esp_group[rw_conf.esp_group] %}
ra-{{ name }} {
+{% if rw_conf.authentication.ppk.id is vyos_defined %}
+ ppk_id = {{ rw_conf.authentication.ppk.id }}
+{% endif %}
+{% if rw_conf.authentication.ppk.required is vyos_defined %}
+ ppk_required = yes
+{% endif %}
+{% if rw_conf.childless is vyos_defined %}
+ childless = {{ rw_conf.childless }}
+{% endif %}
remote_addrs = %any
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' }}
proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
diff --git a/interface-definitions/include/ipsec/childless.xml.i b/interface-definitions/include/ipsec/childless.xml.i
new file mode 100644
index 000000000..76ba8d548
--- /dev/null
+++ b/interface-definitions/include/ipsec/childless.xml.i
@@ -0,0 +1,29 @@
+<!-- include start from ipsec/childless.xml.i -->
+<leafNode name="childless">
+ <properties>
+ <help>Enable support for childless IKE SA initiation</help>
+ <completionHelp>
+ <list>allow prefer force never</list>
+ </completionHelp>
+ <valueHelp>
+ <format>allow</format>
+ <description>Accept childless IKE SA in responder mode. Create regular IKE SA in initiator mode</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prefer</format>
+ <description>In both responder and initiator modes, accept and create childless IKE SA correspondingly</description>
+ </valueHelp>
+ <valueHelp>
+ <format>force</format>
+ <description>Require the use of childless IKE SA in both responder and initiator modes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>never</format>
+ <description>Disable support for childless IKE SAs when acting as a responder</description>
+ </valueHelp>
+ <constraint>
+ <regex>(allow|prefer|force|never)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/ipsec/ppk.xml.i b/interface-definitions/include/ipsec/ppk.xml.i
new file mode 100644
index 000000000..bd420766c
--- /dev/null
+++ b/interface-definitions/include/ipsec/ppk.xml.i
@@ -0,0 +1,24 @@
+<!-- include start from ipsec/ppk.xml.i -->
+<node name="ppk">
+ <properties>
+ <help>Post-quantum preshared key</help>
+ </properties>
+ <children>
+ <leafNode name="id">
+ <properties>
+ <help>Post-quantum preshared key for this connection</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>ID used for PPK</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="required">
+ <properties>
+ <help>Require a valid PPK for connection to establish</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index a2721e2a1..ffdfeaedf 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -45,10 +45,48 @@
<properties>
<help>Secret type</help>
<completionHelp>
- <list>base64 plaintext</list>
+ <list>base64 hex plaintext</list>
</completionHelp>
<constraint>
- <regex>(base64|plaintext)</regex>
+ <regex>(base64|hex|plaintext)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>plaintext</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="ppk">
+ <properties>
+ <help>Post-quantum preshared key name</help>
+ </properties>
+ <children>
+ <leafNode name="id">
+ <properties>
+ <help>ID for PPK</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>ID used for PPK</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>Post-quantum preshared secret key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Post-quantum preshared secret key</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="secret-type">
+ <properties>
+ <help>Secret type</help>
+ <completionHelp>
+ <list>base64 hex plaintext</list>
+ </completionHelp>
+ <constraint>
+ <regex>(base64|hex|plaintext)</regex>
</constraint>
</properties>
<defaultValue>plaintext</defaultValue>
@@ -896,9 +934,11 @@
</properties>
<defaultValue>x509</defaultValue>
</leafNode>
+ #include <include/ipsec/ppk.xml.i>
#include <include/ipsec/authentication-pre-shared-secret.xml.i>
</children>
</node>
+ #include <include/ipsec/childless.xml.i>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
#include <include/ipsec/esp-group.xml.i>
@@ -1113,6 +1153,7 @@
</properties>
<children>
#include <include/ipsec/authentication-id.xml.i>
+ #include <include/ipsec/ppk.xml.i>
#include <include/ipsec/authentication-rsa.xml.i>
#include <include/ipsec/authentication-x509.xml.i>
<leafNode name="mode">
@@ -1156,6 +1197,7 @@
</leafNode>
</children>
</node>
+ #include <include/ipsec/childless.xml.i>
<leafNode name="connection-type">
<properties>
<help>Connection type</help>
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 9461cd183..a15532a96 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -47,6 +47,7 @@ vif = '100'
esp_group = 'MyESPGroup'
ike_group = 'MyIKEGroup'
secret = 'MYSECRETKEY'
+ppk_secret_hex = '55c2ebca1bada7ac0e4e1390a8dbb563cefea0c7bd59f4f2c86a627f5927fb90'
PROCESS_NAME = 'charon-systemd'
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}'
@@ -668,6 +669,139 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
for line in swanctl_conf_lines:
self.assertIn(line, swanctl_conf)
+ def test_site_to_site_nist_800_77_cnsa_1_with_ppk(self):
+ # Setup IKE group
+ self.cli_set(base_path + ['ike-group', 'cnsa1-ike', 'key-exchange', 'ikev2'])
+ self.cli_set(base_path + ['ike-group', 'cnsa1-ike', 'lifetime', '86400'])
+ self.cli_set(
+ base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'dh-group', '20']
+ )
+ self.cli_set(
+ base_path
+ + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'encryption', 'aes256gcm128']
+ )
+ self.cli_set(
+ base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'hash', 'sha384']
+ )
+ self.cli_set(
+ base_path + ['ike-group', 'cnsa1-ike', 'proposal', '10', 'prf', 'prfsha384']
+ )
+
+ # Setup ESP group
+ self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'lifetime', '28800'])
+ self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'mode', 'tunnel'])
+ self.cli_set(base_path + ['esp-group', 'cnsa1-esp', 'pfs', 'dh-group20'])
+ self.cli_set(
+ base_path
+ + ['esp-group', 'cnsa1-esp', 'proposal', '10', 'encryption', 'aes256gcm128']
+ )
+ self.cli_set(
+ base_path + ['esp-group', 'cnsa1-esp', 'proposal', '10', 'hash', 'sha384']
+ )
+
+ local_address = '192.0.2.10'
+
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(
+ base_path + ['authentication', 'psk', connection_name, 'id', local_id]
+ )
+ self.cli_set(
+ base_path + ['authentication', 'psk', connection_name, 'id', remote_id]
+ )
+ self.cli_set(
+ base_path + ['authentication', 'psk', connection_name, 'id', local_address]
+ )
+ self.cli_set(
+ base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]
+ )
+ self.cli_set(
+ base_path + ['authentication', 'psk', connection_name, 'secret', secret]
+ )
+
+ # vpn ipsec auth ppk <tag> id <name>
+ self.cli_set(
+ base_path + ['authentication', 'ppk', connection_name, 'id', 'ppk-test']
+ )
+ self.cli_set(
+ base_path
+ + ['authentication', 'ppk', connection_name, 'secret', ppk_secret_hex]
+ )
+ self.cli_set(
+ base_path + ['authentication', 'ppk', connection_name, 'secret-type', 'hex']
+ )
+
+ # Site to site
+ peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
+
+ self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
+
+ # Require use of valid PPK
+ self.cli_set(peer_base_path + ['authentication', 'ppk', 'id', 'ppk-test'])
+ self.cli_set(peer_base_path + ['authentication', 'ppk', 'required'])
+
+ # Set childless IKE_INIT to prefer
+ self.cli_set(peer_base_path + ['childless', 'prefer'])
+
+ self.cli_set(peer_base_path + ['default-esp-group', 'cnsa1-esp'])
+ self.cli_set(peer_base_path + ['ike-group', 'cnsa1-ike'])
+ self.cli_set(peer_base_path + ['local-address', local_address])
+
+ self.cli_set(peer_base_path + ['remote-address', peer_ip])
+ self.cli_set(
+ peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']
+ )
+ self.cli_set(
+ peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']
+ )
+
+ self.cli_commit()
+
+ # Verify strongSwan configuration
+ swanctl_conf = read_file(swanctl_file)
+ swanctl_conf_lines = [
+ f'ppk_id = ppk-test',
+ f'ppk_required = yes',
+ f'childless = prefer',
+ f'version = 2',
+ f'auth = psk',
+ f'rekey_time = 86400s',
+ f'proposals = aes256gcm128-sha384-prfsha384-ecp384',
+ f'esp_proposals = aes256gcm128-sha384-ecp384',
+ f'life_time = 28800s', # default value
+ f'local_addrs = {local_address} # dhcp:no',
+ f'remote_addrs = {peer_ip}',
+ f'mode = tunnel',
+ f'{connection_name}-tunnel-1',
+ f'local_ts = 172.16.10.0/24',
+ f'remote_ts = 172.17.10.0/24',
+ f'mode = tunnel',
+ f'replay_window = 32',
+ ]
+ for line in swanctl_conf_lines:
+ self.assertIn(line, swanctl_conf)
+
+ # if dpd is not specified it should not be enabled (see T6599)
+ swanctl_unexpected_lines = [
+ 'dpd_timeout',
+ 'dpd_delay',
+ ]
+
+ for unexpected_line in swanctl_unexpected_lines:
+ self.assertNotIn(unexpected_line, swanctl_conf)
+
+ swanctl_secrets_lines = [
+ f'id-{regex_uuid4} = "{local_id}"',
+ f'id-{regex_uuid4} = "{remote_id}"',
+ f'id-{regex_uuid4} = "{local_address}"',
+ f'id-{regex_uuid4} = "{peer_ip}"',
+ f'secret = "{secret}"',
+ f'ppk-{connection_name}',
+ f'id-{regex_uuid4} = "ppk-test"',
+ f'secret = 0x{ppk_secret_hex}',
+ ]
+ for line in swanctl_secrets_lines:
+ self.assertRegex(swanctl_conf, fr'{line}')
+
def test_dmvpn(self):
ike_lifetime = '3600'
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