diff options
Diffstat (limited to 'roles/ipsec_policy_based')
| -rw-r--r-- | roles/ipsec_policy_based/defaults/main.yml | 19 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/meta/argument_specs.yml | 145 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/meta/main.yml | 11 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/tasks/main.yml | 34 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/tasks/verify.yml | 23 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/templates/vpn_ipsec.yml.j2 | 58 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j2 | 29 | ||||
| -rw-r--r-- | roles/ipsec_policy_based/vars/main.yml | 2 |
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' }}" |
