From 899a6bf7955592ec40670944a860a1bee97b432c Mon Sep 17 00:00:00 2001 From: omnom62 <75066712+omnom62@users.noreply.github.com> Date: Fri, 21 Aug 2026 22:12:11 +1000 Subject: T8321: vpn_ipsec modules (#489) Add VPN IPsec module --- plugins/modules/vyos_vpn_ipsec.py | 454 ++++++++++++++++++++++++++++++++++ plugins/modules/vyos_vpn_ipsec_s2s.py | 337 +++++++++++++++++++++++++ 2 files changed, 791 insertions(+) create mode 100644 plugins/modules/vyos_vpn_ipsec.py create mode 100644 plugins/modules/vyos_vpn_ipsec_s2s.py (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_vpn_ipsec.py b/plugins/modules/vyos_vpn_ipsec.py new file mode 100644 index 00000000..9af12ff7 --- /dev/null +++ b/plugins/modules/vyos_vpn_ipsec.py @@ -0,0 +1,454 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2026 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for vyos_vpn_ipsec +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: vyos_vpn_ipsec +short_description: Manages global IPsec (ike-group, esp-group, profile, authentication, options) attributes of VyOS network devices. +description: This module manages global VPN IPsec configuration on VyOS devices + -- IKE groups, ESP groups, PSK/PPK authentication, IPsec profiles, and global + options. Site-to-site peers and IKEv2 remote-access connections are handled by + separate modules. +version_added: 1.0.0 +author: Evgeny Molotkov (@omnom62) +extends_documentation_fragment: + - vyos.vyos.vyos +notes: + - Tested against VyOS 1.4 and 1.5. + - "Source of truth for field types/choices: device node.def templates under /opt/vyatta/share/vyatta-cfg/templates/vpn/ipsec/." +options: + config: + description: IPsec global configuration. + type: dict + suboptions: + ike_group: + description: List of IKE groups. + type: list + elements: dict + suboptions: + name: + description: The name of the IKE group. + type: str + required: true + close_action: + description: Action to take if a child SA is unexpectedly closed. + type: str + choices: [none, trap, start] + dead_peer_detection: + description: Dead Peer Detection (DPD). + type: dict + suboptions: + action: + description: Keep-alive failure action. + type: str + choices: [trap, clear, restart] + interval: + description: Keep-alive interval in seconds. + type: int + timeout: + description: Dead Peer Detection keep-alive timeout (IKEv1 only), in seconds. + type: int + disable_mobike: + description: Disable MOBIKE support (IKEv2 only). + type: bool + ikev2_reauth: + description: Re-authentication of the remote peer during an IKE re-key (IKEv2 only). + type: bool + key_exchange: + description: IKE version. + type: str + choices: [ikev1, ikev2] + lifetime: + description: IKE lifetime in seconds. + type: int + mode: + description: IKEv1 phase 1 mode. + type: str + choices: [main, aggressive] + proposal: + description: List of IKE proposals. + type: list + elements: dict + suboptions: + proposal_id: + description: The proposal identifier. + type: int + dh_group: + description: Diffie-Hellman group. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side, not enumerated here since + the set is version-dependent. + type: int + encryption: + description: Encryption algorithm. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side, not enumerated here since + the set is version-dependent. + type: str + hash: + description: Hash algorithm. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side. + type: str + prf: + description: Pseudo-Random Function. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side. + type: str + esp_group: + description: List of ESP groups. + type: list + elements: dict + suboptions: + name: + description: The name of the ESP group. + type: str + required: true + compression: + description: Enable ESP compression. + type: bool + disable_rekey: + description: Do not locally initiate a re-key of the SA; remote peer must re-key before expiration. + type: bool + life_bytes: + description: Security Association byte count to expire. + type: int + life_packets: + description: Security Association packet count to expire. + type: int + lifetime: + description: Security Association time to expire, in seconds. + type: int + mode: + description: ESP mode. + type: str + choices: [tunnel, transport] + pfs: + description: ESP Perfect Forward Secrecy. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side, not enumerated here since + the set is version-dependent. + type: str + proposal: + description: List of ESP proposals. + type: list + elements: dict + suboptions: + proposal_id: + description: The proposal identifier. + type: int + encryption: + description: Encryption algorithm. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side, not enumerated here since + the set is version-dependent. + type: str + hash: + description: Hash algorithm. See VyOS/strongSwan documentation for the + full set of valid values -- validated device-side. + type: str + authentication: + description: Global pre-shared-key and post-quantum pre-shared-key definitions. + type: dict + suboptions: + psk: + description: List of pre-shared keys. + type: list + elements: dict + suboptions: + name: + description: Pre-shared key name. + type: str + required: true + id: + description: ID(s) for authentication. + type: list + elements: str + dhcp_interface: + description: DHCP interface(s) supplying next-hop IP address. + type: list + elements: str + secret: + description: IKE pre-shared secret key. + type: str + secret_type: + description: Secret encoding type. + type: str + choices: [base64, hex, plaintext] + ppk: + description: List of post-quantum pre-shared keys. + type: list + elements: dict + suboptions: + name: + description: Post-quantum pre-shared key name. + type: str + required: true + id: + description: ID(s) for PPK. + type: list + elements: str + secret: + description: Post-quantum pre-shared secret key. + type: str + secret_type: + description: Secret encoding type. + type: str + choices: [base64, hex, plaintext] + profile: + description: List of VPN IPsec profiles (used for e.g. DMVPN/GRE tunnel binding). + type: list + elements: dict + suboptions: + name: + description: Profile name. + type: str + required: true + authentication: + description: Authentication settings for this profile. + type: dict + suboptions: + mode: + description: Authentication mode. + type: str + choices: [pre-shared-secret] + pre_shared_secret: + description: Pre-shared secret key. + type: str + bind_tunnel: + description: Tunnel interface(s) associated with this profile. + type: list + elements: str + disable: + description: Disable this profile. + type: bool + esp_group: + description: ESP group name to use for this profile. + type: str + ike_group: + description: IKE group name to use for this profile. + type: str + interface: + description: Interface(s) IPsec listens on. If omitted, listens on all interfaces. + type: list + elements: str + log: + description: IPsec logging settings. + type: dict + suboptions: + level: + description: Global IPsec logging level. + type: int + subsystem: + description: Per-subsystem logging levels to enable. + type: list + elements: str + options: + description: Global IPsec options. + type: dict + suboptions: + disable_route_autoinstall: + description: Do not automatically install routes to remote networks. + type: bool + flexvpn: + description: Allow FlexVPN vendor ID payload (IKEv2 only). + type: bool + interface: + description: Single interface for IPsec options scope (distinct from top-level interface list). + type: str + retransmission: + description: IPsec retransmission settings. + type: dict + suboptions: + attempts: + description: Maximum number of retransmissions. + type: int + base: + description: Base of exponential backoff. + type: float + timeout: + description: Timeout in seconds before the first retransmission. + type: int + virtual_ip: + description: Allow install of virtual-ip addresses. + type: bool + disable_uniqreqids: + description: Disable requirement for unique IDs in the Security Database. + type: bool + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VyOS device by + executing the command B(show configuration commands | match "vpn ipsec"). + - The states I(replaced) and I(overridden) have identical behaviour for this module + with respect to named collections (ike_group, esp_group, profile, authentication), + but differ in scope -- see the module description for detail. + - The state I(parsed) reads the configuration from the C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec, + returned in the I(parsed) key within the result. + type: str + state: + description: The state the configuration should be left in. + type: str + choices: [merged, replaced, overridden, deleted, gathered, rendered, parsed] + default: merged +""" + +EXAMPLES = """ +- name: Merge provided configuration with device configuration + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + dh_group: 14 + state: merged + +- name: Replace one named esp-group, leaving all other groups untouched + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes128 + hash: sha256 + state: replaced + +- name: Override the whole configuration -- anything not listed here is removed + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + state: overridden + +- name: Delete one named esp-group, leaving all other groups untouched + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + state: deleted + +- name: Remove all vpn_ipsec configuration + vyos.vyos.vyos_vpn_ipsec: + state: deleted + +- name: Gather current vpn_ipsec configuration + vyos.vyos.vyos_vpn_ipsec: + state: gathered + +- name: Render configuration without touching the device + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + state: rendered + +- name: Parse raw config text into structured facts + vyos.vyos.vyos_vpn_ipsec: + running_config: "{{ lookup('file', './vpn_ipsec.cfg') }}" + state: parsed +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: list + sample: + - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes256 + - set vpn ipsec ike-group IKE-TEST key-exchange ikev2 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes256 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vpn_ipsec.vpn_ipsec import ( + Vpn_ipsecArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vpn_ipsec.vpn_ipsec import ( + Vpn_ipsec, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vpn_ipsecArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Vpn_ipsec(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_vpn_ipsec_s2s.py b/plugins/modules/vyos_vpn_ipsec_s2s.py new file mode 100644 index 00000000..7458381e --- /dev/null +++ b/plugins/modules/vyos_vpn_ipsec_s2s.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2026 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for vyos_vpn_ipsec_s2s +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: vyos_vpn_ipsec_s2s +short_description: Manages IPsec site-to-site VPN peers on VyOS network devices. +description: This module manages VPN IPsec site-to-site peer configuration on VyOS + devices -- policy-based tunnels and route-based (VTI) connections. IKE/ESP groups, + PSK/PPK authentication, and IPsec profiles are managed by the separate vyos_vpn_ipsec + module; peers here reference those by name. +version_added: 1.0.0 +author: Evgeny Molotkov (@omnom62) +extends_documentation_fragment: + - vyos.vyos.vyos +notes: + - Tested against VyOS 1.4 and 1.5. + - "Source of truth: vyos-1x's interface-definitions/vpn_ipsec.xml.in, resolved and + drafted via this collection's fetch_vyos_xml_definition.py / parse_xml_definitions.py + helper scripts, then hand-reviewed." + - "The argspec only requires I(name) on a peer, but VyOS itself enforces + several more requirements at commit time -- confirmed via real device + testing, not visible in the argspec: every peer needs C(authentication), + a real C(remote_address) (not just omitted), a C(local_address) or + C(dhcp_interface), and at least one of C(tunnel) or C(vti). A peer + missing any of these will pass Ansible's own argument validation but + fail the device commit with a specific error naming what's missing." +options: + config: + description: IPsec site-to-site configuration. + type: dict + suboptions: + peer: + description: List of site-to-site peers. + type: list + elements: dict + suboptions: + name: + description: Connection name of the peer. + type: str + required: true + disable: + description: Disable this peer. + type: bool + authentication: + description: Peer authentication settings. + type: dict + suboptions: + local_id: + description: Local ID for peer authentication. + type: str + remote_id: + description: ID for remote authentication. + type: str + mode: + description: Authentication mode. + type: str + choices: [pre-shared-secret, rsa, x509] + use_x509_id: + description: Use certificate common name as ID. + type: bool + ppk: + description: Post-quantum preshared key reference for this peer. + type: dict + suboptions: + id: + description: Post-quantum preshared key ID for this connection. + type: str + required: + description: Require a valid PPK for the connection to establish. + type: bool + rsa: + description: RSA key authentication. + type: dict + suboptions: + local_key: + description: Name of the PKI key-pair with the local private key. + type: str + remote_key: + description: Name of the PKI key-pair with the remote public key. + type: str + passphrase: + description: Local private key passphrase. + type: str + x509: + description: X.509 certificate authentication. + type: dict + suboptions: + certificate: + description: Certificate in PKI configuration. + type: str + passphrase: + description: Private key passphrase. + type: str + ca_certificate: + description: Certificate Authority chain in PKI configuration. + type: list + elements: str + childless: + description: Childless IKE SA initiation support. + type: str + choices: [allow, prefer, force, never] + connection_type: + description: Connection type. + type: str + choices: [initiate, trap, none] + default_esp_group: + description: Default ESP group name for tunnels under this peer that + don't specify their own. + type: str + description: + description: Description. + type: str + dhcp_interface: + description: DHCP interface supplying the next-hop IP address. + type: str + force_udp_encapsulation: + description: Force UDP encapsulation. + type: bool + ike_group: + description: IKE group name. + type: str + ikev2_reauth: + description: Re-authentication of the remote peer during an IKE re-key + (IKEv2 only). + type: str + choices: ["yes", "no", inherit] + local_address: + description: IPv4 or IPv6 address of a local interface to use for the + VPN, or "any". + type: str + remote_address: + description: IPv4 or IPv6 address(es) of the remote peer, or "any". + type: list + elements: str + replay_window: + description: IPsec replay window to configure for this CHILD_SA. + type: int + virtual_address: + description: Initiator-requested virtual address(es) from the peer. + type: list + elements: str + tunnel: + description: Policy-based tunnel definitions for this peer. + type: list + elements: dict + suboptions: + tunnel_id: + description: The tunnel identifier. + type: int + required: true + disable: + description: Disable this tunnel. + type: bool + esp_group: + description: ESP group name for this tunnel (overrides the peer's + default_esp_group). + type: str + protocol: + description: Protocol to match for this tunnel's traffic selector. + type: str + priority: + description: Priority for this IPsec policy (lowest value is most + preferred). + type: int + local: + description: Local traffic selector for this tunnel. + type: dict + suboptions: + port: + description: Local port to match. + type: int + prefix: + description: Local IPv4 or IPv6 prefix(es) to match. + type: list + elements: str + remote: + description: Remote traffic selector for this tunnel. + type: dict + suboptions: + port: + description: Remote port to match. + type: int + prefix: + description: Remote IPv4 or IPv6 prefix(es) to match. + type: list + elements: str + vti: + description: Route-based (VTI) connection settings for this peer. + type: dict + suboptions: + bind: + description: VTI tunnel interface associated with this connection. + type: str + esp_group: + description: ESP group name for this VTI connection. + type: str + traffic_selector: + description: Traffic selector for the VTI connection. + type: dict + suboptions: + local: + description: Local traffic-selector parameters. + type: dict + suboptions: + prefix: + description: Local IPv4 or IPv6 prefix(es). + type: list + elements: str + remote: + description: Remote traffic-selector parameters. + type: dict + suboptions: + prefix: + description: Remote IPv4 or IPv6 prefix(es). + type: list + elements: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VyOS device + by executing the command B(show configuration commands | match "vpn ipsec + site-to-site"). + - The state I(parsed) reads the configuration from the C(running_config) option + and transforms it into Ansible structured data as per the resource module's + argspec, returned in the I(parsed) key within the result. + type: str + state: + description: The state the configuration should be left in. + type: str + choices: [merged, replaced, overridden, deleted, gathered, rendered, parsed] + default: merged +""" + +EXAMPLES = """ +- name: Merge a site-to-site peer + vyos.vyos.vyos_vpn_ipsec_s2s: + config: + peer: + - name: PEER-TEST + ike_group: IKE-TEST + default_esp_group: ESP-TEST + remote_address: + - 203.0.113.1 + state: merged +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: list + sample: + - set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST' + - set vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST' +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST' +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vpn_ipsec_s2s.vpn_ipsec_s2s import ( + Vpn_ipsec_s2sArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vpn_ipsec_s2s.vpn_ipsec_s2s import ( + Vpn_ipsec_s2s, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vpn_ipsec_s2sArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Vpn_ipsec_s2s(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() -- cgit v1.2.3