diff options
| author | omnom62 <omnom62@outlook.com> | 2026-10-05 20:56:05 +1000 |
|---|---|---|
| committer | omnom62 <omnom62@outlook.com> | 2026-10-05 20:56:05 +1000 |
| commit | 8ee802e41374942965b6b93cfb4534725ef45145 (patch) | |
| tree | ef3e5579bd767422caa6fe77cea6a7b5d2d8562c /roles/ipsec_route_based | |
| parent | 748df2bc1d35fa285dd3fe46916e1230408778c7 (diff) | |
| download | vyos.blueprints-main.tar.gz vyos.blueprints-main.zip | |
T9393: new rolesmain
Diffstat (limited to 'roles/ipsec_route_based')
| -rw-r--r-- | roles/ipsec_route_based/defaults/main.yml | 22 | ||||
| -rw-r--r-- | roles/ipsec_route_based/meta/argument_specs.yml | 209 | ||||
| -rw-r--r-- | roles/ipsec_route_based/meta/main.yml | 11 | ||||
| -rw-r--r-- | roles/ipsec_route_based/tasks/main.yml | 113 | ||||
| -rw-r--r-- | roles/ipsec_route_based/tasks/verify.yml | 52 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/bgp_address_family.yml.j2 | 20 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/bgp_global.yml.j2 | 29 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/interface_routes.yml.j2 | 5 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/ospf_interfaces.yml.j2 | 12 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/ospfv2.yml.j2 | 9 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/vpn_ipsec.yml.j2 | 51 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/vpn_ipsec_s2s.yml.j2 | 27 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/vti.yml.j2 | 9 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/vti_cli.j2 | 4 | ||||
| -rw-r--r-- | roles/ipsec_route_based/templates/vti_l3.yml.j2 | 5 | ||||
| -rw-r--r-- | roles/ipsec_route_based/vars/main.yml | 23 |
16 files changed, 601 insertions, 0 deletions
diff --git a/roles/ipsec_route_based/defaults/main.yml b/roles/ipsec_route_based/defaults/main.yml new file mode 100644 index 0000000..238cd54 --- /dev/null +++ b/roles/ipsec_route_based/defaults/main.yml @@ -0,0 +1,22 @@ +--- +ipsec_route_based_ike_group: + name: IKE-GROUP + key_exchange: ikev2 + lifetime: 28800 + dh_group: 14 + encryption: aes256 + hash: sha256 + close_action: start + dead_peer_detection: {action: restart, interval: 10, timeout: 30} +ipsec_route_based_esp_group: + name: ESP-GROUP + lifetime: 3600 + pfs: dh-group14 + encryption: aes256 + hash: sha256 +ipsec_route_based_ospf: null +ipsec_route_based_default_gateway: "" +ipsec_route_based_interfaces: [] +ipsec_route_based_disable_route_autoinstall: true +ipsec_route_based_interface_routes: [] +ipsec_route_based_bgp: null diff --git a/roles/ipsec_route_based/meta/argument_specs.yml b/roles/ipsec_route_based/meta/argument_specs.yml new file mode 100644 index 0000000..f683376 --- /dev/null +++ b/roles/ipsec_route_based/meta/argument_specs.yml @@ -0,0 +1,209 @@ +--- +argument_specs: + main: + short_description: Route-based site-to-site IPsec over VTI, with OSPF or BGP (Cisco, Palo Alto and Azure docs blueprints) + description: + - Configures one IKE group, one ESP group, a pre-shared key and a + site-to-site peer bound to a VTI per peer, with + C(disable-route-autoinstall) as route-based VPNs need. Optionally runs + OSPF over the VTIs and adds a default route towards the WAN gateway. + - Uses vyos.vyos resource modules only (vyos_vpn_ipsec, vyos_vpn_ipsec_s2s, + vyos_interfaces, vyos_l3_interfaces, vyos_ospfv2, vyos_ospf_interfaces, + vyos_static_routes). + - The far end can be any IKE peer (Cisco, Palo Alto, another VyOS, a cloud + gateway); match its proposals in the IKE/ESP group inputs. + - WAN and LAN addressing belongs to C(vyos.blueprints.base). + options: + ipsec_route_based_peers: + type: list + elements: dict + required: true + description: Remote peers, one VTI each. + options: + name: + type: str + required: true + description: Peer name, e.g. C(CISCO). + local_address: + type: str + required: true + description: Local WAN address used for IKE. + remote_address: + type: str + required: true + description: Peer's WAN address. + local_id: + type: str + description: Local IKE id. Defaults to C(local_address). + remote_id: + type: str + description: Remote IKE id. Defaults to C(remote_address). + psk: + type: str + required: true + no_log: true + description: Pre-shared key. + psk_type: + type: str + choices: [plaintext, base64, hex] + description: Encoding of C(psk). Not set when omitted (VyOS default, plaintext). + psk_name: + type: str + description: + - Name of the PSK entry. Defaults to C(<name>-PSK). + - Peers with the same C(psk_name) share one entry carrying all their ids (they must use the same C(psk)). + connection_type: + type: str + choices: [initiate, trap, none] + default: initiate + description: + - C(initiate) brings the tunnel up from this side (recommended by + the page when the far end only initiates on traffic); C(none) + only responds. + description: + type: str + description: Peer description. + ikev2_reauth: + type: str + choices: ['yes', 'no', inherit] + description: Peer-level IKEv2 re-authentication. + esp_group_on_vti: + type: bool + default: false + description: Bind the ESP group on the VTI (C(vti esp-group)) instead of C(default-esp-group), as Azure and GCP pages do. + vti: + type: dict + required: true + description: Tunnel interface for this peer. + options: + interface: + type: str + required: true + description: VTI name, e.g. C(vti1). + address: + type: str + required: true + description: Tunnel address in CIDR notation. + mtu: + type: int + description: VTI MTU, e.g. C(1438). + description: + type: str + description: VTI description. + adjust_mss: + type: str + description: TCP MSS clamping, e.g. C(1350) (configured with vyos_config; vyos_interfaces has no option for it). + ipsec_route_based_ike_group: + type: dict + description: + - IKE (phase 1) settings shared by all peers. + - Settings without a default here are configured only when given. + options: + name: {type: str, default: IKE-GROUP, description: Group name.} + proposal_id: {type: int, default: 10, description: Proposal number.} + key_exchange: {type: str, choices: [ikev1, ikev2], default: ikev2, description: IKE version.} + ikev2_reauth: {type: bool, default: false, description: Re-authenticate on IKEv2 rekey.} + lifetime: {type: int, description: Lifetime in seconds.} + dh_group: {type: int, default: 14, description: Diffie-Hellman group.} + encryption: {type: str, default: aes256, description: "Encryption, e.g. C(aes128)."} + hash: {type: str, default: sha256, description: "Hash, e.g. C(sha1)."} + close_action: {type: str, choices: [none, trap, start], description: Action when the peer closes the SA.} + dead_peer_detection: + type: dict + description: DPD settings; only the keys given are configured. + options: + action: {type: str, choices: [trap, clear, restart], description: DPD action.} + interval: {type: int, description: Interval in seconds.} + timeout: {type: int, description: Timeout in seconds.} + ipsec_route_based_esp_group: + type: dict + description: + - ESP (phase 2) settings shared by all peers. + - Settings without a default here are configured only when given. + options: + name: {type: str, default: ESP-GROUP, description: Group name.} + proposal_id: {type: int, default: 10, description: Proposal number.} + mode: {type: str, choices: [tunnel, transport], description: ESP mode.} + lifetime: {type: int, description: Lifetime in seconds.} + pfs: {type: str, description: PFS group or C(disable).} + encryption: {type: str, default: aes256, description: Encryption.} + hash: {type: str, default: sha256, description: Hash.} + ipsec_route_based_ospf: + type: dict + description: OSPF over the VTIs. Omit to configure routing yourself. + options: + router_id: {type: str, required: true, description: OSPF router-id.} + area: {type: str, default: "0", description: OSPF area.} + networks: + type: list + elements: str + required: true + description: Networks to advertise, including the VTI network(s), as on the docs page. + passive_interfaces: + type: list + elements: str + default: [] + description: LAN interfaces that should not form adjacencies. + ipsec_route_based_interfaces: + type: list + elements: str + default: [] + description: Interfaces IPsec listens on (C(vpn ipsec interface)), e.g. C([eth0]). + ipsec_route_based_disable_route_autoinstall: + type: bool + default: true + description: Set C(vpn ipsec options disable-route-autoinstall) (most route-based setups need it). + ipsec_route_based_interface_routes: + type: list + elements: dict + default: [] + description: Static routes out of a VTI, e.g. to the peer's BGP listener. + options: + dest: {type: str, required: true, description: "Destination prefix, e.g. C(10.0.0.4/32)."} + interface: {type: str, required: true, description: "VTI, e.g. C(vti1)."} + ipsec_route_based_bgp: + type: dict + description: eBGP over the VTIs. Omit to configure routing yourself. + options: + asn: {type: int, required: true, description: Local AS number.} + router_id: {type: str, description: BGP router-id.} + networks: {type: list, elements: str, default: [], description: IPv4 prefixes to announce.} + neighbors: + type: list + elements: dict + required: true + description: BGP neighbours reached through the tunnels. + options: + address: {type: str, required: true, description: Neighbour address.} + remote_as: {type: int, required: true, description: Neighbour AS number.} + holdtime: {type: int, description: Hold time in seconds.} + keepalive: {type: int, description: Keepalive in seconds.} + disable_connected_check: {type: bool, default: true, description: Needed when the neighbour is not on the VTI subnet (cloud BGP listeners).} + ebgp_multihop: {type: int, description: eBGP multihop TTL.} + update_source: {type: str, description: Source address or interface.} + soft_reconfiguration_inbound: {type: bool, default: false, description: Keep received routes for soft reconfiguration.} + ipsec_route_based_default_gateway: + type: str + default: "" + description: Adds C(0.0.0.0/0) via this next hop (the WAN gateway). + vyos_blueprints_render_only: + type: bool + default: false + description: Collect commands into C(vyos_blueprints_rendered) instead of configuring. + verify: + short_description: Post-deployment checks for the ipsec_route_based role + description: + - Run with C(tasks_from=verify). Checks the IPsec SA of every peer is up and, + with OSPF, a Full adjacency on every VTI or, with BGP, every session established. + options: + ipsec_route_based_peers: + type: list + elements: dict + required: true + description: Same value as for C(main). + ipsec_route_based_ospf: + type: dict + description: Same value as for C(main). + ipsec_route_based_bgp: + type: dict + description: Same value as for C(main). diff --git a/roles/ipsec_route_based/meta/main.yml b/roles/ipsec_route_based/meta/main.yml new file mode 100644 index 0000000..95831bc --- /dev/null +++ b/roles/ipsec_route_based/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Route-based site-to-site IPsec over VTI, with optional OSPF inside the tunnel + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, ipsec, vpn] +dependencies: [] diff --git a/roles/ipsec_route_based/tasks/main.yml b/roles/ipsec_route_based/tasks/main.yml new file mode 100644 index 0000000..a3b4c16 --- /dev/null +++ b/roles/ipsec_route_based/tasks/main.yml @@ -0,0 +1,113 @@ +--- +- name: Peers sharing a PSK entry use the same key + ansible.builtin.assert: + that: >- + ipsec_route_based_peers | selectattr('psk_name', 'defined') | selectattr('psk_name', 'equalto', _ipsec_route_based_psk_name) + | map(attribute='psk') | unique | length == 1 + fail_msg: "peers with psk_name {{ _ipsec_route_based_psk_name }} have different psk values" + quiet: true + loop: "{{ ipsec_route_based_peers | selectattr('psk_name', 'defined') | map(attribute='psk_name') | unique | list }}" + loop_control: + loop_var: _ipsec_route_based_psk_name + no_log: true + +- name: Create the VTIs + vyos.vyos.vyos_interfaces: + config: "{{ lookup('ansible.builtin.template', 'vti.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_vti + +- name: Clamp TCP MSS on the VTIs + # vyos_interfaces has no option for ip adjust-mss. + vyos.vyos.vyos_config: + lines: "{{ _ipsec_route_based_cli_lines }}" + register: _ipsec_route_based_r_cli + when: + - _ipsec_route_based_cli_lines | length > 0 + - not (vyos_blueprints_render_only | default(false) | bool) + +- name: Address the VTIs + vyos.vyos.vyos_l3_interfaces: + config: "{{ lookup('ansible.builtin.template', 'vti_l3.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_vti_l3 + +- name: Configure IKE/ESP groups, pre-shared keys and options + vyos.vyos.vyos_vpn_ipsec: + config: "{{ lookup('ansible.builtin.template', 'vpn_ipsec.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_ipsec + no_log: true + +- name: Configure the site-to-site peers + vyos.vyos.vyos_vpn_ipsec_s2s: + config: "{{ lookup('ansible.builtin.template', 'vpn_ipsec_s2s.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_s2s + +- name: Add the default route towards the WAN gateway + vyos.vyos.vyos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: + - dest: 0.0.0.0/0 + next_hops: + - forward_router_address: "{{ ipsec_route_based_default_gateway }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_route + when: ipsec_route_based_default_gateway | length > 0 + +- name: Add interface routes through the VTIs (e.g. to the peer's BGP listener) + vyos.vyos.vyos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: "{{ lookup('ansible.builtin.template', 'interface_routes.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_ifroutes + when: ipsec_route_based_interface_routes | length > 0 + +- name: Run BGP over the VTIs + vyos.vyos.vyos_bgp_global: + config: "{{ lookup('ansible.builtin.template', 'bgp_global.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_bgp + when: _ipsec_route_based_bgp | length > 0 + +- name: Set BGP address-family options (networks, soft-reconfiguration) + vyos.vyos.vyos_bgp_address_family: + config: "{{ _ipsec_route_based_bgp_af }}" + state: "{{ _ipsec_route_based_state }}" + vars: + _ipsec_route_based_bgp_af: "{{ lookup('ansible.builtin.template', 'bgp_address_family.yml.j2') | from_yaml }}" + register: _ipsec_route_based_r_bgp_af + when: + - _ipsec_route_based_bgp | length > 0 + - _ipsec_route_based_bgp_af | length > 1 + +- name: Run OSPF over the VTIs + vyos.vyos.vyos_ospfv2: + config: "{{ lookup('ansible.builtin.template', 'ospfv2.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_ospf + when: _ipsec_route_based_ospf | length > 0 + +- name: Set OSPF interface options (point-to-point VTIs, passive LANs) + vyos.vyos.vyos_ospf_interfaces: + config: "{{ lookup('ansible.builtin.template', 'ospf_interfaces.yml.j2') | from_yaml }}" + state: "{{ _ipsec_route_based_state }}" + register: _ipsec_route_based_r_ospf_if + when: _ipsec_route_based_ospf | length > 0 + +- name: Collect rendered commands # noqa: var-naming[no-role-prefix] - shared across roles by design + ansible.builtin.set_fact: + vyos_blueprints_rendered: >- + {{ vyos_blueprints_rendered | default([]) + + (_ipsec_route_based_r_vti.rendered | default([])) + + _ipsec_route_based_cli_lines + + ([_ipsec_route_based_r_vti_l3, _ipsec_route_based_r_ipsec, _ipsec_route_based_r_s2s, + _ipsec_route_based_r_route, _ipsec_route_based_r_ifroutes, _ipsec_route_based_r_bgp, + _ipsec_route_based_r_bgp_af, _ipsec_route_based_r_ospf, _ipsec_route_based_r_ospf_if] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten | unique) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/ipsec_route_based/tasks/verify.yml b/roles/ipsec_route_based/tasks/verify.yml new file mode 100644 index 0000000..42edd24 --- /dev/null +++ b/roles/ipsec_route_based/tasks/verify.yml @@ -0,0 +1,52 @@ +--- +- name: Read IPsec SAs + vyos.vyos.vyos_command: + commands: + - show vpn ipsec sa + register: _ipsec_route_based_v_sa + +- name: The IPsec SA of every peer is up + ansible.builtin.assert: + that: _ipsec_route_based_v_sa.stdout[0] is search('(?m)^' ~ (item.name | regex_escape) ~ '-vti\s+up\b') + fail_msg: "IPsec SA {{ item.name }}-vti is not up" + quiet: true + loop: "{{ ipsec_route_based_peers }}" + loop_control: + label: "{{ item.name }}" + +- name: Read OSPF neighbours + vyos.vyos.vyos_command: + commands: + - show ip ospf neighbor + register: _ipsec_route_based_v_ospf + when: ipsec_route_based_ospf | default({}, true) | length > 0 + +- name: A Full OSPF adjacency on every VTI + ansible.builtin.assert: + that: _ipsec_route_based_v_ospf.stdout[0] is search('Full.*\s' ~ (item.vti.interface | regex_escape) ~ ':') + fail_msg: "no Full OSPF adjacency on {{ item.vti.interface }}" + quiet: true + loop: "{{ ipsec_route_based_peers }}" + loop_control: + label: "{{ item.vti.interface }}" + when: ipsec_route_based_ospf | default({}, true) | length > 0 + +- name: Read BGP neighbours + vyos.vyos.vyos_command: + commands: + - show bgp summary + register: _ipsec_route_based_v_bgp + when: ipsec_route_based_bgp | default({}, true) | length > 0 + +- name: Every BGP session is established + # An established neighbour shows its Up/Down time followed by a prefix count. + ansible.builtin.assert: + that: >- + _ipsec_route_based_v_bgp.stdout[0] is search('(?m)^' ~ (item.address | regex_escape) + ~ '\s.*\s(\d{2}:\d{2}:\d{2}|\d+[dwh]\S*)\s+\d+\b') + fail_msg: "BGP session to {{ item.address }} is not established" + quiet: true + loop: "{{ (ipsec_route_based_bgp | default({}, true)).neighbors | default([]) }}" + loop_control: + label: "{{ item.address }}" + when: ipsec_route_based_bgp | default({}, true) | length > 0 diff --git a/roles/ipsec_route_based/templates/bgp_address_family.yml.j2 b/roles/ipsec_route_based/templates/bgp_address_family.yml.j2 new file mode 100644 index 0000000..97e88cd --- /dev/null +++ b/roles/ipsec_route_based/templates/bgp_address_family.yml.j2 @@ -0,0 +1,20 @@ +{% set b = _ipsec_route_based_bgp %} +as_number: {{ b.asn | int }} +{% if b.networks | default([]) %} +address_family: + - afi: ipv4 + networks: +{% for n in b.networks %} + - prefix: {{ n | to_json }} +{% endfor %} +{% endif %} +{% set soft = b.neighbors | selectattr('soft_reconfiguration_inbound', 'defined') | selectattr('soft_reconfiguration_inbound') | list %} +{% if soft %} +neighbors: +{% for n in soft %} + - neighbor_address: {{ n.address | to_json }} + address_family: + - afi: ipv4 + soft_reconfiguration: true +{% endfor %} +{% endif %} diff --git a/roles/ipsec_route_based/templates/bgp_global.yml.j2 b/roles/ipsec_route_based/templates/bgp_global.yml.j2 new file mode 100644 index 0000000..f4bcf33 --- /dev/null +++ b/roles/ipsec_route_based/templates/bgp_global.yml.j2 @@ -0,0 +1,29 @@ +{% set b = _ipsec_route_based_bgp %} +as_number: {{ b.asn | int }} +{% if b.router_id is defined %} +bgp_params: + router_id: {{ b.router_id | to_json }} +{% endif %} +neighbor: +{% for n in b.neighbors %} + - address: {{ n.address | to_json }} + remote_as: {{ n.remote_as | int }} +{% if n.holdtime is defined or n.keepalive is defined %} + timers: +{% if n.holdtime is defined %} + holdtime: {{ n.holdtime | int }} +{% endif %} +{% if n.keepalive is defined %} + keepalive: {{ n.keepalive | int }} +{% endif %} +{% endif %} +{% if n.disable_connected_check | default(true) | bool %} + disable_connected_check: true +{% endif %} +{% if n.ebgp_multihop is defined %} + ebgp_multihop: {{ n.ebgp_multihop | int }} +{% endif %} +{% if n.update_source is defined %} + update_source: {{ n.update_source | to_json }} +{% endif %} +{% endfor %} diff --git a/roles/ipsec_route_based/templates/interface_routes.yml.j2 b/roles/ipsec_route_based/templates/interface_routes.yml.j2 new file mode 100644 index 0000000..8039e19 --- /dev/null +++ b/roles/ipsec_route_based/templates/interface_routes.yml.j2 @@ -0,0 +1,5 @@ +{% for r in ipsec_route_based_interface_routes %} +- dest: {{ r.dest | to_json }} + next_hops: + - interface: {{ r.interface | to_json }} +{% endfor %} diff --git a/roles/ipsec_route_based/templates/ospf_interfaces.yml.j2 b/roles/ipsec_route_based/templates/ospf_interfaces.yml.j2 new file mode 100644 index 0000000..09452ea --- /dev/null +++ b/roles/ipsec_route_based/templates/ospf_interfaces.yml.j2 @@ -0,0 +1,12 @@ +{% for i in _ipsec_route_based_ospf.passive_interfaces | default([]) %} +- name: {{ i }} + address_family: + - afi: ipv4 + passive: true +{% endfor %} +{% for p in ipsec_route_based_peers %} +- name: {{ p.vti.interface }} + address_family: + - afi: ipv4 + network: point-to-point +{% endfor %} diff --git a/roles/ipsec_route_based/templates/ospfv2.yml.j2 b/roles/ipsec_route_based/templates/ospfv2.yml.j2 new file mode 100644 index 0000000..93ee2ff --- /dev/null +++ b/roles/ipsec_route_based/templates/ospfv2.yml.j2 @@ -0,0 +1,9 @@ +{% set o = _ipsec_route_based_ospf %} +parameters: + router_id: {{ o.router_id | to_json }} +areas: + - area_id: {{ o.area | default('0') | string | to_json }} + network: +{% for n in o.networks %} + - address: {{ n | to_json }} +{% endfor %} diff --git a/roles/ipsec_route_based/templates/vpn_ipsec.yml.j2 b/roles/ipsec_route_based/templates/vpn_ipsec.yml.j2 new file mode 100644 index 0000000..a9eab25 --- /dev/null +++ b/roles/ipsec_route_based/templates/vpn_ipsec.yml.j2 @@ -0,0 +1,51 @@ +{#- Optional settings are rendered only when given. -#} +{% set ike = ipsec_route_based_ike_group %} +{% set esp = ipsec_route_based_esp_group %} +{% set dpd = ike.dead_peer_detection | default({}, true) %} +ike_group: + - name: {{ ike.name | default('IKE-GROUP') | to_json }} + key_exchange: {{ ike.key_exchange | default('ikev2') }} +{% if ike.lifetime is defined and ike.lifetime is not none %} + lifetime: {{ ike.lifetime | int }} +{% endif %} +{% if ike.close_action is defined and ike.close_action %} + close_action: {{ ike.close_action }} +{% endif %} +{% if ike.ikev2_reauth | default(false) | bool %} + ikev2_reauth: true +{% endif %} +{% if dpd %} + dead_peer_detection: +{% for k in ['action', 'interval', 'timeout'] if dpd[k] is defined and dpd[k] is not none %} + {{ k }}: {{ dpd[k] }} +{% endfor %} +{% endif %} + proposal: + - proposal_id: {{ ike.proposal_id | default(10) | int }} + dh_group: {{ ike.dh_group | default(14) | int }} + encryption: {{ ike.encryption | default('aes256') | to_json }} + hash: {{ ike.hash | default('sha256') | to_json }} +esp_group: + - name: {{ esp.name | default('ESP-GROUP') | to_json }} +{% if esp.mode is defined and esp.mode %} + mode: {{ esp.mode }} +{% endif %} +{% if esp.lifetime is defined and esp.lifetime is not none %} + lifetime: {{ esp.lifetime | int }} +{% endif %} +{% if esp.pfs is defined and esp.pfs %} + pfs: {{ esp.pfs | to_json }} +{% endif %} + proposal: + - proposal_id: {{ esp.proposal_id | default(10) | int }} + encryption: {{ esp.encryption | default('aes256') | to_json }} + hash: {{ esp.hash | default('sha256') | to_json }} +authentication: + psk: {{ _ipsec_route_based_psks | to_json }} +{% if ipsec_route_based_interfaces %} +interface: {{ ipsec_route_based_interfaces | to_json }} +{% endif %} +{% if ipsec_route_based_disable_route_autoinstall | bool %} +options: + disable_route_autoinstall: true +{% endif %} diff --git a/roles/ipsec_route_based/templates/vpn_ipsec_s2s.yml.j2 b/roles/ipsec_route_based/templates/vpn_ipsec_s2s.yml.j2 new file mode 100644 index 0000000..2dba193 --- /dev/null +++ b/roles/ipsec_route_based/templates/vpn_ipsec_s2s.yml.j2 @@ -0,0 +1,27 @@ +{% set esp_name = ipsec_route_based_esp_group.name | default('ESP-GROUP') %} +peer: +{% for p in ipsec_route_based_peers %} + - name: {{ p.name | to_json }} +{% if p.description is defined %} + description: {{ p.description | to_json }} +{% endif %} + authentication: + mode: pre-shared-secret + local_id: {{ p.local_id | default(p.local_address) | to_json }} + remote_id: {{ p.remote_id | default(p.remote_address) | to_json }} + connection_type: {{ p.connection_type | default('initiate') }} +{% if not (p.esp_group_on_vti | default(false) | bool) %} + default_esp_group: {{ esp_name | to_json }} +{% endif %} + ike_group: {{ ipsec_route_based_ike_group.name | default('IKE-GROUP') | to_json }} +{% if p.ikev2_reauth is defined %} + ikev2_reauth: {{ p.ikev2_reauth | to_json }} +{% endif %} + local_address: {{ p.local_address | to_json }} + remote_address: [{{ p.remote_address | to_json }}] + vti: + bind: {{ p.vti.interface | to_json }} +{% if p.esp_group_on_vti | default(false) | bool %} + esp_group: {{ esp_name | to_json }} +{% endif %} +{% endfor %} diff --git a/roles/ipsec_route_based/templates/vti.yml.j2 b/roles/ipsec_route_based/templates/vti.yml.j2 new file mode 100644 index 0000000..2047055 --- /dev/null +++ b/roles/ipsec_route_based/templates/vti.yml.j2 @@ -0,0 +1,9 @@ +{% for p in ipsec_route_based_peers %} +- name: {{ p.vti.interface }} +{% if p.vti.mtu is defined %} + mtu: {{ p.vti.mtu | int }} +{% endif %} +{% if p.vti.description is defined %} + description: {{ p.vti.description | to_json }} +{% endif %} +{% endfor %} diff --git a/roles/ipsec_route_based/templates/vti_cli.j2 b/roles/ipsec_route_based/templates/vti_cli.j2 new file mode 100644 index 0000000..8dddeb0 --- /dev/null +++ b/roles/ipsec_route_based/templates/vti_cli.j2 @@ -0,0 +1,4 @@ +{# MSS clamping on VTIs: vyos_interfaces has no option for it. #} +{% for p in ipsec_route_based_peers if p.vti.adjust_mss is defined %} +set interfaces vti {{ p.vti.interface }} ip adjust-mss '{{ p.vti.adjust_mss }}' +{% endfor %} diff --git a/roles/ipsec_route_based/templates/vti_l3.yml.j2 b/roles/ipsec_route_based/templates/vti_l3.yml.j2 new file mode 100644 index 0000000..a54b5de --- /dev/null +++ b/roles/ipsec_route_based/templates/vti_l3.yml.j2 @@ -0,0 +1,5 @@ +{% for p in ipsec_route_based_peers %} +- name: {{ p.vti.interface }} + ipv4: + - address: {{ p.vti.address | to_json }} +{% endfor %} diff --git a/roles/ipsec_route_based/vars/main.yml b/roles/ipsec_route_based/vars/main.yml new file mode 100644 index 0000000..2db5c62 --- /dev/null +++ b/roles/ipsec_route_based/vars/main.yml @@ -0,0 +1,23 @@ +--- +_ipsec_route_based_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_ipsec_route_based_ospf: "{{ ipsec_route_based_ospf | default({}, true) }}" +_ipsec_route_based_bgp: "{{ ipsec_route_based_bgp | default({}, true) }}" +_ipsec_route_based_cli_lines: "{{ (lookup('ansible.builtin.template', 'vti_cli.j2') or '').splitlines() | select | list }}" +# One PSK entry per psk_name; peers sharing a psk_name contribute all their ids. +_ipsec_route_based_psks: >- + {%- set out = [] -%} + {%- set names = [] -%} + {%- for p in ipsec_route_based_peers -%} + {%- set n = p.psk_name | default(p.name ~ '-PSK') -%} + {%- set ids = [p.local_id | default(p.local_address), p.remote_id | default(p.remote_address)] -%} + {%- if n in names -%} + {%- set e = out[names.index(n)] -%} + {%- for i in ids if i not in e.id -%}{%- set _ = e.id.append(i) -%}{%- endfor -%} + {%- else -%} + {%- set e = {'name': n, 'id': ids | unique | list, 'secret': p.psk} -%} + {%- if p.psk_type is defined -%}{%- set _ = e.update({'secret_type': p.psk_type}) -%}{%- endif -%} + {%- set _ = names.append(n) -%} + {%- set _ = out.append(e) -%} + {%- endif -%} + {%- endfor -%} + {{ out }} |
