summaryrefslogtreecommitdiff
path: root/roles/ipsec_policy_based
diff options
context:
space:
mode:
Diffstat (limited to 'roles/ipsec_policy_based')
-rw-r--r--roles/ipsec_policy_based/defaults/main.yml19
-rw-r--r--roles/ipsec_policy_based/meta/argument_specs.yml145
-rw-r--r--roles/ipsec_policy_based/meta/main.yml11
-rw-r--r--roles/ipsec_policy_based/tasks/main.yml34
-rw-r--r--roles/ipsec_policy_based/tasks/verify.yml23
-rw-r--r--roles/ipsec_policy_based/templates/vpn_ipsec.yml.j258
-rw-r--r--roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j229
-rw-r--r--roles/ipsec_policy_based/vars/main.yml2
8 files changed, 321 insertions, 0 deletions
diff --git a/roles/ipsec_policy_based/defaults/main.yml b/roles/ipsec_policy_based/defaults/main.yml
new file mode 100644
index 0000000..eeb3961
--- /dev/null
+++ b/roles/ipsec_policy_based/defaults/main.yml
@@ -0,0 +1,19 @@
+---
+ipsec_policy_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_policy_based_esp_group:
+ name: ESP-GROUP
+ lifetime: 3600
+ pfs: dh-group14
+ encryption: aes256
+ hash: sha256
+ipsec_policy_based_default_gateway: ""
+ipsec_policy_based_interfaces: []
+ipsec_policy_based_options: {}
diff --git a/roles/ipsec_policy_based/meta/argument_specs.yml b/roles/ipsec_policy_based/meta/argument_specs.yml
new file mode 100644
index 0000000..48d3341
--- /dev/null
+++ b/roles/ipsec_policy_based/meta/argument_specs.yml
@@ -0,0 +1,145 @@
+---
+argument_specs:
+ main:
+ short_description: Policy-based site-to-site IPsec (docs blueprint "Policy-based Site-to-Site VPN IPsec between VyOS and Cisco")
+ description:
+ - Configures one IKE group, one ESP group, a pre-shared key per peer and
+ site-to-site peers whose tunnels are defined by traffic selectors
+ (local and remote prefixes). Optionally adds a default route towards
+ the WAN gateway.
+ - Uses vyos.vyos resource modules only (vyos_vpn_ipsec, vyos_vpn_ipsec_s2s,
+ vyos_static_routes).
+ - The far end can be any IKE peer; match its proposals and mirror the
+ traffic selectors there.
+ - WAN and LAN addressing belongs to C(vyos.blueprints.base).
+ options:
+ ipsec_policy_based_peers:
+ type: list
+ elements: dict
+ required: true
+ description: Remote peers.
+ 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).
+ connection_type:
+ type: str
+ choices: [initiate, trap, none]
+ default: initiate
+ description: C(initiate) brings the tunnels up from this side; C(none) only responds.
+ virtual_address:
+ type: str
+ description: Request a virtual IP from the peer, e.g. C(0.0.0.0) for FlexVPN.
+ authentication_ids:
+ type: bool
+ default: true
+ description: Set C(local-id)/C(remote-id) on the peer (the PSK ids are always set).
+ tunnels:
+ type: list
+ elements: dict
+ required: true
+ description: Traffic selectors, one IPsec SA each.
+ options:
+ id:
+ type: int
+ description: Tunnel number. Defaults to the position in the list (1, 2, ...).
+ local_prefix:
+ type: str
+ required: true
+ description: Local network, e.g. C(192.168.0.0/24).
+ remote_prefix:
+ type: str
+ required: true
+ description: Remote network, e.g. C(192.168.10.0/24).
+ protocol:
+ type: str
+ description: Protect only this protocol, e.g. C(gre) for GRE over IPsec.
+ ipsec_policy_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.}
+ 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_policy_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_policy_based_interfaces:
+ type: list
+ elements: str
+ default: []
+ description: Interfaces IPsec listens on (C(vpn ipsec interface)), e.g. C([eth0]).
+ ipsec_policy_based_options:
+ type: dict
+ default: {}
+ description:
+ - C(vpn ipsec options), passed through as the C(options) of vyos.vyos.vyos_vpn_ipsec.
+ - "For FlexVPN, e.g. C({flexvpn: true, virtual_ip: true, interface: tun1, disable_route_autoinstall: true})."
+ ipsec_policy_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_policy_based role
+ description: Run with C(tasks_from=verify). Checks the IPsec SA of every tunnel of every peer is up.
+ options:
+ ipsec_policy_based_peers:
+ type: list
+ elements: dict
+ required: true
+ description: Same value as for C(main).
diff --git a/roles/ipsec_policy_based/meta/main.yml b/roles/ipsec_policy_based/meta/main.yml
new file mode 100644
index 0000000..316e8e1
--- /dev/null
+++ b/roles/ipsec_policy_based/meta/main.yml
@@ -0,0 +1,11 @@
+---
+galaxy_info:
+ author: VyOS maintainers and contributors
+ description: Policy-based site-to-site IPsec with traffic selectors
+ 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_policy_based/tasks/main.yml b/roles/ipsec_policy_based/tasks/main.yml
new file mode 100644
index 0000000..c9c7329
--- /dev/null
+++ b/roles/ipsec_policy_based/tasks/main.yml
@@ -0,0 +1,34 @@
+---
+- name: Configure IKE/ESP groups and pre-shared keys
+ vyos.vyos.vyos_vpn_ipsec:
+ config: "{{ lookup('ansible.builtin.template', 'vpn_ipsec.yml.j2') | from_yaml }}"
+ state: "{{ _ipsec_policy_based_state }}"
+ register: _ipsec_policy_based_r_ipsec
+ no_log: true
+
+- name: Configure the site-to-site peers and their traffic selectors
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config: "{{ lookup('ansible.builtin.template', 'vpn_ipsec_s2s.yml.j2') | from_yaml }}"
+ state: "{{ _ipsec_policy_based_state }}"
+ register: _ipsec_policy_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_policy_based_default_gateway }}"
+ state: "{{ _ipsec_policy_based_state }}"
+ register: _ipsec_policy_based_r_route
+ when: ipsec_policy_based_default_gateway | 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_policy_based_r_ipsec, _ipsec_policy_based_r_s2s, _ipsec_policy_based_r_route]
+ | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten) }}
+ when: vyos_blueprints_render_only | default(false) | bool
diff --git a/roles/ipsec_policy_based/tasks/verify.yml b/roles/ipsec_policy_based/tasks/verify.yml
new file mode 100644
index 0000000..ec85d8d
--- /dev/null
+++ b/roles/ipsec_policy_based/tasks/verify.yml
@@ -0,0 +1,23 @@
+---
+- name: Read IPsec SAs
+ vyos.vyos.vyos_command:
+ commands:
+ - show vpn ipsec sa
+ register: _ipsec_policy_based_v_sa
+
+- name: The IPsec SA of every tunnel is up
+ ansible.builtin.assert:
+ that: _ipsec_policy_based_v_sa.stdout[0] is search('(?m)^' ~ (item | regex_escape) ~ '\s+up\b')
+ fail_msg: "IPsec SA {{ item }} is not up"
+ quiet: true
+ loop: "{{ _ipsec_policy_based_sa_names }}"
+ vars:
+ # SA names as VyOS shows them: <peer>-tunnel-<id>
+ _ipsec_policy_based_sa_names: >-
+ {%- set out = [] -%}
+ {%- for p in ipsec_policy_based_peers -%}
+ {%- for t in p.tunnels -%}
+ {%- set _ = out.append(p.name ~ '-tunnel-' ~ (t.id | default(loop.index))) -%}
+ {%- endfor -%}
+ {%- endfor -%}
+ {{ out }}
diff --git a/roles/ipsec_policy_based/templates/vpn_ipsec.yml.j2 b/roles/ipsec_policy_based/templates/vpn_ipsec.yml.j2
new file mode 100644
index 0000000..faa824e
--- /dev/null
+++ b/roles/ipsec_policy_based/templates/vpn_ipsec.yml.j2
@@ -0,0 +1,58 @@
+{#- Optional settings are rendered only when given, so a page that leaves
+ something at the VyOS default (lifetime, PFS, DPD, ...) renders as written. -#}
+{% set ike = ipsec_policy_based_ike_group %}
+{% set esp = ipsec_policy_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') }}
+{% for k in ['lifetime'] if ike[k] is defined and ike[k] is not none %}
+ {{ k }}: {{ ike[k] | int }}
+{% endfor %}
+{% if ike.close_action is defined and ike.close_action %}
+ close_action: {{ ike.close_action }}
+{% 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:
+{% for p in ipsec_policy_based_peers %}
+ - name: {{ p.psk_name | default(p.name ~ '-PSK') | to_json }}
+ id:
+ - {{ p.local_id | default(p.local_address) | to_json }}
+ - {{ p.remote_id | default(p.remote_address) | to_json }}
+ secret: {{ p.psk | to_json }}
+{% if p.psk_type is defined %}
+ secret_type: {{ p.psk_type }}
+{% endif %}
+{% endfor %}
+{% if ipsec_policy_based_interfaces %}
+interface: {{ ipsec_policy_based_interfaces | to_json }}
+{% endif %}
+{% if ipsec_policy_based_options %}
+options: {{ ipsec_policy_based_options | to_json }}
+{% endif %}
diff --git a/roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j2 b/roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j2
new file mode 100644
index 0000000..cb2277c
--- /dev/null
+++ b/roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j2
@@ -0,0 +1,29 @@
+peer:
+{% for p in ipsec_policy_based_peers %}
+ - name: {{ p.name | to_json }}
+ authentication:
+ mode: pre-shared-secret
+{% if p.authentication_ids | default(true) | bool %}
+ local_id: {{ p.local_id | default(p.local_address) | to_json }}
+ remote_id: {{ p.remote_id | default(p.remote_address) | to_json }}
+{% endif %}
+ connection_type: {{ p.connection_type | default('initiate') }}
+ default_esp_group: {{ ipsec_policy_based_esp_group.name | default('ESP-GROUP') | to_json }}
+ ike_group: {{ ipsec_policy_based_ike_group.name | default('IKE-GROUP') | to_json }}
+ local_address: {{ p.local_address | to_json }}
+ remote_address: [{{ p.remote_address | to_json }}]
+{% if p.virtual_address is defined %}
+ virtual_address: [{{ p.virtual_address | to_json }}]
+{% endif %}
+ tunnel:
+{% for t in p.tunnels %}
+ - tunnel_id: {{ t.id | default(loop.index) | int }}
+ local:
+ prefix: [{{ t.local_prefix | to_json }}]
+ remote:
+ prefix: [{{ t.remote_prefix | to_json }}]
+{% if t.protocol is defined %}
+ protocol: {{ t.protocol | to_json }}
+{% endif %}
+{% endfor %}
+{% endfor %}
diff --git a/roles/ipsec_policy_based/vars/main.yml b/roles/ipsec_policy_based/vars/main.yml
new file mode 100644
index 0000000..f077122
--- /dev/null
+++ b/roles/ipsec_policy_based/vars/main.yml
@@ -0,0 +1,2 @@
+---
+_ipsec_policy_based_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}"