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 | |
| parent | 748df2bc1d35fa285dd3fe46916e1230408778c7 (diff) | |
| download | vyos.blueprints-main.tar.gz vyos.blueprints-main.zip | |
T9393: new rolesmain
230 files changed, 7420 insertions, 8 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22f18f4..47a6ad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - scenario: [base, edge_nat, ha_vrrp] + scenario: [base, edge_nat, ha_vrrp, ospf_unnumbered, bgp_unnumbered, zone_firewall, vrf_firewall, bridge_firewall, ipsec_route_based, ipsec_policy_based, policy_ipsec_firewall, gre_tunnel, ipsec_route_based_bgp, ipsec_route_based_bgp_dual] steps: - uses: actions/checkout@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3afca90..60d95db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,16 @@ ## 0.1.0 (unreleased) - Initial roles: `base`, `edge_nat`, `ha_vrrp`. +- New role `ospf_unnumbered` (docs blueprint "OSPF unnumbered with ECMP"). +- New role `bgp_unnumbered` (docs blueprint "BGP IPv6 unnumbered with extended nexthop"). +- New role `zone_firewall` (docs blueprint "Zone-Policy example"). +- New role `vrf_firewall` (docs blueprint "VRF and firewall example"). +- New role `bridge_firewall` (docs blueprint "Bridge and firewall example"). +- New role `ipsec_route_based` (docs blueprints "Route-based Site-to-Site VPN IPsec between VyOS and Cisco" and "... between VyOS and Palo Alto"). +- New role `ipsec_policy_based` (docs blueprint "Policy-based Site-to-Site VPN IPsec between VyOS and Cisco"). +- New roles `firewall` and `nat`; `ipsec_policy_based` gains proposal ids, ESP mode, `vpn ipsec interface`, optional peer IKE ids and optional lifetime/PFS/DPD (docs blueprint "Policy-Based Site-to-Site VPN and Firewall Configuration"). +- New role `gre_tunnel`; `ipsec_policy_based` gains `vpn ipsec options` (FlexVPN, virtual-ip), per-tunnel protocol and peer virtual-address (docs blueprint "Site-to-Site IPSec VPN to Cisco using FlexVPN"). +- `ipsec_route_based` gains BGP over the VTIs, interface routes, VTI description and MSS clamping, IKEv2 re-auth, peer description, ESP group on the VTI, `vpn ipsec interface` and optional lifetime/PFS/DPD/close-action (docs blueprint "Route-Based Site-to-Site VPN to Azure"). - Render-only preview mode (`vyos_blueprints_render_only`). - Offline render tests and containerlab-based Molecule scenarios. +- `ipsec_route_based`: peers sharing a `psk_name` share one PSK entry with all their ids (docs blueprint "Route-Based Redundant Site-to-Site VPN to Azure"). @@ -12,6 +12,16 @@ validated, idempotent, testable unit. | `vyos.blueprints.base` | Hostname, interfaces and VLAN sub-interfaces, IPv4/IPv6 addressing, NTP, remote syslog | building block | | `vyos.blueprints.edge_nat` | WAN (DHCP or static), default route, source NAT masquerade | small-office edge | | `vyos.blueprints.ha_vrrp` | Two-node VRRP, sync-group, conntrack-sync | [High Availability Walkthrough](https://docs.vyos.io/en/1.5/configexamples/ha.html) | +| `vyos.blueprints.ospf_unnumbered` | OSPF over unnumbered point-to-point links with ECMP, MD5 auth, redistribute connected | [OSPF unnumbered with ECMP](https://docs.vyos.io/en/1.5/configexamples/ospf-unnumbered.html) | +| `vyos.blueprints.bgp_unnumbered` | eBGP over IPv6 link-local interfaces, extended next-hop, ECMP, redistribute connected | [BGP IPv6 unnumbered with extended nexthop](https://docs.vyos.io/en/1.5/configexamples/bgp-ipv6-unnumbered.html) | +| `vyos.blueprints.zone_firewall` | Zones, one ruleset per zone-pair-direction (IPv4 and IPv6), base established/invalid rules, default-log | [Zone-Policy example](https://docs.vyos.io/en/1.5/configexamples/zone-policy.html) | +| `vyos.blueprints.vrf_firewall` | VRFs and tables, VLAN/PPPoE VRF membership, inter-VRF route leaking, forward/input filters, state policy | [VRF and firewall example](https://docs.vyos.io/en/1.5/configexamples/fwall-and-vrf.html) | +| `vyos.blueprints.bridge_firewall` | Bridges, interface groups, bridge prerouting/forward rulesets, IPv4 rulesets for routed traffic and router access | [Bridge and firewall example](https://docs.vyos.io/en/1.5/configexamples/fwall-and-bridge.html) | +| `vyos.blueprints.ipsec_route_based` | Route-based site-to-site IPsec over VTI (IKEv1/IKEv2, PSK), optional OSPF or BGP inside the tunnel | [Route-based ... VyOS and Cisco](https://docs.vyos.io/en/1.5/configexamples/ipsec-cisco-route-based.html), [Route-based ... VyOS and Palo Alto](https://docs.vyos.io/en/1.5/configexamples/ipsec-pa-route-based.html), [Route-Based ... to Azure (BGP)](https://docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html), [Route-Based Redundant ... to Azure](https://docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html) | +| `vyos.blueprints.ipsec_policy_based` | Policy-based site-to-site IPsec with traffic selectors (IKEv1/IKEv2, PSK) | [Policy-based Site-to-Site VPN IPsec between VyOS and Cisco](https://docs.vyos.io/en/1.5/configexamples/ipsec-cisco-policy-based.html) | +| `vyos.blueprints.firewall` | Firewall network/address/port groups, IPv4/IPv6 input, forward and output filters | building block; used by [Policy-Based Site-to-Site VPN and Firewall Configuration](https://docs.vyos.io/en/1.5/configexamples/policy-based-ipsec-and-firewall.html) | +| `vyos.blueprints.nat` | Source NAT (masquerade, SNAT, exclusions) and destination NAT | building block; used by [Policy-Based Site-to-Site VPN and Firewall Configuration](https://docs.vyos.io/en/1.5/configexamples/policy-based-ipsec-and-firewall.html) | +| `vyos.blueprints.gre_tunnel` | GRE and other tunnel interfaces (endpoints, MTU, MSS clamping, addresses) | building block; used by [Site-to-Site IPSec VPN to Cisco using FlexVPN](https://docs.vyos.io/en/1.5/configexamples/site-2-site-cisco.html) | Every role has documented, validated inputs (`ansible-doc -t role vyos.blueprints.<role>`) and a `verify` entry point with operational checks diff --git a/examples/README.md b/examples/README.md index 449efe4..8c149d5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,8 +6,19 @@ Each directory is a complete, runnable Ansible project: |---|---|---| | `single-edge/` | `base`, `edge_nat` | – (small-office internet edge) | | `ha-pair/` | `base`, `ha_vrrp` | [High Availability Walkthrough](https://docs.vyos.io/en/1.5/configexamples/ha.html) | +| `ospf-unnumbered/` | `base`, `ospf_unnumbered` | [OSPF unnumbered with ECMP](https://docs.vyos.io/en/1.5/configexamples/ospf-unnumbered.html) | +| `bgp-unnumbered/` | `bgp_unnumbered` | [BGP IPv6 unnumbered with extended nexthop](https://docs.vyos.io/en/1.5/configexamples/bgp-ipv6-unnumbered.html) | +| `zone-policy/` | `base`, `zone_firewall` | [Zone-Policy example](https://docs.vyos.io/en/1.5/configexamples/zone-policy.html) | +| `vrf-firewall/` | `base`, `vrf_firewall` | [VRF and firewall example](https://docs.vyos.io/en/1.5/configexamples/fwall-and-vrf.html) | +| `bridge-firewall/` | `base`, `bridge_firewall` | [Bridge and firewall example](https://docs.vyos.io/en/1.5/configexamples/fwall-and-bridge.html) | +| `ipsec-route-based/` | `base`, `ipsec_route_based` | [Route-based ... VyOS and Cisco](https://docs.vyos.io/en/1.5/configexamples/ipsec-cisco-route-based.html); for [Route-based ... VyOS and Palo Alto](https://docs.vyos.io/en/1.5/configexamples/ipsec-pa-route-based.html) rename the peer `CISCO` to `PA` - the VyOS side is otherwise identical | +| `ipsec-policy-based/` | `base`, `ipsec_policy_based` | [Policy-based Site-to-Site VPN IPsec between VyOS and Cisco](https://docs.vyos.io/en/1.5/configexamples/ipsec-cisco-policy-based.html) | +| `policy-ipsec-firewall/` | `base`, `ipsec_policy_based`, `firewall`, `nat` | [Policy-Based Site-to-Site VPN and Firewall Configuration](https://docs.vyos.io/en/1.5/configexamples/policy-based-ipsec-and-firewall.html) | +| `flexvpn-cisco/` | `gre_tunnel`, `ipsec_policy_based` | [Site-to-Site IPSec VPN to Cisco using FlexVPN](https://docs.vyos.io/en/1.5/configexamples/site-2-site-cisco.html) (no containerlab topology: needs a Cisco FlexVPN hub) | +| `azure-vpn-bgp/` | `ipsec_route_based` | [Route-Based Site-to-Site VPN to Azure (BGP over IKEv2/IPsec)](https://docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html) (no containerlab topology: the far end is Azure) | +| `azure-vpn-dual-bgp/` | `ipsec_route_based` | [Route-Based Redundant Site-to-Site VPN to Azure (BGP over IKEv2/IPsec)](https://docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html) (no containerlab topology: the far end is Azure) | -Every example ships a `topology.clab.yml`, so you can try it against +Most examples ship a `topology.clab.yml`, so you can try them against containerized VyOS before pointing it at real routers: ```bash diff --git a/examples/azure-vpn-bgp/group_vars/onprem.yml b/examples/azure-vpn-bgp/group_vars/onprem.yml new file mode 100644 index 0000000..fdada7a --- /dev/null +++ b/examples/azure-vpn-bgp/group_vars/onprem.yml @@ -0,0 +1,45 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html +ipsec_route_based_peers: + - name: 203.0.113.2 + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk # use ansible-vault for real devices + local_address: 10.10.0.5 # private IP; the device is behind NAT + local_id: 198.51.100.3 # public IP + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, description: Azure Tunnel, adjust_mss: "1350"} +ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} +ipsec_route_based_esp_group: + name: AZURE + lifetime: 3600 + mode: tunnel + pfs: dh-group2 + proposal_id: 1 + encryption: aes256 + hash: sha1 +ipsec_route_based_interfaces: [eth0] +ipsec_route_based_disable_route_autoinstall: false # the page does not set it +ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} +ipsec_route_based_bgp: + asn: 64499 + neighbors: + - address: 10.0.0.4 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true diff --git a/examples/azure-vpn-bgp/inventory.yml b/examples/azure-vpn-bgp/inventory.yml new file mode 100644 index 0000000..787a66e --- /dev/null +++ b/examples/azure-vpn-bgp/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html +# Only the VyOS side is configured here; the Azure VNet gateway, local network +# gateway and connection are created in Azure as the page's prerequisites describe. +all: + children: + onprem: + hosts: + vyos: + ansible_host: 192.0.2.10 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_password: vyos # use ansible-vault for real devices diff --git a/examples/azure-vpn-bgp/site.yml b/examples/azure-vpn-bgp/site.yml new file mode 100644 index 0000000..8d27036 --- /dev/null +++ b/examples/azure-vpn-bgp/site.yml @@ -0,0 +1,11 @@ +--- +- name: Route-based VPN to Azure with BGP + hosts: onprem + gather_facts: false + roles: + - vyos.blueprints.ipsec_route_based + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/azure-vpn-bgp/verify.yml b/examples/azure-vpn-bgp/verify.yml new file mode 100644 index 0000000..fb4e633 --- /dev/null +++ b/examples/azure-vpn-bgp/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the Azure VPN + hosts: onprem + gather_facts: false + tasks: + - name: IPsec and BGP checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify diff --git a/examples/azure-vpn-dual-bgp/group_vars/onprem.yml b/examples/azure-vpn-dual-bgp/group_vars/onprem.yml new file mode 100644 index 0000000..7f0f1ba --- /dev/null +++ b/examples/azure-vpn-dual-bgp/group_vars/onprem.yml @@ -0,0 +1,64 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html +# Both peers share the PSK entry "azure" (one entry, three ids), as on the page. +ipsec_route_based_peers: + - name: azure-primary + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk # use ansible-vault for real devices + local_address: 10.10.0.5 # private IP; the device is behind NAT + local_id: 198.51.100.3 # public IP + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, description: Azure Primary Tunnel, adjust_mss: "1350"} + - name: azure-secondary + description: AZURE secondary TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk # use ansible-vault for real devices + local_address: 10.10.0.5 + local_id: 198.51.100.3 + remote_address: 203.0.113.3 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti2, address: 10.10.1.6/32, description: Azure Secondary Tunnel, adjust_mss: "1350"} +ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} +ipsec_route_based_esp_group: + name: AZURE + lifetime: 3600 + mode: tunnel + pfs: dh-group2 + proposal_id: 1 + encryption: aes256 + hash: sha1 +ipsec_route_based_interfaces: [eth0] +ipsec_route_based_disable_route_autoinstall: false # the page does not set it +ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} + - {dest: 10.0.0.5/32, interface: vti2} +ipsec_route_based_bgp: + asn: 64499 + neighbors: + - address: 10.0.0.4 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true + - address: 10.0.0.5 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true diff --git a/examples/azure-vpn-dual-bgp/inventory.yml b/examples/azure-vpn-dual-bgp/inventory.yml new file mode 100644 index 0000000..1d4f699 --- /dev/null +++ b/examples/azure-vpn-dual-bgp/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html +# Only the VyOS side is configured here; the Azure VNet gateway, local network +# gateway and connection are created in Azure as the page's prerequisites describe. +all: + children: + onprem: + hosts: + vyos: + ansible_host: 192.0.2.10 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_password: vyos # use ansible-vault for real devices diff --git a/examples/azure-vpn-dual-bgp/site.yml b/examples/azure-vpn-dual-bgp/site.yml new file mode 100644 index 0000000..651bf93 --- /dev/null +++ b/examples/azure-vpn-dual-bgp/site.yml @@ -0,0 +1,11 @@ +--- +- name: Redundant route-based VPN to Azure with BGP + hosts: onprem + gather_facts: false + roles: + - vyos.blueprints.ipsec_route_based + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/azure-vpn-dual-bgp/verify.yml b/examples/azure-vpn-dual-bgp/verify.yml new file mode 100644 index 0000000..ef02155 --- /dev/null +++ b/examples/azure-vpn-dual-bgp/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check both Azure tunnels + hosts: onprem + gather_facts: false + tasks: + - name: IPsec and BGP checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify diff --git a/examples/bgp-unnumbered/group_vars/fabric.yml b/examples/bgp-unnumbered/group_vars/fabric.yml new file mode 100644 index 0000000..34617ff --- /dev/null +++ b/examples/bgp-unnumbered/group_vars/fabric.yml @@ -0,0 +1,3 @@ +--- +bgp_unnumbered_interfaces: [eth1, eth2] +bgp_unnumbered_group: fabric diff --git a/examples/bgp-unnumbered/host_vars/routerA.yml b/examples/bgp-unnumbered/host_vars/routerA.yml new file mode 100644 index 0000000..e538109 --- /dev/null +++ b/examples/bgp-unnumbered/host_vars/routerA.yml @@ -0,0 +1,3 @@ +--- +bgp_unnumbered_asn: 64496 +bgp_unnumbered_router_id: 192.168.0.1 diff --git a/examples/bgp-unnumbered/host_vars/routerB.yml b/examples/bgp-unnumbered/host_vars/routerB.yml new file mode 100644 index 0000000..e9e9e62 --- /dev/null +++ b/examples/bgp-unnumbered/host_vars/routerB.yml @@ -0,0 +1,3 @@ +--- +bgp_unnumbered_asn: 64499 +bgp_unnumbered_router_id: 192.168.0.2 diff --git a/examples/bgp-unnumbered/inventory.yml b/examples/bgp-unnumbered/inventory.yml new file mode 100644 index 0000000..30bdaba --- /dev/null +++ b/examples/bgp-unnumbered/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/bgp-ipv6-unnumbered.html +all: + children: + fabric: + hosts: + routerA: + ansible_host: clab-bgp-unnumbered-routerA # your router's address + routerB: + ansible_host: clab-bgp-unnumbered-routerB + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/bgp-unnumbered/site.yml b/examples/bgp-unnumbered/site.yml new file mode 100644 index 0000000..a314bc9 --- /dev/null +++ b/examples/bgp-unnumbered/site.yml @@ -0,0 +1,11 @@ +--- +- name: BGP IPv6 unnumbered with extended next-hop + hosts: fabric + gather_facts: false + roles: + - vyos.blueprints.bgp_unnumbered + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/bgp-unnumbered/topology.clab.yml b/examples/bgp-unnumbered/topology.clab.yml new file mode 100644 index 0000000..004cd54 --- /dev/null +++ b/examples/bgp-unnumbered/topology.clab.yml @@ -0,0 +1,13 @@ +name: bgp-unnumbered +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:latest} + nodes: + routerA: + kind: vyosnetworks_vyos + routerB: + kind: vyosnetworks_vyos + links: + - endpoints: ["routerA:eth1", "routerB:eth1"] + - endpoints: ["routerA:eth2", "routerB:eth2"] diff --git a/examples/bgp-unnumbered/verify.yml b/examples/bgp-unnumbered/verify.yml new file mode 100644 index 0000000..07004ac --- /dev/null +++ b/examples/bgp-unnumbered/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the BGP fabric + hosts: fabric + gather_facts: false + tasks: + - name: BGP checks (all routers in one play for the route checks) + ansible.builtin.include_role: + name: vyos.blueprints.bgp_unnumbered + tasks_from: verify diff --git a/examples/bridge-firewall/group_vars/bridge.yml b/examples/bridge-firewall/group_vars/bridge.yml new file mode 100644 index 0000000..a72e386 --- /dev/null +++ b/examples/bridge-firewall/group_vars/bridge.yml @@ -0,0 +1,85 @@ +--- +# The page's internet uplink is eth0; this example uses eth8 because +# containerlab reserves eth0 for management. Use eth0 on a real router. +base_interfaces: + - {name: eth8, addresses: [203.0.113.1/24]} + +bridge_firewall_bridges: + - name: br0 + description: Isolated L2 bridge + members: + - {interface: eth1, description: br0} + - {interface: eth2, description: br0} + prerouting: + default_action: drop + rules: + - {number: 10, description: Accept IPv6 traffic, ethernet_type: ipv6} + forward: + default_action: accept + + - name: br1 + description: L3 bridge br1 + addresses: [10.1.1.1/24] + members: + - {interface: eth3, description: br1} + - {interface: eth4, description: br1} + prerouting: + default_action: accept + rules: + - number: 10 + description: Drop DHCP discover + action: drop + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + log: true + - {number: 20, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - {number: 10, description: Accept ARP, ethernet_type: arp} + - {number: 20, description: Accept ipv4 from host, source: {address: 10.1.1.102}, state: [new]} + ip_forward: + rules: + - {number: 10, description: br1 - allow internet access, outbound_interface: eth8} + router_access: accept + + - name: br2 + description: L3 bridge br2 + addresses: [10.2.2.1/24] + members: + - {interface: eth5, description: br2 - Host} + - {interface: eth6, description: br2 - Trusted DHCP Server} + - {interface: eth7, description: br2} + prerouting: + default_action: accept + rules: + - {number: 10, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - number: 10 + description: Accept DHCP discover + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + - number: 20 + description: Accept DHCP offers from trusted interface + protocol: udp + source: {port: "67"} + destination: {port: "68"} + inbound_interface: eth6 + - number: 22 + description: Drop all other DHCP offers + action: drop + protocol: udp + source: {port: "67"} + destination: {port: "68"} + log: true + - {number: 30, description: Accept ARP, ethernet_type: arp} + - {number: 40, description: Accept ipv4, ethernet_type: ipv4} + ip_forward: + rules: + - {number: 10, description: br2 - allow internet access, outbound_interface: eth8} + - {number: 20, description: br2 - allow access to br1, outbound_bridge: br1} + router_access: drop diff --git a/examples/bridge-firewall/inventory.yml b/examples/bridge-firewall/inventory.yml new file mode 100644 index 0000000..3d882a0 --- /dev/null +++ b/examples/bridge-firewall/inventory.yml @@ -0,0 +1,13 @@ +--- +# docs.vyos.io/en/1.5/configexamples/fwall-and-bridge.html +all: + children: + bridge: + hosts: + r1: + ansible_host: clab-bridge-firewall-r1 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/bridge-firewall/site.yml b/examples/bridge-firewall/site.yml new file mode 100644 index 0000000..c8ac898 --- /dev/null +++ b/examples/bridge-firewall/site.yml @@ -0,0 +1,12 @@ +--- +- name: Bridges and firewall + hosts: bridge + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.bridge_firewall + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/bridge-firewall/topology.clab.yml b/examples/bridge-firewall/topology.clab.yml new file mode 100644 index 0000000..14b9b55 --- /dev/null +++ b/examples/bridge-firewall/topology.clab.yml @@ -0,0 +1,63 @@ +name: bridge-firewall +topology: + nodes: + r1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:latest} + h1: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.0.1/24 dev eth1 + - ip -6 addr add fd00:b0::1/64 dev eth1 + h2: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.0.2/24 dev eth1 + - ip -6 addr add fd00:b0::2/64 dev eth1 + h3: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.1.1.102/24 dev eth1 + - ip route replace default via 10.1.1.1 + h4: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.1.1.103/24 dev eth1 + - ip route replace default via 10.1.1.1 + h5: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.5/24 dev eth1 + - ip route replace default via 10.2.2.1 + h6: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.6/24 dev eth1 + - ip route replace default via 10.2.2.1 + h7: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.7/24 dev eth1 + - ip route replace default via 10.2.2.1 + wan: + kind: linux + image: alpine:3 + exec: + - ip addr add 203.0.113.100/24 dev eth1 + - ip route replace default via 203.0.113.1 + links: + - endpoints: ["r1:eth1", "h1:eth1"] + - endpoints: ["r1:eth2", "h2:eth1"] + - endpoints: ["r1:eth3", "h3:eth1"] + - endpoints: ["r1:eth4", "h4:eth1"] + - endpoints: ["r1:eth5", "h5:eth1"] + - endpoints: ["r1:eth6", "h6:eth1"] + - endpoints: ["r1:eth7", "h7:eth1"] + - endpoints: ["r1:eth8", "wan:eth1"] diff --git a/examples/bridge-firewall/verify.yml b/examples/bridge-firewall/verify.yml new file mode 100644 index 0000000..1af9772 --- /dev/null +++ b/examples/bridge-firewall/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the bridges + hosts: bridge + gather_facts: false + tasks: + - name: Bridge checks + ansible.builtin.include_role: + name: vyos.blueprints.bridge_firewall + tasks_from: verify diff --git a/examples/flexvpn-cisco/group_vars/spoke.yml b/examples/flexvpn-cisco/group_vars/spoke.yml new file mode 100644 index 0000000..c0c5dd0 --- /dev/null +++ b/examples/flexvpn-cisco/group_vars/spoke.yml @@ -0,0 +1,44 @@ +--- +# docs.vyos.io/en/1.5/configexamples/site-2-site-cisco.html - VyOS side +gre_tunnel_interfaces: + - name: tun1 + encapsulation: gre + adjust_mss: "1336" + mtu: 1376 + remote: 10.1.1.6 + source_address: 198.51.100.1 + +ipsec_policy_based_peers: + - name: cisco_hub + psk_name: vyos_cisco_l + psk: secret # use ansible-vault for real devices + local_address: 198.51.100.1 + remote_address: 10.1.1.6 + local_id: vyos.net + remote_id: cisco.hub.net + connection_type: initiate + virtual_address: 0.0.0.0 + tunnels: + - {id: 1, local_prefix: 198.51.100.1/32, remote_prefix: 10.1.1.6/32, protocol: gre} +ipsec_policy_based_ike_group: + name: i1 + key_exchange: ikev2 + lifetime: 28800 + proposal_id: 1 + dh_group: 5 + encryption: aes256 + hash: sha256 +ipsec_policy_based_esp_group: + name: e1 + lifetime: 3600 + mode: tunnel + pfs: disable + proposal_id: 1 + encryption: aes128 + hash: sha256 +ipsec_policy_based_interfaces: [eth2] # WAN interface (198.51.100.1/24 on the page) +ipsec_policy_based_options: + disable_route_autoinstall: true + flexvpn: true + interface: tun1 + virtual_ip: true diff --git a/examples/flexvpn-cisco/inventory.yml b/examples/flexvpn-cisco/inventory.yml new file mode 100644 index 0000000..9368dca --- /dev/null +++ b/examples/flexvpn-cisco/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/site-2-site-cisco.html +# Only the VyOS spoke is configured here; the Cisco FlexVPN hub is configured +# as shown on the page (there is no lab stand-in: VyOS cannot act as a FlexVPN hub). +all: + children: + spoke: + hosts: + vyos: + ansible_host: 192.0.2.10 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_password: vyos # use ansible-vault for real devices diff --git a/examples/flexvpn-cisco/site.yml b/examples/flexvpn-cisco/site.yml new file mode 100644 index 0000000..6204e3f --- /dev/null +++ b/examples/flexvpn-cisco/site.yml @@ -0,0 +1,12 @@ +--- +- name: FlexVPN spoke - GRE over IPsec to a Cisco hub + hosts: spoke + gather_facts: false + roles: + - vyos.blueprints.gre_tunnel + - vyos.blueprints.ipsec_policy_based + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/flexvpn-cisco/verify.yml b/examples/flexvpn-cisco/verify.yml new file mode 100644 index 0000000..e637db8 --- /dev/null +++ b/examples/flexvpn-cisco/verify.yml @@ -0,0 +1,13 @@ +--- +- name: Check the FlexVPN spoke + hosts: spoke + gather_facts: false + tasks: + - name: Tunnel interface checks + ansible.builtin.include_role: + name: vyos.blueprints.gre_tunnel + tasks_from: verify + - name: IPsec checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_policy_based + tasks_from: verify diff --git a/examples/ipsec-route-based/group_vars/vpn.yml b/examples/ipsec-route-based/group_vars/vpn.yml new file mode 100644 index 0000000..092d64b --- /dev/null +++ b/examples/ipsec-route-based/group_vars/vpn.yml @@ -0,0 +1,17 @@ +--- +# IKE and IPsec parameters from the page's tables +ipsec_route_based_ike_group: + name: IKE-GROUP + key_exchange: ikev1 + lifetime: 28800 + dh_group: 14 + encryption: aes128 + hash: sha1 + close_action: start + dead_peer_detection: {action: restart, interval: 10, timeout: 30} +ipsec_route_based_esp_group: + name: ESP-GROUP + lifetime: 3600 + pfs: disable + encryption: aes256 + hash: sha256 diff --git a/examples/ipsec-route-based/host_vars/cisco.yml b/examples/ipsec-route-based/host_vars/cisco.yml new file mode 100644 index 0000000..f536639 --- /dev/null +++ b/examples/ipsec-route-based/host_vars/cisco.yml @@ -0,0 +1,20 @@ +--- +# Lab stand-in for the page's Cisco router, using the page's Cisco values +base_interfaces: + - {name: eth1, addresses: [10.0.2.2/30]} + - {name: eth2, addresses: [192.168.10.1/24]} + - {name: eth3, addresses: [192.168.11.1/24]} +ipsec_route_based_peers: + - name: VYOS + local_address: 10.0.2.2 + remote_address: 10.0.1.2 + psk: dGVzdA== + psk_type: base64 + psk_name: AUTH-PSK + connection_type: none + vti: {interface: vti1, address: 10.100.100.2/30, mtu: 1438} +ipsec_route_based_ospf: + router_id: 1.1.1.1 + networks: [10.100.100.0/30, 192.168.10.0/24, 192.168.11.0/24] + passive_interfaces: [eth2, eth3] +ipsec_route_based_default_gateway: 10.0.2.1 diff --git a/examples/ipsec-route-based/host_vars/vyos.yml b/examples/ipsec-route-based/host_vars/vyos.yml new file mode 100644 index 0000000..8674a81 --- /dev/null +++ b/examples/ipsec-route-based/host_vars/vyos.yml @@ -0,0 +1,20 @@ +--- +# WAN is eth1 here, eth0 on the page (containerlab uses eth0 for management) +base_interfaces: + - {name: eth1, addresses: [10.0.1.2/30]} + - {name: eth2, addresses: [192.168.0.1/24]} + - {name: eth3, addresses: [192.168.1.1/24]} +ipsec_route_based_peers: + - name: CISCO + local_address: 10.0.1.2 + remote_address: 10.0.2.2 + psk: dGVzdA== # use ansible-vault for real devices + psk_type: base64 + psk_name: AUTH-PSK + connection_type: initiate + vti: {interface: vti1, address: 10.100.100.1/30, mtu: 1438} +ipsec_route_based_ospf: + router_id: 2.2.2.2 + networks: [10.100.100.0/30, 192.168.0.0/24, 192.168.1.0/24] + passive_interfaces: [eth2, eth3] +ipsec_route_based_default_gateway: 10.0.1.1 diff --git a/examples/ipsec-route-based/inventory.yml b/examples/ipsec-route-based/inventory.yml new file mode 100644 index 0000000..4d42e0d --- /dev/null +++ b/examples/ipsec-route-based/inventory.yml @@ -0,0 +1,18 @@ +--- +# docs.vyos.io/en/1.5/configexamples/ipsec-cisco-route-based.html +# "vyos" is the router this collection configures. "cisco" is a second VyOS in +# the lab standing in for the page's Cisco router; on a real network, configure +# the Cisco side as shown on the page and remove it from this inventory. +all: + children: + vpn: + hosts: + vyos: + ansible_host: clab-ipsec-route-based-vyos # your router's address + cisco: + ansible_host: clab-ipsec-route-based-cisco + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/ipsec-route-based/site.yml b/examples/ipsec-route-based/site.yml new file mode 100644 index 0000000..db7d07b --- /dev/null +++ b/examples/ipsec-route-based/site.yml @@ -0,0 +1,12 @@ +--- +- name: Route-based site-to-site IPsec + hosts: vpn + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_route_based + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/ipsec-route-based/topology.clab.yml b/examples/ipsec-route-based/topology.clab.yml new file mode 100644 index 0000000..23e49ac --- /dev/null +++ b/examples/ipsec-route-based/topology.clab.yml @@ -0,0 +1,25 @@ +name: ipsec-route-based +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:latest} + nodes: + vyos: + kind: vyosnetworks_vyos + cisco: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: ["sysctl -w net.ipv4.ip_forward=1", "ip addr add 10.0.1.1/30 dev eth1", "ip addr add 10.0.2.1/30 dev eth2"] + pc1: {kind: linux, image: "alpine:3", exec: ["ip addr add 192.168.0.2/24 dev eth1", "ip route replace default via 192.168.0.1"]} + pc2: {kind: linux, image: "alpine:3", exec: ["ip addr add 192.168.1.2/24 dev eth1", "ip route replace default via 192.168.1.1"]} + pc3: {kind: linux, image: "alpine:3", exec: ["ip addr add 192.168.10.2/24 dev eth1", "ip route replace default via 192.168.10.1"]} + pc4: {kind: linux, image: "alpine:3", exec: ["ip addr add 192.168.11.2/24 dev eth1", "ip route replace default via 192.168.11.1"]} + links: + - endpoints: ["vyos:eth1", "isp:eth1"] + - endpoints: ["cisco:eth1", "isp:eth2"] + - endpoints: ["vyos:eth2", "pc1:eth1"] + - endpoints: ["vyos:eth3", "pc2:eth1"] + - endpoints: ["cisco:eth2", "pc3:eth1"] + - endpoints: ["cisco:eth3", "pc4:eth1"] diff --git a/examples/ipsec-route-based/verify.yml b/examples/ipsec-route-based/verify.yml new file mode 100644 index 0000000..268ff9c --- /dev/null +++ b/examples/ipsec-route-based/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the VPN + hosts: vpn + gather_facts: false + tasks: + - name: IPsec and OSPF checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify diff --git a/examples/ospf-unnumbered/group_vars/fabric.yml b/examples/ospf-unnumbered/group_vars/fabric.yml new file mode 100644 index 0000000..9fa7a6c --- /dev/null +++ b/examples/ospf-unnumbered/group_vars/fabric.yml @@ -0,0 +1,4 @@ +--- +ospf_unnumbered_interfaces: [eth1, eth2] +ospf_unnumbered_md5_key: yourpassword # use ansible-vault for real devices +ospf_unnumbered_group: fabric diff --git a/examples/ospf-unnumbered/host_vars/routerA.yml b/examples/ospf-unnumbered/host_vars/routerA.yml new file mode 100644 index 0000000..e3803e6 --- /dev/null +++ b/examples/ospf-unnumbered/host_vars/routerA.yml @@ -0,0 +1,5 @@ +--- +ospf_unnumbered_router_id: 192.168.0.1 +# LAN address; eth0 on the docs page, eth3 here because containerlab uses eth0 for management +base_interfaces: + - {name: eth3, addresses: [10.0.0.1/24]} diff --git a/examples/ospf-unnumbered/host_vars/routerB.yml b/examples/ospf-unnumbered/host_vars/routerB.yml new file mode 100644 index 0000000..b2aac66 --- /dev/null +++ b/examples/ospf-unnumbered/host_vars/routerB.yml @@ -0,0 +1,4 @@ +--- +ospf_unnumbered_router_id: 192.168.0.2 +base_interfaces: + - {name: eth3, addresses: [10.0.0.2/24]} diff --git a/examples/ospf-unnumbered/inventory.yml b/examples/ospf-unnumbered/inventory.yml new file mode 100644 index 0000000..97bc8d4 --- /dev/null +++ b/examples/ospf-unnumbered/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/ospf-unnumbered.html +all: + children: + fabric: + hosts: + routerA: + ansible_host: clab-ospf-unnumbered-routerA # your router's address + routerB: + ansible_host: clab-ospf-unnumbered-routerB + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/ospf-unnumbered/site.yml b/examples/ospf-unnumbered/site.yml new file mode 100644 index 0000000..ad9bd0a --- /dev/null +++ b/examples/ospf-unnumbered/site.yml @@ -0,0 +1,12 @@ +--- +- name: OSPF unnumbered with ECMP + hosts: fabric + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ospf_unnumbered + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/ospf-unnumbered/topology.clab.yml b/examples/ospf-unnumbered/topology.clab.yml new file mode 100644 index 0000000..4b2ae23 --- /dev/null +++ b/examples/ospf-unnumbered/topology.clab.yml @@ -0,0 +1,25 @@ +name: ospf-unnumbered +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:latest} + nodes: + routerA: + kind: vyosnetworks_vyos + routerB: + kind: vyosnetworks_vyos + # shared 10.0.0.0/24 LAN, as on the docs page: a Linux bridge plus a test host + lan: + kind: linux + image: alpine:3 + exec: + - ip link add br0 type bridge + - ip link set eth1 master br0 + - ip link set eth2 master br0 + - ip link set br0 up + - ip addr add 10.0.0.10/24 dev br0 + links: + - endpoints: ["routerA:eth1", "routerB:eth1"] + - endpoints: ["routerA:eth2", "routerB:eth2"] + - endpoints: ["routerA:eth3", "lan:eth1"] + - endpoints: ["routerB:eth3", "lan:eth2"] diff --git a/examples/ospf-unnumbered/verify.yml b/examples/ospf-unnumbered/verify.yml new file mode 100644 index 0000000..f5b7b6f --- /dev/null +++ b/examples/ospf-unnumbered/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the OSPF fabric + hosts: fabric + gather_facts: false + tasks: + - name: OSPF checks (all routers in one play for the route checks) + ansible.builtin.include_role: + name: vyos.blueprints.ospf_unnumbered + tasks_from: verify diff --git a/examples/policy-ipsec-firewall/group_vars/sites.yml b/examples/policy-ipsec-firewall/group_vars/sites.yml new file mode 100644 index 0000000..72cb49a --- /dev/null +++ b/examples/policy-ipsec-firewall/group_vars/sites.yml @@ -0,0 +1,5 @@ +--- +# IPsec settings shared by both routers, as on the page +ipsec_policy_based_ike_group: {name: IKE-GROUP, key_exchange: ikev2, proposal_id: 1, dh_group: 14, encryption: aes256, hash: sha256} +ipsec_policy_based_esp_group: {name: ESP-GROUP, mode: tunnel, proposal_id: 1, encryption: aes256, hash: sha256} +ipsec_policy_based_interfaces: [eth3] diff --git a/examples/policy-ipsec-firewall/host_vars/left.yml b/examples/policy-ipsec-firewall/host_vars/left.yml new file mode 100644 index 0000000..e6fe1ee --- /dev/null +++ b/examples/policy-ipsec-firewall/host_vars/left.yml @@ -0,0 +1,98 @@ +--- +# WAN is eth3 here, eth0 on the page (containerlab uses eth0 for management). +# Input rule 5 keeps containerlab management reachable - drop it on a real router +# and make sure your management source is allowed (rule 20, TRUSTED) instead. +ipsec_policy_based_peers: + - name: RIGHT + psk_name: RIGHT + psk: p4ssw0rd # use ansible-vault for real devices + authentication_ids: false + local_address: 198.51.100.14 + remote_address: 192.0.2.130 + connection_type: initiate + tunnels: + - {id: 0, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.21.0/24} + - {id: 1, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.22.0/24} + - {id: 2, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.21.0/24} + - {id: 3, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.22.0/24} +ipsec_policy_based_default_gateway: 198.51.100.13 +firewall_groups: + network: + - name: LOCAL-NETS + networks: [10.1.11.0/24, 10.1.12.0/24] + - name: REMOTE-NETS + networks: [10.2.21.0/24, 10.2.22.0/24] + - name: TRUSTED + networks: [198.51.100.125/32, 203.0.113.0/24, 10.1.11.0/24, 192.168.70.0/24] +firewall_ipv4: + forward: + default_action: drop + rules: + - number: 1 + action: accept + state: {established: true, related: true} + - number: 2 + action: drop + state: {invalid: true} + - number: 10 + action: accept + source: + group: {network_group: LOCAL-NETS} + - number: 20 + action: accept + source: + group: {network_group: REMOTE-NETS} + destination: + group: {network_group: LOCAL-NETS} + input: + default_action: drop + rules: + - number: 1 + action: accept + state: {established: true, related: true} + - number: 2 + action: drop + state: {invalid: true} + - number: 5 + action: accept + description: containerlab management + inbound_interface: {name: eth0} + - number: 10 + action: accept + protocol: udp + destination: {port: '500,4500'} + inbound_interface: {name: eth3} + - number: 15 + action: accept + protocol: esp + inbound_interface: {name: eth3} + - number: 20 + action: accept + protocol: tcp + destination: {port: '22'} + source: + group: {network_group: TRUSTED} + - number: 25 + action: accept + protocol: udp + destination: {port: '53'} + source: + group: {network_group: LOCAL-NETS} + - {number: 30, action: accept, protocol: icmp} +nat_source_rules: + - id: 10 + exclude: true + outbound_interface: {name: eth3} + source: {network_group: LOCAL-NETS} + destination: {network_group: REMOTE-NETS} + - id: 20 + outbound_interface: {name: eth3} + source: {network_group: LOCAL-NETS} + translation: {address: masquerade} +base_interfaces: + - name: eth3 + addresses: [198.51.100.14/30] + - name: eth1.111 + addresses: [10.1.11.1/24] + - name: eth2.112 + addresses: [10.1.12.1/24] diff --git a/examples/policy-ipsec-firewall/host_vars/right.yml b/examples/policy-ipsec-firewall/host_vars/right.yml new file mode 100644 index 0000000..ab629da --- /dev/null +++ b/examples/policy-ipsec-firewall/host_vars/right.yml @@ -0,0 +1,23 @@ +--- +# The page sets no default route on RIGHT; it needs one to reach LEFT. +ipsec_policy_based_peers: + - name: LEFT + psk_name: LEFT + psk: p4ssw0rd # use ansible-vault for real devices + authentication_ids: false + local_address: 192.0.2.130 + remote_address: 198.51.100.14 + connection_type: none + tunnels: + - {id: 0, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.11.0/24} + - {id: 1, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.11.0/24} + - {id: 2, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.12.0/24} + - {id: 3, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.12.0/24} +base_interfaces: + - name: eth3 + addresses: [192.0.2.130/30] + - name: eth1.221 + addresses: [10.2.21.1/24] + - name: eth2.222 + addresses: [10.2.22.1/24] +ipsec_policy_based_default_gateway: 192.0.2.129 diff --git a/examples/policy-ipsec-firewall/inventory.yml b/examples/policy-ipsec-firewall/inventory.yml new file mode 100644 index 0000000..95eeddb --- /dev/null +++ b/examples/policy-ipsec-firewall/inventory.yml @@ -0,0 +1,15 @@ +--- +# docs.vyos.io/en/1.5/configexamples/policy-based-ipsec-and-firewall.html +all: + children: + sites: + hosts: + left: + ansible_host: clab-policy-ipsec-firewall-left # your router's address + right: + ansible_host: clab-policy-ipsec-firewall-right + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/policy-ipsec-firewall/site.yml b/examples/policy-ipsec-firewall/site.yml new file mode 100644 index 0000000..87ea9a5 --- /dev/null +++ b/examples/policy-ipsec-firewall/site.yml @@ -0,0 +1,15 @@ +--- +- name: Policy-based IPsec with firewall and NAT + hosts: sites + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_policy_based + # firewall and nat only have inputs on LEFT, as on the page; on RIGHT they do nothing + - vyos.blueprints.firewall + - vyos.blueprints.nat + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/policy-ipsec-firewall/topology.clab.yml b/examples/policy-ipsec-firewall/topology.clab.yml new file mode 100644 index 0000000..e512c9c --- /dev/null +++ b/examples/policy-ipsec-firewall/topology.clab.yml @@ -0,0 +1,51 @@ +name: policy-ipsec-firewall +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:latest} + nodes: + left: + kind: vyosnetworks_vyos + right: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: + - sysctl -w net.ipv4.ip_forward=1 + - ip addr add 198.51.100.13/30 dev eth1 + - ip addr add 192.0.2.129/30 dev eth2 + - ip addr add 203.0.113.1/24 dev eth3 + internet: + kind: linux + image: alpine:3 + exec: ["ip addr add 203.0.113.10/24 dev eth1", "ip route replace default via 203.0.113.1"] + l11: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.111 type vlan id 111", "ip link set eth1.111 up", + "ip addr add 10.1.11.10/24 dev eth1.111", "ip route replace default via 10.1.11.1"] + l12: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.112 type vlan id 112", "ip link set eth1.112 up", + "ip addr add 10.1.12.10/24 dev eth1.112", "ip route replace default via 10.1.12.1"] + r21: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.221 type vlan id 221", "ip link set eth1.221 up", + "ip addr add 10.2.21.10/24 dev eth1.221", "ip route replace default via 10.2.21.1"] + r22: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.222 type vlan id 222", "ip link set eth1.222 up", + "ip addr add 10.2.22.10/24 dev eth1.222", "ip route replace default via 10.2.22.1"] + links: + # WAN is eth3 here (eth0 on the page; eth0 is containerlab management) + - endpoints: ["left:eth3", "isp:eth1"] + - endpoints: ["right:eth3", "isp:eth2"] + - endpoints: ["isp:eth3", "internet:eth1"] + - endpoints: ["left:eth1", "l11:eth1"] + - endpoints: ["left:eth2", "l12:eth1"] + - endpoints: ["right:eth1", "r21:eth1"] + - endpoints: ["right:eth2", "r22:eth1"] diff --git a/examples/policy-ipsec-firewall/verify.yml b/examples/policy-ipsec-firewall/verify.yml new file mode 100644 index 0000000..d1006cf --- /dev/null +++ b/examples/policy-ipsec-firewall/verify.yml @@ -0,0 +1,22 @@ +--- +- name: Check both sites + hosts: sites + gather_facts: false + tasks: + - name: IPsec checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_policy_based + tasks_from: verify + +- name: Check firewall and NAT on LEFT + hosts: left + gather_facts: false + tasks: + - name: Firewall checks + ansible.builtin.include_role: + name: vyos.blueprints.firewall + tasks_from: verify + - name: NAT checks + ansible.builtin.include_role: + name: vyos.blueprints.nat + tasks_from: verify diff --git a/examples/vrf-firewall/group_vars/router.yml b/examples/vrf-firewall/group_vars/router.yml new file mode 100644 index 0000000..3045bce --- /dev/null +++ b/examples/vrf-firewall/group_vars/router.yml @@ -0,0 +1,51 @@ +--- +# The page's WAN is PPPoE (pppoe0); this example uses eth3 instead, because +# PPPoE needs an access concentrator. Swap eth3 back to pppoe0 on a real router. +base_interfaces: + - {name: eth1, addresses: [10.100.100.1/24]} + - {name: eth2} + - {name: eth2.150, addresses: [10.150.150.1/24]} + - {name: eth2.160, addresses: [10.160.160.1/24]} + - {name: eth2.3500, addresses: [172.16.20.1/24]} + - {name: eth3, addresses: [203.0.113.1/24]} +vrf_firewall_vrfs: + - name: MGMT + table: 102 + interfaces: [eth1] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: WAN + table: 101 + interfaces: [eth3] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: LAN + table: 103 + interfaces: [eth2.150, eth2.160] + routes: + - {dest: 0.0.0.0/0, interface: eth3, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: PROD + table: 104 + interfaces: [eth2.3500] + routes: + - {dest: 0.0.0.0/0, interface: eth3, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + +vrf_firewall_forward_rules: + - {number: 10, description: MGMT - Allow to LAN and PROD, inbound_interface: MGMT, outbound_interface: eth2*} + - {number: 99, action: drop, description: MGMT - Drop all going to mgmt, outbound_interface: eth1} + - {number: 120, description: LAN - Allow to PROD, inbound_interface: LAN, outbound_interface: eth2.3500} + - {number: 130, description: LAN - Allow internet, inbound_interface: LAN, outbound_interface: eth3} + +vrf_firewall_input_rules: + # containerlab management only - on a real router the page allows input from MGMT only + - {number: 5, description: containerlab management, inbound_interface: eth0} + - {number: 10, description: MGMT - Allow input, inbound_interface: MGMT} diff --git a/examples/vrf-firewall/inventory.yml b/examples/vrf-firewall/inventory.yml new file mode 100644 index 0000000..f3e6b10 --- /dev/null +++ b/examples/vrf-firewall/inventory.yml @@ -0,0 +1,13 @@ +--- +# docs.vyos.io/en/1.5/configexamples/fwall-and-vrf.html +all: + children: + router: + hosts: + r1: + ansible_host: clab-vrf-firewall-r1 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/vrf-firewall/site.yml b/examples/vrf-firewall/site.yml new file mode 100644 index 0000000..0f0b66a --- /dev/null +++ b/examples/vrf-firewall/site.yml @@ -0,0 +1,12 @@ +--- +- name: VRFs and firewall + hosts: router + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.vrf_firewall + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/vrf-firewall/topology.clab.yml b/examples/vrf-firewall/topology.clab.yml new file mode 100644 index 0000000..0612a1f --- /dev/null +++ b/examples/vrf-firewall/topology.clab.yml @@ -0,0 +1,43 @@ +name: vrf-firewall +topology: + nodes: + r1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:latest} + mgmt: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.100.100.10/24 dev eth1", "ip route replace default via 10.100.100.1"] + # VLAN-aware bridge for the eth2 trunk (vif 150 = LAN, vif 3500 = PROD) + sw: + kind: linux + image: alpine:3 + exec: + - ip link add br0 type bridge vlan_filtering 1 + - ip link set eth1 master br0 + - ip link set eth2 master br0 + - ip link set eth3 master br0 + - bridge vlan add dev eth1 vid 150 + - bridge vlan add dev eth1 vid 3500 + - bridge vlan add dev eth2 vid 150 pvid untagged + - bridge vlan add dev eth3 vid 3500 pvid untagged + - ip link set br0 up + lan: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.150.150.10/24 dev eth1", "ip route replace default via 10.150.150.1"] + prod: + kind: linux + image: alpine:3 + exec: ["ip addr add 172.16.20.10/24 dev eth1", "ip route replace default via 172.16.20.1"] + # stands in for the page's PPPoE uplink + wan: + kind: linux + image: alpine:3 + exec: ["ip addr add 203.0.113.100/24 dev eth1", "ip route replace default via 203.0.113.1"] + links: + - endpoints: ["r1:eth1", "mgmt:eth1"] + - endpoints: ["r1:eth2", "sw:eth1"] + - endpoints: ["sw:eth2", "lan:eth1"] + - endpoints: ["sw:eth3", "prod:eth1"] + - endpoints: ["r1:eth3", "wan:eth1"] diff --git a/examples/vrf-firewall/verify.yml b/examples/vrf-firewall/verify.yml new file mode 100644 index 0000000..6a66c8f --- /dev/null +++ b/examples/vrf-firewall/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the VRFs + hosts: router + gather_facts: false + tasks: + - name: VRF checks + ansible.builtin.include_role: + name: vyos.blueprints.vrf_firewall + tasks_from: verify diff --git a/examples/zone-policy/group_vars/firewall.yml b/examples/zone-policy/group_vars/firewall.yml new file mode 100644 index 0000000..13c0188 --- /dev/null +++ b/examples/zone-policy/group_vars/firewall.yml @@ -0,0 +1,80 @@ +--- +# Router on a stick, as on the docs page. The page trunks VLANs 10/20/30 on +# eth0; this example uses eth1 because containerlab reserves eth0 for +# management. +base_interfaces: + - {name: eth1, description: trunk} + - {name: eth1.10, description: WAN, addresses: [172.16.10.1/24, "2001:db8:0:9999::1/64"]} + - {name: eth1.20, description: LAN, addresses: [192.168.100.1/24, "2001:db8:0:aaaa::1/64"]} + - {name: eth1.30, description: DMZ, addresses: [192.168.200.1/24, "2001:db8:0:bbbb::1/64"]} + +zone_firewall_zones: + # mgmt exists only so Ansible keeps its SSH session in containerlab. On a real + # router follow the page: the admin console (192.168.100.10) reaches the router + # through the lan-local rule 800, so make sure that rule is in place first. + - {name: mgmt, interfaces: [eth0]} + - {name: wan, interfaces: [eth1.10]} + - {name: lan, interfaces: [eth1.20]} + - {name: dmz, interfaces: [eth1.30]} + - {name: local, local: true} + +zone_firewall_policies: + - {from: mgmt, to: local, default_action: accept} + - from: wan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {address: 192.168.200.200, port: "80,443"}} + - {number: 200, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "80,443"}} + - {number: 500, protocol: tcp, destination: {address: 192.168.200.200, port: "25"}} + - {number: 500, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "25"}} + - {number: 600, protocol: tcp, destination: {address: 192.168.200.200, port: "53"}} + - {number: 600, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "53"}} + - from: dmz + to: local + rules: + - {number: 400, protocol: tcp, destination: {port: "123"}} + - {number: 600, protocol: tcp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: lan + to: local + rules: + - {number: 400, protocol: tcp, destination: {port: "123"}} + - {number: 600, protocol: tcp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - {number: 800, protocol: tcp, source: {address: 192.168.100.10}, destination: {port: "22"}} + - {number: 800, protocol: tcp, source: {address: "2001:db8:0:aaaa::10"}, destination: {port: "22"}} + - from: lan + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - {number: 800, protocol: tcp, destination: {port: "22"}} + - from: dmz + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - {number: 600, protocol: tcp_udp, destination: {port: "53"}} + - {number: 800, protocol: tcp, destination: {port: "22"}} + - from: local + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - from: local + to: dmz + rules: + - {number: 500, protocol: tcp, destination: {port: "25"}} + - {number: 600, protocol: tcp_udp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: local + to: lan + rules: + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: lan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 800, protocol: tcp, source: {address: 192.168.100.10}, destination: {address: 192.168.200.200, port: "22"}} + - {number: 800, protocol: tcp, source: {address: "2001:db8:0:aaaa::10"}, destination: {address: "2001:db8:0:bbbb::200", port: "22"}} + - {number: 900, protocol: tcp, destination: {port: "993"}} diff --git a/examples/zone-policy/inventory.yml b/examples/zone-policy/inventory.yml new file mode 100644 index 0000000..206412d --- /dev/null +++ b/examples/zone-policy/inventory.yml @@ -0,0 +1,13 @@ +--- +# docs.vyos.io/en/1.5/configexamples/zone-policy.html +all: + children: + firewall: + hosts: + fw1: + ansible_host: clab-zone-policy-fw1 # your router's address + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin # use ansible-vault for real devices diff --git a/examples/zone-policy/site.yml b/examples/zone-policy/site.yml new file mode 100644 index 0000000..4d82cdf --- /dev/null +++ b/examples/zone-policy/site.yml @@ -0,0 +1,12 @@ +--- +- name: Zone-based firewall + hosts: firewall + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.zone_firewall + post_tasks: + - name: Show rendered commands + ansible.builtin.debug: + var: vyos_blueprints_rendered + when: show_rendered | default(false) | bool diff --git a/examples/zone-policy/topology.clab.yml b/examples/zone-policy/topology.clab.yml new file mode 100644 index 0000000..63080d2 --- /dev/null +++ b/examples/zone-policy/topology.clab.yml @@ -0,0 +1,40 @@ +name: zone-policy +topology: + nodes: + fw1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:latest} + # one host per network on its own VLAN, behind a VLAN-aware bridge + sw: + kind: linux + image: alpine:3 + exec: + - ip link add br0 type bridge vlan_filtering 1 + - ip link set eth1 master br0 + - ip link set eth2 master br0 + - ip link set eth3 master br0 + - ip link set eth4 master br0 + - bridge vlan add dev eth1 vid 10 + - bridge vlan add dev eth1 vid 20 + - bridge vlan add dev eth1 vid 30 + - bridge vlan add dev eth2 vid 10 pvid untagged + - bridge vlan add dev eth3 vid 20 pvid untagged + - bridge vlan add dev eth4 vid 30 pvid untagged + - ip link set br0 up + wan: + kind: linux + image: alpine:3 + exec: ["ip addr add 172.16.10.100/24 dev eth1", "ip route replace default via 172.16.10.1"] + lan: + kind: linux + image: alpine:3 + exec: ["ip addr add 192.168.100.10/24 dev eth1", "ip route replace default via 192.168.100.1"] + dmz: + kind: linux + image: alpine:3 + exec: ["ip addr add 192.168.200.200/24 dev eth1", "ip route replace default via 192.168.200.1"] + links: + - endpoints: ["fw1:eth1", "sw:eth1"] + - endpoints: ["sw:eth2", "wan:eth1"] + - endpoints: ["sw:eth3", "lan:eth1"] + - endpoints: ["sw:eth4", "dmz:eth1"] diff --git a/examples/zone-policy/verify.yml b/examples/zone-policy/verify.yml new file mode 100644 index 0000000..3132524 --- /dev/null +++ b/examples/zone-policy/verify.yml @@ -0,0 +1,9 @@ +--- +- name: Check the zone firewall + hosts: firewall + gather_facts: false + tasks: + - name: Zone checks + ansible.builtin.include_role: + name: vyos.blueprints.zone_firewall + tasks_from: verify diff --git a/extensions/molecule/bgp_unnumbered/converge.yml b/extensions/molecule/bgp_unnumbered/converge.yml new file mode 100644 index 0000000..3b3260a --- /dev/null +++ b/extensions/molecule/bgp_unnumbered/converge.yml @@ -0,0 +1,6 @@ +--- +- name: Converge + hosts: fabric + gather_facts: false + roles: + - vyos.blueprints.bgp_unnumbered diff --git a/extensions/molecule/bgp_unnumbered/inventory.yml b/extensions/molecule/bgp_unnumbered/inventory.yml new file mode 100644 index 0000000..1785087 --- /dev/null +++ b/extensions/molecule/bgp_unnumbered/inventory.yml @@ -0,0 +1,23 @@ +--- +all: + children: + vyos: + children: + fabric: + hosts: + r1: + ansible_host: clab-bp-bgp-r1 + bgp_unnumbered_asn: 64496 + bgp_unnumbered_router_id: 192.168.0.1 + r2: + ansible_host: clab-bp-bgp-r2 + bgp_unnumbered_asn: 64499 + bgp_unnumbered_router_id: 192.168.0.2 + vars: + bgp_unnumbered_interfaces: [eth1, eth2] + bgp_unnumbered_group: fabric + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin diff --git a/extensions/molecule/bgp_unnumbered/molecule.yml b/extensions/molecule/bgp_unnumbered/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/bgp_unnumbered/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/bgp_unnumbered/topology.clab.yml b/extensions/molecule/bgp_unnumbered/topology.clab.yml new file mode 100644 index 0000000..a370c2f --- /dev/null +++ b/extensions/molecule/bgp_unnumbered/topology.clab.yml @@ -0,0 +1,14 @@ +name: bp-bgp +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + r1: + kind: vyosnetworks_vyos + r2: + kind: vyosnetworks_vyos + links: + # two parallel links without IP addresses; BGP peers over IPv6 link-local + - endpoints: ["r1:eth1", "r2:eth1"] + - endpoints: ["r1:eth2", "r2:eth2"] diff --git a/extensions/molecule/bgp_unnumbered/verify.yml b/extensions/molecule/bgp_unnumbered/verify.yml new file mode 100644 index 0000000..3f251af --- /dev/null +++ b/extensions/molecule/bgp_unnumbered/verify.yml @@ -0,0 +1,20 @@ +--- +- name: Verify + hosts: fabric + gather_facts: false + tasks: + - name: Wait for BGP sessions and routes + ansible.builtin.pause: + seconds: 45 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.bgp_unnumbered + tasks_from: verify + + - name: Ping the other router's loopback from this loopback, as on the docs page + vyos.vyos.vyos_ping: + dest: "{{ '192.168.0.2' if inventory_hostname == 'r1' else '192.168.0.1' }}" + source: "{{ bgp_unnumbered_router_id }}" + count: 3 diff --git a/extensions/molecule/bridge_firewall/converge.yml b/extensions/molecule/bridge_firewall/converge.yml new file mode 100644 index 0000000..89558a3 --- /dev/null +++ b/extensions/molecule/bridge_firewall/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vyos + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.bridge_firewall diff --git a/extensions/molecule/bridge_firewall/inventory.yml b/extensions/molecule/bridge_firewall/inventory.yml new file mode 100644 index 0000000..dface0d --- /dev/null +++ b/extensions/molecule/bridge_firewall/inventory.yml @@ -0,0 +1,94 @@ +--- +all: + children: + vyos: + hosts: + r1: + ansible_host: clab-bp-bridge-r1 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin + # internet uplink: eth8 here, eth0 on the docs page (management in containerlab) + base_interfaces: + - {name: eth8, addresses: [203.0.113.1/24]} + bridge_firewall_bridges: + - name: br0 + description: Isolated L2 bridge + members: + - {interface: eth1, description: br0} + - {interface: eth2, description: br0} + prerouting: + default_action: drop + rules: + - {number: 10, description: Accept IPv6 traffic, ethernet_type: ipv6} + forward: + default_action: accept + + - name: br1 + description: L3 bridge br1 + addresses: [10.1.1.1/24] + members: + - {interface: eth3, description: br1} + - {interface: eth4, description: br1} + prerouting: + default_action: accept + rules: + - number: 10 + description: Drop DHCP discover + action: drop + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + log: true + - {number: 20, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - {number: 10, description: Accept ARP, ethernet_type: arp} + - {number: 20, description: Accept ipv4 from host, source: {address: 10.1.1.102}, state: [new]} + ip_forward: + rules: + - {number: 10, description: br1 - allow internet access, outbound_interface: eth8} + router_access: accept + + - name: br2 + description: L3 bridge br2 + addresses: [10.2.2.1/24] + members: + - {interface: eth5, description: br2 - Host} + - {interface: eth6, description: br2 - Trusted DHCP Server} + - {interface: eth7, description: br2} + prerouting: + default_action: accept + rules: + - {number: 10, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - number: 10 + description: Accept DHCP discover + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + - number: 20 + description: Accept DHCP offers from trusted interface + protocol: udp + source: {port: "67"} + destination: {port: "68"} + inbound_interface: eth6 + - number: 22 + description: Drop all other DHCP offers + action: drop + protocol: udp + source: {port: "67"} + destination: {port: "68"} + log: true + - {number: 30, description: Accept ARP, ethernet_type: arp} + - {number: 40, description: Accept ipv4, ethernet_type: ipv4} + ip_forward: + rules: + - {number: 10, description: br2 - allow internet access, outbound_interface: eth8} + - {number: 20, description: br2 - allow access to br1, outbound_bridge: br1} + router_access: drop diff --git a/extensions/molecule/bridge_firewall/molecule.yml b/extensions/molecule/bridge_firewall/molecule.yml new file mode 100644 index 0000000..612af0a --- /dev/null +++ b/extensions/molecule/bridge_firewall/molecule.yml @@ -0,0 +1,27 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/bridge_firewall/topology.clab.yml b/extensions/molecule/bridge_firewall/topology.clab.yml new file mode 100644 index 0000000..1b70e89 --- /dev/null +++ b/extensions/molecule/bridge_firewall/topology.clab.yml @@ -0,0 +1,63 @@ +name: bp-bridge +topology: + nodes: + r1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + h1: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.0.1/24 dev eth1 + - ip -6 addr add fd00:b0::1/64 dev eth1 + h2: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.0.2/24 dev eth1 + - ip -6 addr add fd00:b0::2/64 dev eth1 + h3: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.1.1.102/24 dev eth1 + - ip route replace default via 10.1.1.1 + h4: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.1.1.103/24 dev eth1 + - ip route replace default via 10.1.1.1 + h5: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.5/24 dev eth1 + - ip route replace default via 10.2.2.1 + h6: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.6/24 dev eth1 + - ip route replace default via 10.2.2.1 + h7: + kind: linux + image: alpine:3 + exec: + - ip addr add 10.2.2.7/24 dev eth1 + - ip route replace default via 10.2.2.1 + wan: + kind: linux + image: alpine:3 + exec: + - ip addr add 203.0.113.100/24 dev eth1 + - ip route replace default via 203.0.113.1 + links: + - endpoints: ["r1:eth1", "h1:eth1"] + - endpoints: ["r1:eth2", "h2:eth1"] + - endpoints: ["r1:eth3", "h3:eth1"] + - endpoints: ["r1:eth4", "h4:eth1"] + - endpoints: ["r1:eth5", "h5:eth1"] + - endpoints: ["r1:eth6", "h6:eth1"] + - endpoints: ["r1:eth7", "h7:eth1"] + - endpoints: ["r1:eth8", "wan:eth1"] diff --git a/extensions/molecule/bridge_firewall/verify.yml b/extensions/molecule/bridge_firewall/verify.yml new file mode 100644 index 0000000..dd2f042 --- /dev/null +++ b/extensions/molecule/bridge_firewall/verify.yml @@ -0,0 +1,37 @@ +--- +- name: Verify role state + hosts: vyos + gather_facts: false + tasks: + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.bridge_firewall + tasks_from: verify + +- name: Verify the page's requirements with traffic + hosts: localhost + gather_facts: false + vars: + _flows: + - {from: h1, ping: "-6 fd00:b0::2", allowed: true, why: "br0 accepts IPv6 within the bridge"} + - {from: h1, ping: "192.168.0.2", allowed: false, why: "br0 drops everything but IPv6"} + - {from: h3, ping: "10.1.1.103", allowed: true, why: "br1 accepts new IPv4 from 10.1.1.102"} + - {from: h4, ping: "10.1.1.102", allowed: false, why: "br1 drops other IPv4 connections"} + - {from: h3, ping: "10.1.1.1", allowed: true, why: "br1 may access the router"} + - {from: h3, ping: "203.0.113.100", allowed: true, why: "br1 may reach the internet"} + - {from: h3, ping: "10.2.2.5", allowed: false, why: "br1 may not reach other LANs"} + - {from: h5, ping: "10.2.2.7", allowed: true, why: "br2 accepts all IPv4"} + - {from: h5, ping: "10.2.2.1", allowed: false, why: "br2 may not access the router"} + - {from: h5, ping: "203.0.113.100", allowed: true, why: "br2 may reach the internet"} + - {from: h5, ping: "10.1.1.102", allowed: true, why: "br2 may connect to br1"} + tasks: + - name: Ping per requirement + ansible.builtin.command: + cmd: docker exec clab-bp-bridge-{{ item.from }} ping -c 2 -W 2 {{ item.ping }} + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + register: _ping + changed_when: false + failed_when: (_ping.rc == 0) != item.allowed + loop: "{{ _flows }}" + loop_control: + label: "{{ item.from }} -> {{ item.ping }} ({{ item.why }})" diff --git a/extensions/molecule/gre_tunnel/converge.yml b/extensions/molecule/gre_tunnel/converge.yml new file mode 100644 index 0000000..913e659 --- /dev/null +++ b/extensions/molecule/gre_tunnel/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: gre + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.gre_tunnel diff --git a/extensions/molecule/gre_tunnel/inventory.yml b/extensions/molecule/gre_tunnel/inventory.yml new file mode 100644 index 0000000..191ed70 --- /dev/null +++ b/extensions/molecule/gre_tunnel/inventory.yml @@ -0,0 +1,24 @@ +--- +all: + children: + vyos: + children: + gre: + hosts: + r1: + ansible_host: clab-bp-gre-r1 + base_interfaces: + - {name: eth1, addresses: [198.51.100.1/24]} + gre_tunnel_interfaces: + - {name: tun1, source_address: 198.51.100.1, remote: 198.51.100.2, mtu: 1376, adjust_mss: "1336", addresses: [172.16.122.1/30]} + r2: + ansible_host: clab-bp-gre-r2 + base_interfaces: + - {name: eth1, addresses: [198.51.100.2/24]} + gre_tunnel_interfaces: + - {name: tun1, source_address: 198.51.100.2, remote: 198.51.100.1, mtu: 1376, adjust_mss: "1336", addresses: [172.16.122.2/30]} + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin diff --git a/extensions/molecule/gre_tunnel/molecule.yml b/extensions/molecule/gre_tunnel/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/gre_tunnel/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/gre_tunnel/topology.clab.yml b/extensions/molecule/gre_tunnel/topology.clab.yml new file mode 100644 index 0000000..7a5e955 --- /dev/null +++ b/extensions/molecule/gre_tunnel/topology.clab.yml @@ -0,0 +1,12 @@ +name: bp-gre +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + r1: + kind: vyosnetworks_vyos + r2: + kind: vyosnetworks_vyos + links: + - endpoints: ["r1:eth1", "r2:eth1"] diff --git a/extensions/molecule/gre_tunnel/verify.yml b/extensions/molecule/gre_tunnel/verify.yml new file mode 100644 index 0000000..88f2442 --- /dev/null +++ b/extensions/molecule/gre_tunnel/verify.yml @@ -0,0 +1,14 @@ +--- +- name: Verify + hosts: gre + gather_facts: false + tasks: + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.gre_tunnel + tasks_from: verify + + - name: Ping the other end through the tunnel + vyos.vyos.vyos_ping: + dest: "{{ '172.16.122.2' if inventory_hostname == 'r1' else '172.16.122.1' }}" + count: 3 diff --git a/extensions/molecule/ipsec_route_based/converge.yml b/extensions/molecule/ipsec_route_based/converge.yml new file mode 100644 index 0000000..0fb7a50 --- /dev/null +++ b/extensions/molecule/ipsec_route_based/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vpn + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_route_based diff --git a/extensions/molecule/ipsec_route_based/inventory.yml b/extensions/molecule/ipsec_route_based/inventory.yml new file mode 100644 index 0000000..92b460c --- /dev/null +++ b/extensions/molecule/ipsec_route_based/inventory.yml @@ -0,0 +1,61 @@ +--- +all: + children: + vyos: + children: + vpn: + hosts: + r1: + ansible_host: clab-bp-ipsec-vyos + base_interfaces: + - {name: eth1, addresses: [10.0.1.2/30]} + - {name: eth2, addresses: [192.168.0.1/24]} + ipsec_route_based_peers: + - name: CISCO + local_address: 10.0.1.2 + remote_address: 10.0.2.2 + psk: molecule-psk + connection_type: initiate + vti: {interface: vti1, address: 10.100.100.1/30, mtu: 1438} + ipsec_route_based_ospf: + router_id: 2.2.2.2 + networks: [10.100.100.0/30, 192.168.0.0/24] + passive_interfaces: [eth2] + ipsec_route_based_default_gateway: 10.0.1.1 + r2: + ansible_host: clab-bp-ipsec-cisco + base_interfaces: + - {name: eth1, addresses: [10.0.2.2/30]} + - {name: eth2, addresses: [192.168.10.1/24]} + ipsec_route_based_peers: + - name: VYOS + local_address: 10.0.2.2 + remote_address: 10.0.1.2 + psk: molecule-psk + connection_type: none + vti: {interface: vti1, address: 10.100.100.2/30, mtu: 1438} + ipsec_route_based_ospf: + router_id: 1.1.1.1 + networks: [10.100.100.0/30, 192.168.10.0/24] + passive_interfaces: [eth2] + ipsec_route_based_default_gateway: 10.0.2.1 + vars: + # proposals from the page's IKE and IPsec parameter tables + ipsec_route_based_ike_group: + name: IKE-GROUP + key_exchange: ikev1 + lifetime: 28800 + dh_group: 14 + encryption: aes128 + hash: sha1 + ipsec_route_based_esp_group: + name: ESP-GROUP + lifetime: 3600 + pfs: disable + encryption: aes256 + hash: sha256 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin diff --git a/extensions/molecule/ipsec_route_based/molecule.yml b/extensions/molecule/ipsec_route_based/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/ipsec_route_based/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/ipsec_route_based/topology.clab.yml b/extensions/molecule/ipsec_route_based/topology.clab.yml new file mode 100644 index 0000000..f6b9b29 --- /dev/null +++ b/extensions/molecule/ipsec_route_based/topology.clab.yml @@ -0,0 +1,32 @@ +name: bp-ipsec +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + vyos: + kind: vyosnetworks_vyos + # second VyOS standing in for the page's Cisco router + cisco: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: + - sysctl -w net.ipv4.ip_forward=1 + - ip addr add 10.0.1.1/30 dev eth1 + - ip addr add 10.0.2.1/30 dev eth2 + pc1: + kind: linux + image: alpine:3 + exec: ["ip addr add 192.168.0.2/24 dev eth1", "ip route replace default via 192.168.0.1"] + pc3: + kind: linux + image: alpine:3 + exec: ["ip addr add 192.168.10.2/24 dev eth1", "ip route replace default via 192.168.10.1"] + links: + # WAN is eth1 here (eth0 on the page; eth0 is containerlab management) + - endpoints: ["vyos:eth1", "isp:eth1"] + - endpoints: ["cisco:eth1", "isp:eth2"] + - endpoints: ["vyos:eth2", "pc1:eth1"] + - endpoints: ["cisco:eth2", "pc3:eth1"] diff --git a/extensions/molecule/ipsec_route_based/verify.yml b/extensions/molecule/ipsec_route_based/verify.yml new file mode 100644 index 0000000..e190a90 --- /dev/null +++ b/extensions/molecule/ipsec_route_based/verify.yml @@ -0,0 +1,29 @@ +--- +- name: Verify role state + hosts: vpn + gather_facts: false + tasks: + - name: Wait for IKE, IPsec and OSPF + ansible.builtin.pause: + seconds: 45 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify + +- name: Verify traffic through the tunnel, as in the page's "Checking Connectivity" + hosts: localhost + gather_facts: false + tasks: + - name: Ping across the VPN in both directions + ansible.builtin.command: + cmd: docker exec clab-bp-ipsec-{{ item.from }} ping -c 3 -W 2 {{ item.to }} + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + changed_when: false + loop: + - {from: pc1, to: 192.168.10.2} + - {from: pc3, to: 192.168.0.2} + loop_control: + label: "{{ item.from }} -> {{ item.to }}" diff --git a/extensions/molecule/ipsec_route_based_bgp/converge.yml b/extensions/molecule/ipsec_route_based_bgp/converge.yml new file mode 100644 index 0000000..0fb7a50 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vpn + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_route_based diff --git a/extensions/molecule/ipsec_route_based_bgp/inventory.yml b/extensions/molecule/ipsec_route_based_bgp/inventory.yml new file mode 100644 index 0000000..dff75f1 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp/inventory.yml @@ -0,0 +1,86 @@ +--- +# Values from docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html, except: +# - no NAT in the lab, so local_address is the public IP 198.51.100.3; +# - disable-route-autoinstall is set (the page does not set it): without it +# IPsec installs a route for the VTI's catch-all selector that would also +# capture containerlab management traffic; +# - both sides announce a LAN so the test can ping across BGP-learned routes. +all: + children: + vyos: + children: + vpn: + hosts: + r1: + ansible_host: clab-bp-azure-vyos + base_interfaces: + - {name: eth1, addresses: [198.51.100.3/24]} + - {name: eth2, addresses: [10.10.2.1/24]} + ipsec_route_based_peers: + - name: 203.0.113.2 + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: molecule-psk + local_address: 198.51.100.3 + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, description: Azure Tunnel, adjust_mss: "1350"} + ipsec_route_based_interfaces: [eth1] + ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} + ipsec_route_based_bgp: + asn: 64499 + networks: [10.10.2.0/24] + neighbors: + - {address: 10.0.0.4, remote_as: 65540, holdtime: 30, keepalive: 10, soft_reconfiguration_inbound: true} + ipsec_route_based_default_gateway: 198.51.100.1 + r2: + ansible_host: clab-bp-azure-azure + base_interfaces: + - {name: eth1, addresses: [203.0.113.2/24]} + - {name: eth2, addresses: [10.0.1.1/24]} + ipsec_route_based_peers: + - name: 198.51.100.3 + psk_name: onprem + psk: molecule-psk + local_address: 203.0.113.2 + remote_address: 198.51.100.3 + connection_type: none + esp_group_on_vti: true + vti: {interface: vti1, address: 10.0.0.4/32} + ipsec_route_based_interfaces: [eth1] + ipsec_route_based_interface_routes: + - {dest: 10.10.1.5/32, interface: vti1} + ipsec_route_based_bgp: + asn: 65540 + networks: [10.0.1.0/24] + neighbors: + - {address: 10.10.1.5, remote_as: 64499, holdtime: 30, keepalive: 10} + ipsec_route_based_default_gateway: 203.0.113.1 + vars: + ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} + ipsec_route_based_esp_group: + name: AZURE + lifetime: 3600 + mode: tunnel + pfs: dh-group2 + proposal_id: 1 + encryption: aes256 + hash: sha1 + ipsec_route_based_disable_route_autoinstall: true + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin diff --git a/extensions/molecule/ipsec_route_based_bgp/molecule.yml b/extensions/molecule/ipsec_route_based_bgp/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/ipsec_route_based_bgp/topology.clab.yml b/extensions/molecule/ipsec_route_based_bgp/topology.clab.yml new file mode 100644 index 0000000..e8ba878 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp/topology.clab.yml @@ -0,0 +1,28 @@ +name: bp-azure +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + vyos: + kind: vyosnetworks_vyos + # second VyOS standing in for the Azure VNet gateway + azure: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: ["sysctl -w net.ipv4.ip_forward=1", "ip addr add 198.51.100.1/24 dev eth1", "ip addr add 203.0.113.1/24 dev eth2"] + onprem: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.10.2.10/24 dev eth1", "ip route replace default via 10.10.2.1"] + vm: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.0.1.10/24 dev eth1", "ip route replace default via 10.0.1.1"] + links: + - endpoints: ["vyos:eth1", "isp:eth1"] + - endpoints: ["azure:eth1", "isp:eth2"] + - endpoints: ["vyos:eth2", "onprem:eth1"] + - endpoints: ["azure:eth2", "vm:eth1"] diff --git a/extensions/molecule/ipsec_route_based_bgp/verify.yml b/extensions/molecule/ipsec_route_based_bgp/verify.yml new file mode 100644 index 0000000..04ad7c2 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp/verify.yml @@ -0,0 +1,29 @@ +--- +- name: Verify role state + hosts: vpn + gather_facts: false + tasks: + - name: Wait for IKE, IPsec and BGP + ansible.builtin.pause: + seconds: 60 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify + +- name: Verify traffic over BGP-learned routes through the tunnel + hosts: localhost + gather_facts: false + tasks: + - name: Ping between on-premises and the "Azure" LAN + ansible.builtin.command: + cmd: docker exec clab-bp-azure-{{ item.from }} ping -c 3 -W 2 {{ item.to }} + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + changed_when: false + loop: + - {from: onprem, to: 10.0.1.10} + - {from: vm, to: 10.10.2.10} + loop_control: + label: "{{ item.from }} -> {{ item.to }}" diff --git a/extensions/molecule/ipsec_route_based_bgp_dual/converge.yml b/extensions/molecule/ipsec_route_based_bgp_dual/converge.yml new file mode 100644 index 0000000..0fb7a50 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp_dual/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vpn + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_route_based diff --git a/extensions/molecule/ipsec_route_based_bgp_dual/inventory.yml b/extensions/molecule/ipsec_route_based_bgp_dual/inventory.yml new file mode 100644 index 0000000..84d5c9d --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp_dual/inventory.yml @@ -0,0 +1,115 @@ +--- +# Values from docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html, except: +# - no NAT in the lab, so local_address is the public IP 198.51.100.3; +# - the second gateway instance is on its own ISP subnet (203.0.114.0/24); +# - disable-route-autoinstall is set (see ipsec_route_based_bgp/inventory.yml); +# - both sides announce a LAN so the test can ping across BGP-learned routes. +all: + children: + vyos: + children: + vpn: + hosts: + r1: + ansible_host: clab-bp-azure2-vyos + base_interfaces: + - name: eth1 + addresses: [198.51.100.3/24] + - name: eth2 + addresses: [10.10.2.1/24] + ipsec_route_based_peers: + - name: azure-primary + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: molecule-psk + local_address: 198.51.100.3 + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, adjust_mss: '1350'} + - name: azure-secondary + description: AZURE secondary TUNNEL + psk_name: azure + psk: molecule-psk + local_address: 198.51.100.3 + remote_address: 203.0.114.3 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti2, address: 10.10.1.6/32, adjust_mss: '1350'} + ipsec_route_based_interfaces: [eth1] + ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} + - {dest: 10.0.0.5/32, interface: vti2} + ipsec_route_based_bgp: + asn: 64499 + networks: [10.10.2.0/24] + neighbors: + - {address: 10.0.0.4, remote_as: 65540, holdtime: 30, keepalive: 10, soft_reconfiguration_inbound: true} + - {address: 10.0.0.5, remote_as: 65540, holdtime: 30, keepalive: 10, soft_reconfiguration_inbound: true} + ipsec_route_based_default_gateway: 198.51.100.1 + r2: + ansible_host: clab-bp-azure2-azure1 + base_interfaces: + - name: eth1 + addresses: [203.0.113.2/24] + - name: eth2 + addresses: [10.0.1.1/24] + ipsec_route_based_peers: + - name: 198.51.100.3 + psk_name: onprem + psk: molecule-psk + local_address: 203.0.113.2 + remote_address: 198.51.100.3 + connection_type: none + esp_group_on_vti: true + vti: {interface: vti1, address: 10.0.0.4/32} + ipsec_route_based_interfaces: [eth1] + ipsec_route_based_interface_routes: + - {dest: 10.10.1.5/32, interface: vti1} + ipsec_route_based_bgp: + asn: 65540 + networks: [10.0.1.0/24] + neighbors: + - {address: 10.10.1.5, remote_as: 64499, holdtime: 30, keepalive: 10} + ipsec_route_based_default_gateway: 203.0.113.1 + r3: + ansible_host: clab-bp-azure2-azure2 + base_interfaces: + - name: eth1 + addresses: [203.0.114.3/24] + - name: eth2 + addresses: [10.0.1.2/24] + ipsec_route_based_peers: + - name: 198.51.100.3 + psk_name: onprem + psk: molecule-psk + local_address: 203.0.114.3 + remote_address: 198.51.100.3 + connection_type: none + esp_group_on_vti: true + vti: {interface: vti1, address: 10.0.0.5/32} + ipsec_route_based_interfaces: [eth1] + ipsec_route_based_interface_routes: + - {dest: 10.10.1.6/32, interface: vti1} + ipsec_route_based_bgp: + asn: 65540 + networks: [10.0.1.0/24] + neighbors: + - {address: 10.10.1.6, remote_as: 64499, holdtime: 30, keepalive: 10} + ipsec_route_based_default_gateway: 203.0.114.1 + vars: + ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} + ipsec_route_based_esp_group: {name: AZURE, lifetime: 3600, mode: tunnel, pfs: dh-group2, proposal_id: 1, encryption: aes256, hash: sha1} + ipsec_route_based_disable_route_autoinstall: true + vars: {ansible_network_os: vyos.vyos.vyos, ansible_connection: ansible.netcommon.network_cli, ansible_user: admin, ansible_password: admin} diff --git a/extensions/molecule/ipsec_route_based_bgp_dual/molecule.yml b/extensions/molecule/ipsec_route_based_bgp_dual/molecule.yml new file mode 100644 index 0000000..11a5f84 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp_dual/molecule.yml @@ -0,0 +1,29 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 + - name: r3 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/ipsec_route_based_bgp_dual/topology.clab.yml b/extensions/molecule/ipsec_route_based_bgp_dual/topology.clab.yml new file mode 100644 index 0000000..140aee6 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp_dual/topology.clab.yml @@ -0,0 +1,39 @@ +name: bp-azure2 +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + vyos: + kind: vyosnetworks_vyos + # two VyOS standing in for the active-active Azure VNet gateway instances + azure1: + kind: vyosnetworks_vyos + azure2: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: ["sysctl -w net.ipv4.ip_forward=1", "ip addr add 198.51.100.1/24 dev eth1", "ip addr add 203.0.113.1/24 dev eth2", "ip addr add 203.0.114.1/24 dev eth3"] + onprem: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.10.2.10/24 dev eth1", "ip route replace default via 10.10.2.1"] + # the "VNet": one host behind both gateway instances + vm: + kind: linux + image: alpine:3 + exec: + - ip link add br0 type bridge + - ip link set eth1 master br0 + - ip link set eth2 master br0 + - ip link set br0 up + - ip addr add 10.0.1.10/24 dev br0 + - ip route replace 10.10.0.0/16 nexthop via 10.0.1.1 nexthop via 10.0.1.2 + links: + - endpoints: ["vyos:eth1", "isp:eth1"] + - endpoints: ["azure1:eth1", "isp:eth2"] + - endpoints: ["azure2:eth1", "isp:eth3"] + - endpoints: ["vyos:eth2", "onprem:eth1"] + - endpoints: ["azure1:eth2", "vm:eth1"] + - endpoints: ["azure2:eth2", "vm:eth2"] diff --git a/extensions/molecule/ipsec_route_based_bgp_dual/verify.yml b/extensions/molecule/ipsec_route_based_bgp_dual/verify.yml new file mode 100644 index 0000000..34bee33 --- /dev/null +++ b/extensions/molecule/ipsec_route_based_bgp_dual/verify.yml @@ -0,0 +1,47 @@ +--- +- name: Verify both tunnels and BGP sessions + hosts: vpn + gather_facts: false + tasks: + - name: Wait for IKE, IPsec and BGP + ansible.builtin.pause: + seconds: 60 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.ipsec_route_based + tasks_from: verify + +- name: Verify redundancy - traffic survives losing the primary tunnel + hosts: r1 + gather_facts: false + vars: + _ping: docker exec clab-bp-azure2-onprem ping -c 3 -W 2 10.0.1.10 + _become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + tasks: + - name: Ping the VNet with both tunnels up + ansible.builtin.command: "{{ _ping }}" + delegate_to: localhost + become: "{{ _become }}" + changed_when: false + + - name: Take the primary tunnel down + vyos.vyos.vyos_config: + lines: + - set vpn ipsec site-to-site peer azure-primary disable + + - name: Wait for BGP to converge on the secondary path + ansible.builtin.pause: + seconds: 40 + + - name: Ping the VNet over the secondary tunnel only + ansible.builtin.command: "{{ _ping }}" + delegate_to: localhost + become: "{{ _become }}" + changed_when: false + + - name: Bring the primary tunnel back + vyos.vyos.vyos_config: + lines: + - delete vpn ipsec site-to-site peer azure-primary disable diff --git a/extensions/molecule/ospf_unnumbered/converge.yml b/extensions/molecule/ospf_unnumbered/converge.yml new file mode 100644 index 0000000..c5e906b --- /dev/null +++ b/extensions/molecule/ospf_unnumbered/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: fabric + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ospf_unnumbered diff --git a/extensions/molecule/ospf_unnumbered/inventory.yml b/extensions/molecule/ospf_unnumbered/inventory.yml new file mode 100644 index 0000000..efaf93f --- /dev/null +++ b/extensions/molecule/ospf_unnumbered/inventory.yml @@ -0,0 +1,26 @@ +--- +all: + children: + vyos: + children: + fabric: + hosts: + r1: + ansible_host: clab-bp-ospf-r1 + ospf_unnumbered_router_id: 192.168.0.1 + base_interfaces: + - {name: eth3, addresses: [10.1.0.1/24]} + r2: + ansible_host: clab-bp-ospf-r2 + ospf_unnumbered_router_id: 192.168.0.2 + base_interfaces: + - {name: eth3, addresses: [10.2.0.1/24]} + vars: + ospf_unnumbered_interfaces: [eth1, eth2] + ospf_unnumbered_md5_key: molecule-ospf + ospf_unnumbered_group: fabric + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin diff --git a/extensions/molecule/ospf_unnumbered/molecule.yml b/extensions/molecule/ospf_unnumbered/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/ospf_unnumbered/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/ospf_unnumbered/topology.clab.yml b/extensions/molecule/ospf_unnumbered/topology.clab.yml new file mode 100644 index 0000000..e2fb3c9 --- /dev/null +++ b/extensions/molecule/ospf_unnumbered/topology.clab.yml @@ -0,0 +1,20 @@ +name: bp-ospf +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + r1: + kind: vyosnetworks_vyos + r2: + kind: vyosnetworks_vyos + lan: + kind: linux + image: alpine:3 + links: + # two parallel unnumbered links -> ECMP, as on the docs page + - endpoints: ["r1:eth1", "r2:eth1"] + - endpoints: ["r1:eth2", "r2:eth2"] + # LAN side (eth0 on the docs page; eth0 is management in containerlab) + - endpoints: ["r1:eth3", "lan:eth1"] + - endpoints: ["r2:eth3", "lan:eth2"] diff --git a/extensions/molecule/ospf_unnumbered/verify.yml b/extensions/molecule/ospf_unnumbered/verify.yml new file mode 100644 index 0000000..d75a2ea --- /dev/null +++ b/extensions/molecule/ospf_unnumbered/verify.yml @@ -0,0 +1,27 @@ +--- +- name: Verify + hosts: fabric + gather_facts: false + tasks: + - name: Wait for OSPF adjacencies and routes + ansible.builtin.pause: + seconds: 45 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.ospf_unnumbered + tasks_from: verify + + - name: Read the route to the other router's LAN + vyos.vyos.vyos_command: + commands: + - "show ip route {{ '10.2.0.0/24' if inventory_hostname == 'r1' else '10.1.0.0/24' }}" + register: _lan_route + + - name: The other LAN is learned via OSPF (redistribute connected) over both links + ansible.builtin.assert: + that: + - _lan_route.stdout[0] is search('Known via "ospf"') + - _lan_route.stdout[0] | regex_findall('via \S+ onlink') | length == 2 + quiet: true diff --git a/extensions/molecule/policy_ipsec_firewall/converge.yml b/extensions/molecule/policy_ipsec_firewall/converge.yml new file mode 100644 index 0000000..1696e59 --- /dev/null +++ b/extensions/molecule/policy_ipsec_firewall/converge.yml @@ -0,0 +1,9 @@ +--- +- name: Converge + hosts: sites + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.ipsec_policy_based + - vyos.blueprints.firewall + - vyos.blueprints.nat diff --git a/extensions/molecule/policy_ipsec_firewall/inventory.yml b/extensions/molecule/policy_ipsec_firewall/inventory.yml new file mode 100644 index 0000000..afc0223 --- /dev/null +++ b/extensions/molecule/policy_ipsec_firewall/inventory.yml @@ -0,0 +1,131 @@ +--- +all: + children: + vyos: + children: + sites: + hosts: + left: + ansible_host: clab-bp-pif-left + ipsec_policy_based_peers: + - name: RIGHT + psk_name: RIGHT + psk: molecule-psk + authentication_ids: false + local_address: 198.51.100.14 + remote_address: 192.0.2.130 + connection_type: initiate + tunnels: + - {id: 0, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.21.0/24} + - {id: 1, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.22.0/24} + - {id: 2, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.21.0/24} + - {id: 3, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.22.0/24} + ipsec_policy_based_default_gateway: 198.51.100.13 + firewall_groups: + network: + - name: LOCAL-NETS + networks: [10.1.11.0/24, 10.1.12.0/24] + - name: REMOTE-NETS + networks: [10.2.21.0/24, 10.2.22.0/24] + - name: TRUSTED + networks: [198.51.100.125/32, 203.0.113.0/24, 10.1.11.0/24, 192.168.70.0/24] + firewall_ipv4: + forward: + default_action: drop + rules: + - number: 1 + action: accept + state: {established: true, related: true} + - number: 2 + action: drop + state: {invalid: true} + - number: 10 + action: accept + source: + group: {network_group: LOCAL-NETS} + - number: 20 + action: accept + source: + group: {network_group: REMOTE-NETS} + destination: + group: {network_group: LOCAL-NETS} + input: + default_action: drop + rules: + - number: 1 + action: accept + state: {established: true, related: true} + - number: 2 + action: drop + state: {invalid: true} + - number: 5 + action: accept + description: containerlab management + inbound_interface: {name: eth0} + - number: 10 + action: accept + protocol: udp + destination: {port: '500,4500'} + inbound_interface: {name: eth3} + - number: 15 + action: accept + protocol: esp + inbound_interface: {name: eth3} + - number: 20 + action: accept + protocol: tcp + destination: {port: '22'} + source: + group: {network_group: TRUSTED} + - number: 25 + action: accept + protocol: udp + destination: {port: '53'} + source: + group: {network_group: LOCAL-NETS} + - {number: 30, action: accept, protocol: icmp} + nat_source_rules: + - id: 10 + exclude: true + outbound_interface: {name: eth3} + source: {network_group: LOCAL-NETS} + destination: {network_group: REMOTE-NETS} + - id: 20 + outbound_interface: {name: eth3} + source: {network_group: LOCAL-NETS} + translation: {address: masquerade} + base_interfaces: + - name: eth3 + addresses: [198.51.100.14/30] + - name: eth1.111 + addresses: [10.1.11.1/24] + - name: eth2.112 + addresses: [10.1.12.1/24] + right: + ansible_host: clab-bp-pif-right + ipsec_policy_based_peers: + - name: LEFT + psk_name: LEFT + psk: molecule-psk + authentication_ids: false + local_address: 192.0.2.130 + remote_address: 198.51.100.14 + connection_type: none + tunnels: + - {id: 0, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.11.0/24} + - {id: 1, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.11.0/24} + - {id: 2, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.12.0/24} + - {id: 3, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.12.0/24} + base_interfaces: + - name: eth3 + addresses: [192.0.2.130/30] + - name: eth1.221 + addresses: [10.2.21.1/24] + - name: eth2.222 + addresses: [10.2.22.1/24] + ipsec_policy_based_default_gateway: 192.0.2.129 + vars: + ipsec_policy_based_ike_group: {name: IKE-GROUP, key_exchange: ikev2, proposal_id: 1, dh_group: 14, encryption: aes256, hash: sha256} + ipsec_policy_based_esp_group: {name: ESP-GROUP, mode: tunnel, proposal_id: 1, encryption: aes256, hash: sha256} + ipsec_policy_based_interfaces: [eth3] + vars: {ansible_network_os: vyos.vyos.vyos, ansible_connection: ansible.netcommon.network_cli, ansible_user: admin, ansible_password: admin} diff --git a/extensions/molecule/policy_ipsec_firewall/molecule.yml b/extensions/molecule/policy_ipsec_firewall/molecule.yml new file mode 100644 index 0000000..9fa905c --- /dev/null +++ b/extensions/molecule/policy_ipsec_firewall/molecule.yml @@ -0,0 +1,28 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 + - name: r2 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/policy_ipsec_firewall/topology.clab.yml b/extensions/molecule/policy_ipsec_firewall/topology.clab.yml new file mode 100644 index 0000000..a94822e --- /dev/null +++ b/extensions/molecule/policy_ipsec_firewall/topology.clab.yml @@ -0,0 +1,51 @@ +name: bp-pif +topology: + kinds: + vyosnetworks_vyos: + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + nodes: + left: + kind: vyosnetworks_vyos + right: + kind: vyosnetworks_vyos + isp: + kind: linux + image: alpine:3 + exec: + - sysctl -w net.ipv4.ip_forward=1 + - ip addr add 198.51.100.13/30 dev eth1 + - ip addr add 192.0.2.129/30 dev eth2 + - ip addr add 203.0.113.1/24 dev eth3 + internet: + kind: linux + image: alpine:3 + exec: ["ip addr add 203.0.113.10/24 dev eth1", "ip route replace default via 203.0.113.1"] + l11: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.111 type vlan id 111", "ip link set eth1.111 up", + "ip addr add 10.1.11.10/24 dev eth1.111", "ip route replace default via 10.1.11.1"] + l12: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.112 type vlan id 112", "ip link set eth1.112 up", + "ip addr add 10.1.12.10/24 dev eth1.112", "ip route replace default via 10.1.12.1"] + r21: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.221 type vlan id 221", "ip link set eth1.221 up", + "ip addr add 10.2.21.10/24 dev eth1.221", "ip route replace default via 10.2.21.1"] + r22: + kind: linux + image: alpine:3 + exec: ["ip link add link eth1 name eth1.222 type vlan id 222", "ip link set eth1.222 up", + "ip addr add 10.2.22.10/24 dev eth1.222", "ip route replace default via 10.2.22.1"] + links: + # WAN is eth3 here (eth0 on the page; eth0 is containerlab management) + - endpoints: ["left:eth3", "isp:eth1"] + - endpoints: ["right:eth3", "isp:eth2"] + - endpoints: ["isp:eth3", "internet:eth1"] + - endpoints: ["left:eth1", "l11:eth1"] + - endpoints: ["left:eth2", "l12:eth1"] + - endpoints: ["right:eth1", "r21:eth1"] + - endpoints: ["right:eth2", "r22:eth1"] diff --git a/extensions/molecule/policy_ipsec_firewall/verify.yml b/extensions/molecule/policy_ipsec_firewall/verify.yml new file mode 100644 index 0000000..5502dc4 --- /dev/null +++ b/extensions/molecule/policy_ipsec_firewall/verify.yml @@ -0,0 +1,35 @@ +--- +- name: Verify role state + hosts: sites + gather_facts: false + tasks: + - name: Wait for IKE and IPsec + ansible.builtin.pause: + seconds: 30 + run_once: true + + - name: Run role checks + ansible.builtin.include_role: + name: "vyos.blueprints.{{ item }}" + tasks_from: verify + loop: "{{ ['ipsec_policy_based', 'firewall', 'nat'] if inventory_hostname == 'left' else ['ipsec_policy_based'] }}" + +- name: Verify the page's requirements with traffic + hosts: localhost + gather_facts: false + vars: + _flows: + - {from: l11, to: 10.2.21.10, why: "LAN to LAN through the tunnel, without NAT"} + - {from: l12, to: 10.2.22.10, why: "second LAN pair through the tunnel"} + - {from: r21, to: 10.1.11.10, why: "remote LANs may connect to local LANs"} + - {from: l11, to: 203.0.113.10, why: "local subnets reach the internet using source NAT"} + - {from: internet, to: 198.51.100.14, why: "ICMP is allowed on all interfaces"} + tasks: + - name: Ping per requirement + ansible.builtin.command: + cmd: docker exec clab-bp-pif-{{ item.from }} ping -c 3 -W 2 {{ item.to }} + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + changed_when: false + loop: "{{ _flows }}" + loop_control: + label: "{{ item.from }} -> {{ item.to }} ({{ item.why }})" diff --git a/extensions/molecule/vrf_firewall/converge.yml b/extensions/molecule/vrf_firewall/converge.yml new file mode 100644 index 0000000..bdf90f7 --- /dev/null +++ b/extensions/molecule/vrf_firewall/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vyos + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.vrf_firewall diff --git a/extensions/molecule/vrf_firewall/inventory.yml b/extensions/molecule/vrf_firewall/inventory.yml new file mode 100644 index 0000000..4d3b6f7 --- /dev/null +++ b/extensions/molecule/vrf_firewall/inventory.yml @@ -0,0 +1,54 @@ +--- +all: + children: + vyos: + hosts: + r1: + ansible_host: clab-bp-vrf-r1 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin + base_interfaces: + - {name: eth1, addresses: [10.100.100.1/24]} + - {name: eth2} + - {name: eth2.150, addresses: [10.150.150.1/24]} + - {name: eth2.3500, addresses: [172.16.20.1/24]} + - {name: eth3, addresses: [203.0.113.1/24]} + vrf_firewall_vrfs: + - name: MGMT + table: 102 + interfaces: [eth1] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: WAN + table: 101 + interfaces: [eth3] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: LAN + table: 103 + interfaces: [eth2.150] + routes: + - {dest: 0.0.0.0/0, interface: eth3, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: PROD + table: 104 + interfaces: [eth2.3500] + routes: + - {dest: 0.0.0.0/0, interface: eth3, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + vrf_firewall_forward_rules: + - {number: 10, description: MGMT - Allow to LAN and PROD, inbound_interface: MGMT, outbound_interface: eth2*} + - {number: 99, action: drop, description: MGMT - Drop all going to mgmt, outbound_interface: eth1} + - {number: 120, description: LAN - Allow to PROD, inbound_interface: LAN, outbound_interface: eth2.3500} + - {number: 130, description: LAN - Allow internet, inbound_interface: LAN, outbound_interface: eth3} + vrf_firewall_input_rules: + # containerlab management; keeps Ansible's SSH session working + - {number: 5, description: containerlab management, inbound_interface: eth0} + - {number: 10, description: MGMT - Allow input, inbound_interface: MGMT} diff --git a/extensions/molecule/vrf_firewall/molecule.yml b/extensions/molecule/vrf_firewall/molecule.yml new file mode 100644 index 0000000..612af0a --- /dev/null +++ b/extensions/molecule/vrf_firewall/molecule.yml @@ -0,0 +1,27 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/vrf_firewall/topology.clab.yml b/extensions/molecule/vrf_firewall/topology.clab.yml new file mode 100644 index 0000000..567cd56 --- /dev/null +++ b/extensions/molecule/vrf_firewall/topology.clab.yml @@ -0,0 +1,43 @@ +name: bp-vrf +topology: + nodes: + r1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + mgmt: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.100.100.10/24 dev eth1", "ip route replace default via 10.100.100.1"] + # VLAN-aware bridge for the eth2 trunk (vif 150 = LAN, vif 3500 = PROD) + sw: + kind: linux + image: alpine:3 + exec: + - ip link add br0 type bridge vlan_filtering 1 + - ip link set eth1 master br0 + - ip link set eth2 master br0 + - ip link set eth3 master br0 + - bridge vlan add dev eth1 vid 150 + - bridge vlan add dev eth1 vid 3500 + - bridge vlan add dev eth2 vid 150 pvid untagged + - bridge vlan add dev eth3 vid 3500 pvid untagged + - ip link set br0 up + lan: + kind: linux + image: alpine:3 + exec: ["ip addr add 10.150.150.10/24 dev eth1", "ip route replace default via 10.150.150.1"] + prod: + kind: linux + image: alpine:3 + exec: ["ip addr add 172.16.20.10/24 dev eth1", "ip route replace default via 172.16.20.1"] + # stands in for the page's PPPoE uplink + wan: + kind: linux + image: alpine:3 + exec: ["ip addr add 203.0.113.100/24 dev eth1", "ip route replace default via 203.0.113.1"] + links: + - endpoints: ["r1:eth1", "mgmt:eth1"] + - endpoints: ["r1:eth2", "sw:eth1"] + - endpoints: ["sw:eth2", "lan:eth1"] + - endpoints: ["sw:eth3", "prod:eth1"] + - endpoints: ["r1:eth3", "wan:eth1"] diff --git a/extensions/molecule/vrf_firewall/verify.yml b/extensions/molecule/vrf_firewall/verify.yml new file mode 100644 index 0000000..82989cc --- /dev/null +++ b/extensions/molecule/vrf_firewall/verify.yml @@ -0,0 +1,36 @@ +--- +- name: Verify role state + hosts: vyos + gather_facts: false + tasks: + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.vrf_firewall + tasks_from: verify + +- name: Verify traffic between VRFs against the page's requirements + hosts: localhost + gather_facts: false + vars: + _flows: + - {from: mgmt, to: 10.150.150.10, allowed: true, why: "MGMT may connect to LAN"} + - {from: mgmt, to: 172.16.20.10, allowed: true, why: "MGMT may connect to PROD"} + - {from: mgmt, to: 203.0.113.100, allowed: false, why: "MGMT may not reach the internet"} + - {from: lan, to: 172.16.20.10, allowed: true, why: "LAN may connect to PROD"} + - {from: lan, to: 203.0.113.100, allowed: true, why: "LAN may reach the internet"} + - {from: lan, to: 10.100.100.10, allowed: false, why: "nothing may connect to MGMT"} + - {from: prod, to: 10.150.150.10, allowed: false, why: "PROD only accepts connections"} + # The page's text says WAN may connect to PROD, but its configuration has + # no rule for it, so the configuration (default drop) is what is tested. + - {from: wan, to: 172.16.20.10, allowed: false, why: "no WAN->PROD rule in the page's configuration"} + tasks: + - name: Ping across VRFs + ansible.builtin.command: + cmd: docker exec clab-bp-vrf-{{ item.from }} ping -c 2 -W 2 {{ item.to }} + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + register: _ping + changed_when: false + failed_when: (_ping.rc == 0) != item.allowed + loop: "{{ _flows }}" + loop_control: + label: "{{ item.from }} -> {{ item.to }} ({{ item.why }})" diff --git a/extensions/molecule/zone_firewall/converge.yml b/extensions/molecule/zone_firewall/converge.yml new file mode 100644 index 0000000..03e3ad7 --- /dev/null +++ b/extensions/molecule/zone_firewall/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: vyos + gather_facts: false + roles: + - vyos.blueprints.base + - vyos.blueprints.zone_firewall diff --git a/extensions/molecule/zone_firewall/inventory.yml b/extensions/molecule/zone_firewall/inventory.yml new file mode 100644 index 0000000..6e9f461 --- /dev/null +++ b/extensions/molecule/zone_firewall/inventory.yml @@ -0,0 +1,33 @@ +--- +all: + children: + vyos: + hosts: + r1: + ansible_host: clab-bp-zfw-r1 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: admin + ansible_password: admin + base_interfaces: + - {name: eth1, addresses: [172.16.10.1/24]} + - {name: eth2, addresses: [192.168.100.1/24]} + - {name: eth3, addresses: [192.168.200.1/24]} + zone_firewall_zones: + # containerlab management; keeps Ansible's SSH session working + - {name: mgmt, interfaces: [eth0]} + - {name: wan, interfaces: [eth1]} + - {name: lan, interfaces: [eth2]} + - {name: dmz, interfaces: [eth3]} + - {name: local, local: true} + zone_firewall_policies: + - {from: mgmt, to: local, default_action: accept} + - from: wan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {address: 192.168.200.200, port: "80,443"}} + - from: lan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} diff --git a/extensions/molecule/zone_firewall/molecule.yml b/extensions/molecule/zone_firewall/molecule.yml new file mode 100644 index 0000000..612af0a --- /dev/null +++ b/extensions/molecule/zone_firewall/molecule.yml @@ -0,0 +1,27 @@ +--- +# VyOS runs as a container (containerlab kind vyosnetworks_vyos); +# the lab is deployed in prepare and destroyed in cleanup. +dependency: + name: galaxy + enabled: false +driver: + name: default + options: + managed: false +platforms: + - name: r1 +provisioner: + name: ansible + config_options: + defaults: + host_key_checking: false + persistent_connection: + command_timeout: 60 + inventory: + links: + hosts: inventory.yml + playbooks: + prepare: ../_shared/lab_up.yml + cleanup: ../_shared/lab_down.yml +verifier: + name: ansible diff --git a/extensions/molecule/zone_firewall/topology.clab.yml b/extensions/molecule/zone_firewall/topology.clab.yml new file mode 100644 index 0000000..9be83a6 --- /dev/null +++ b/extensions/molecule/zone_firewall/topology.clab.yml @@ -0,0 +1,34 @@ +name: bp-zfw +topology: + nodes: + r1: + kind: vyosnetworks_vyos + image: ${VYOS_IMAGE:=vyos:blueprints-ci} + wan: + kind: linux + image: alpine:3 + exec: + - ip addr add 172.16.10.100/24 dev eth1 + - ip route replace default via 172.16.10.1 + - apk add --no-cache busybox-extras + - httpd -p 80 -h /tmp + lan: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.100.10/24 dev eth1 + - ip route replace default via 192.168.100.1 + - apk add --no-cache busybox-extras + - httpd -p 80 -h /tmp + dmz: + kind: linux + image: alpine:3 + exec: + - ip addr add 192.168.200.200/24 dev eth1 + - ip route replace default via 192.168.200.1 + - apk add --no-cache busybox-extras + - httpd -p 80 -h /tmp + links: + - endpoints: ["r1:eth1", "wan:eth1"] + - endpoints: ["r1:eth2", "lan:eth1"] + - endpoints: ["r1:eth3", "dmz:eth1"] diff --git a/extensions/molecule/zone_firewall/verify.yml b/extensions/molecule/zone_firewall/verify.yml new file mode 100644 index 0000000..16584f3 --- /dev/null +++ b/extensions/molecule/zone_firewall/verify.yml @@ -0,0 +1,30 @@ +--- +- name: Verify role state + hosts: vyos + gather_facts: false + tasks: + - name: Run role checks + ansible.builtin.include_role: + name: vyos.blueprints.zone_firewall + tasks_from: verify + +- name: Verify traffic between zones + hosts: localhost + gather_facts: false + vars: + _flows: + - {from: lan, to: 192.168.200.200, allowed: true, why: "LAN can access DMZ resources"} + - {from: wan, to: 192.168.200.200, allowed: true, why: "inbound WAN connects to the DMZ host"} + - {from: dmz, to: 192.168.100.10, allowed: false, why: "DMZ cannot access LAN resources"} + - {from: wan, to: 192.168.100.10, allowed: false, why: "WAN cannot reach the LAN"} + tasks: + - name: Try HTTP across zones + ansible.builtin.command: + cmd: docker exec clab-bp-zfw-{{ item.from }} wget -q -T 3 -O /dev/null http://{{ item.to }}/ + become: "{{ lookup('ansible.builtin.env', 'CLAB_BECOME', default='true') | bool }}" + register: _http + changed_when: false + failed_when: (_http.rc == 0) != item.allowed + loop: "{{ _flows }}" + loop_control: + label: "{{ item.from }} -> {{ item.to }} ({{ item.why }})" diff --git a/roles/bgp_unnumbered/defaults/main.yml b/roles/bgp_unnumbered/defaults/main.yml new file mode 100644 index 0000000..cadff8d --- /dev/null +++ b/roles/bgp_unnumbered/defaults/main.yml @@ -0,0 +1,7 @@ +--- +bgp_unnumbered_peer_group: fabric +bgp_unnumbered_remote_as: external +bgp_unnumbered_address_families: [ipv4, ipv6] +bgp_unnumbered_redistribute: [connected] +bgp_unnumbered_multipath_relax: true +bgp_unnumbered_group: "" diff --git a/roles/bgp_unnumbered/meta/argument_specs.yml b/roles/bgp_unnumbered/meta/argument_specs.yml new file mode 100644 index 0000000..043e26d --- /dev/null +++ b/roles/bgp_unnumbered/meta/argument_specs.yml @@ -0,0 +1,81 @@ +--- +argument_specs: + main: + short_description: BGP IPv6 unnumbered with extended next-hop (docs blueprint of the same name) + description: + - Peers over IPv6 link-local addresses on each listed interface, carries + IPv4 and IPv6 with extended next-hop, and redistributes connected + routes. Parallel links give ECMP; C(multipath-relax) allows it across + different neighbour ASes. + - Puts the router-id as a /32 on C(lo), as in the page's results. + - AS, router-id and redistribution use vyos.vyos BGP modules. Peer-group, + interface (v6only) neighbours and multipath-relax use + vyos.vyos.vyos_config, because the BGP resource modules cannot express + them yet. + - Works for any number of routers; each router lists only its own links. + options: + bgp_unnumbered_asn: + type: int + required: true + description: Local AS number (C(system-as)). + bgp_unnumbered_router_id: + type: str + required: true + description: BGP router-id; also configured as a /32 on C(lo). + bgp_unnumbered_interfaces: + type: list + elements: str + required: true + description: Interfaces to peer on, e.g. C([eth1, eth2]). They need no IP address. + bgp_unnumbered_peer_group: + type: str + default: fabric + description: Peer-group name shared by all unnumbered neighbours. + bgp_unnumbered_remote_as: + type: str + default: external + description: C(external), C(internal) or an AS number. + bgp_unnumbered_address_families: + type: list + elements: str + choices: [ipv4, ipv6] + default: [ipv4, ipv6] + description: Address families activated on the peer-group. + bgp_unnumbered_redistribute: + type: list + elements: str + default: [connected] + description: Route types redistributed in every listed address family. + bgp_unnumbered_multipath_relax: + type: bool + default: true + description: Set C(bestpath as-path multipath-relax). + bgp_unnumbered_group: + type: str + default: "" + description: Inventory group of all routers in the fabric; used only by C(verify). + 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 bgp_unnumbered role + description: + - Run with C(tasks_from=verify) against all routers in the same play. + - Checks one established session per listed interface and, with + C(bgp_unnumbered_group), a BGP route to every other router-id. When the + group has exactly two routers, also checks one ECMP next hop per link. + options: + bgp_unnumbered_router_id: + type: str + required: true + description: Same value as for C(main). + bgp_unnumbered_interfaces: + type: list + elements: str + required: true + description: Same value as for C(main). + bgp_unnumbered_group: + type: str + default: "" + description: Same value as for C(main). diff --git a/roles/bgp_unnumbered/meta/main.yml b/roles/bgp_unnumbered/meta/main.yml new file mode 100644 index 0000000..af7872b --- /dev/null +++ b/roles/bgp_unnumbered/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: eBGP over IPv6 link-local (unnumbered) interfaces with extended next-hop + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, bgp] +dependencies: [] diff --git a/roles/bgp_unnumbered/tasks/main.yml b/roles/bgp_unnumbered/tasks/main.yml new file mode 100644 index 0000000..1317f4f --- /dev/null +++ b/roles/bgp_unnumbered/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Put the router-id on the loopback + vyos.vyos.vyos_l3_interfaces: + config: + - name: lo + ipv4: + - address: "{{ bgp_unnumbered_router_id }}/32" + state: "{{ _bgp_unnumbered_state }}" + register: _bgp_unnumbered_r_lo + +- name: Configure the BGP process (AS, router-id, best-path) + vyos.vyos.vyos_bgp_global: + config: "{{ lookup('ansible.builtin.template', 'bgp_global.yml.j2') | from_yaml }}" + state: "{{ _bgp_unnumbered_state }}" + register: _bgp_unnumbered_r_global + +- name: Redistribute into each address family + vyos.vyos.vyos_bgp_address_family: + config: "{{ lookup('ansible.builtin.template', 'bgp_address_family.yml.j2') | from_yaml }}" + state: "{{ _bgp_unnumbered_state }}" + register: _bgp_unnumbered_r_af + when: bgp_unnumbered_redistribute | length > 0 + +- name: Configure the peer-group and unnumbered neighbours + # Not expressible with vyos_bgp_global yet (interface v6only neighbours, + # peer-group definitions, remote-as external, multipath-relax). + vyos.vyos.vyos_config: + lines: "{{ _bgp_unnumbered_cli_lines }}" + register: _bgp_unnumbered_r_cli + when: not (vyos_blueprints_render_only | default(false) | bool) + +- 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([]) + + ([_bgp_unnumbered_r_lo, _bgp_unnumbered_r_global, _bgp_unnumbered_r_af] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten + | unique) + + _bgp_unnumbered_cli_lines }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/bgp_unnumbered/tasks/verify.yml b/roles/bgp_unnumbered/tasks/verify.yml new file mode 100644 index 0000000..cbc98b9 --- /dev/null +++ b/roles/bgp_unnumbered/tasks/verify.yml @@ -0,0 +1,44 @@ +--- +- name: Read BGP sessions + vyos.vyos.vyos_command: + commands: + - show bgp summary + register: _bgp_unnumbered_v_sum + +- name: One established session per interface + # An established neighbour shows its Up/Down time followed by a prefix + # count; other states show their name (Active, Connect, ...) instead. + ansible.builtin.assert: + that: _bgp_unnumbered_v_sum.stdout[0] is search('(?m)^' ~ item ~ '\s.*\s(\d{2}:\d{2}:\d{2}|\d+[dwh]\S*)\s+\d+\b') + fail_msg: "BGP session on {{ item }} is not established" + quiet: true + loop: "{{ bgp_unnumbered_interfaces }}" + +- name: Read routes to the other routers + vyos.vyos.vyos_command: + commands: "{{ _bgp_unnumbered_peers | map('regex_replace', '^(.*)$', 'show ip route \\1/32') | list }}" + vars: + _bgp_unnumbered_peers: >- + {{ groups[bgp_unnumbered_group] | difference([inventory_hostname]) + | map('extract', hostvars, 'bgp_unnumbered_router_id') | list }} + register: _bgp_unnumbered_v_routes + when: bgp_unnumbered_group | length > 0 + +- name: Every other router-id is reached via BGP + ansible.builtin.assert: + that: item is search('Known via "bgp"') + fail_msg: "{{ item | regex_search('Routing entry for \\S+') | default('a peer', true) }} is not learned via BGP" + quiet: true + loop: "{{ _bgp_unnumbered_v_routes.stdout | default([]) }}" + loop_control: + label: "{{ item | regex_search('Routing entry for \\S+') | default('route', true) }}" + when: bgp_unnumbered_group | length > 0 + +- name: With two routers, one ECMP next hop per link + ansible.builtin.assert: + that: _bgp_unnumbered_v_routes.stdout[0] | regex_findall('fe80::\S+, via \S+') | length == bgp_unnumbered_interfaces | length + fail_msg: "expected {{ bgp_unnumbered_interfaces | length }} ECMP next hops to the peer" + quiet: true + when: + - bgp_unnumbered_group | length > 0 + - groups[bgp_unnumbered_group] | length == 2 diff --git a/roles/bgp_unnumbered/templates/bgp_address_family.yml.j2 b/roles/bgp_unnumbered/templates/bgp_address_family.yml.j2 new file mode 100644 index 0000000..552f1ae --- /dev/null +++ b/roles/bgp_unnumbered/templates/bgp_address_family.yml.j2 @@ -0,0 +1,11 @@ +as_number: {{ bgp_unnumbered_asn | int }} +address_family: +{% for afi in bgp_unnumbered_address_families %} + - afi: {{ afi }} +{% if bgp_unnumbered_redistribute %} + redistribute: +{% for r in bgp_unnumbered_redistribute %} + - protocol: {{ r }} +{% endfor %} +{% endif %} +{% endfor %} diff --git a/roles/bgp_unnumbered/templates/bgp_global.yml.j2 b/roles/bgp_unnumbered/templates/bgp_global.yml.j2 new file mode 100644 index 0000000..4c9c633 --- /dev/null +++ b/roles/bgp_unnumbered/templates/bgp_global.yml.j2 @@ -0,0 +1,7 @@ +as_number: {{ bgp_unnumbered_asn | int }} +bgp_params: + router_id: {{ bgp_unnumbered_router_id | to_json }} + bestpath: + compare_routerid: true + default: + no_ipv4_unicast: true diff --git a/roles/bgp_unnumbered/templates/bgp_neighbors.j2 b/roles/bgp_unnumbered/templates/bgp_neighbors.j2 new file mode 100644 index 0000000..34565ac --- /dev/null +++ b/roles/bgp_unnumbered/templates/bgp_neighbors.j2 @@ -0,0 +1,14 @@ +{% set pg = bgp_unnumbered_peer_group %} +{% set base = 'set protocols bgp' %} +{% for afi in bgp_unnumbered_address_families %} +{{ base }} peer-group {{ pg }} address-family {{ afi }}-unicast +{% endfor %} +{{ base }} peer-group {{ pg }} capability extended-nexthop +{{ base }} peer-group {{ pg }} remote-as '{{ bgp_unnumbered_remote_as }}' +{% for i in bgp_unnumbered_interfaces %} +{{ base }} neighbor {{ i }} interface v6only +{{ base }} neighbor {{ i }} interface v6only peer-group '{{ pg }}' +{% endfor %} +{% if bgp_unnumbered_multipath_relax | bool %} +{{ base }} parameters bestpath as-path multipath-relax +{% endif %} diff --git a/roles/bgp_unnumbered/vars/main.yml b/roles/bgp_unnumbered/vars/main.yml new file mode 100644 index 0000000..64eb708 --- /dev/null +++ b/roles/bgp_unnumbered/vars/main.yml @@ -0,0 +1,3 @@ +--- +_bgp_unnumbered_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_bgp_unnumbered_cli_lines: "{{ (lookup('ansible.builtin.template', 'bgp_neighbors.j2') or '').splitlines() | select | list }}" diff --git a/roles/bridge_firewall/defaults/main.yml b/roles/bridge_firewall/defaults/main.yml new file mode 100644 index 0000000..f656cb5 --- /dev/null +++ b/roles/bridge_firewall/defaults/main.yml @@ -0,0 +1,4 @@ +--- +bridge_firewall_state_rules: true +bridge_firewall_bridge_forward_default_action: drop +bridge_firewall_ipv4_forward_default_action: drop diff --git a/roles/bridge_firewall/meta/argument_specs.yml b/roles/bridge_firewall/meta/argument_specs.yml new file mode 100644 index 0000000..6d5493e --- /dev/null +++ b/roles/bridge_firewall/meta/argument_specs.yml @@ -0,0 +1,163 @@ +--- +argument_specs: + main: + short_description: Bridges with bridge and IPv4 firewall (docs blueprint "Bridge and firewall example") + description: + - Per bridge, creates the bridge and its members, an interface group + C(<bridge>-ifaces), bridge-firewall rulesets C(<bridge>-pre) (prerouting) + and C(<bridge>-fwd) (forward) with the jump rules that select them, and + optionally an IPv4 ruleset C(ip-<bridge>-fwd) for routed traffic and an + input rule for access to the router - the structure used on the docs page. + - Bridges, interface groups and the bridge firewall use + vyos.vyos.vyos_config, because vyos.vyos has no resource modules for + them yet. Member descriptions and the IPv4 firewall use resource modules. + options: + bridge_firewall_bridges: + type: list + elements: dict + required: true + description: Bridges, in order; their position sets the jump rule numbers (10, 20, ... and 110, 120, ...). + options: + name: + type: str + required: true + description: Bridge interface, e.g. C(br1). + description: + type: str + description: Bridge description. + addresses: + type: list + elements: str + default: [] + description: Bridge addresses; omit for a pure layer-2 bridge. + members: + type: list + elements: dict + required: true + description: Member interfaces. + options: + interface: + type: str + required: true + description: Member interface, e.g. C(eth3). + description: + type: str + description: Member interface description. + prerouting: + type: dict + description: Bridge ruleset C(<bridge>-pre), applied in the prerouting chain. + options: &bridge_ruleset + default_action: + type: str + choices: [accept, drop] + description: Ruleset default action (C(accept) for prerouting, C(drop) for forward by default). + rules: + type: list + elements: dict + default: [] + description: Bridge firewall rules. + options: + number: + type: int + required: true + description: Rule number. + action: + type: str + choices: [accept, drop] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + ethernet_type: + type: str + description: Ethernet type, e.g. C(ipv4), C(ipv6), C(arp). + protocol: + type: str + description: IP protocol, e.g. C(udp). + source: + type: dict + description: Source match (C(address), C(port), C(mac_address)). + destination: + type: dict + description: Destination match (C(address), C(port), C(mac_address)). + inbound_interface: + type: str + description: Inbound bridge port, e.g. C(eth6). + state: + type: list + elements: str + default: [] + description: Connection states, e.g. C([new]). + log: + type: bool + default: false + description: Log matches. + forward: + type: dict + description: Bridge ruleset C(<bridge>-fwd), applied in the bridge forward chain. + options: *bridge_ruleset + ip_forward: + type: dict + description: IPv4 ruleset C(ip-<bridge>-fwd) for traffic routed out of the bridge. + options: + default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Ruleset default action. + rules: + type: list + elements: dict + default: [] + description: IPv4 rules. + options: + number: + type: int + required: true + description: Rule number. + action: + type: str + choices: [accept, drop, reject] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + outbound_interface: + type: str + description: Outbound interface, e.g. the internet uplink. + outbound_bridge: + type: str + description: Another bridge from this list, matched via its interface group. + router_access: + type: str + choices: [accept, drop] + description: Accept or drop IPv4 traffic from this bridge to the router itself. + bridge_firewall_state_rules: + type: bool + default: true + description: Add the page's established/related and invalid rules to the forward and input chains. + bridge_firewall_bridge_forward_default_action: + type: str + choices: [accept, drop] + default: drop + description: Default action of the bridge forward chain. + bridge_firewall_ipv4_forward_default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Default action of the IPv4 forward chain. + 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 bridge_firewall role + description: Run with C(tasks_from=verify). Checks bridge membership and that the bridge rulesets are active. + options: + bridge_firewall_bridges: + type: list + elements: dict + required: true + description: Same value as for C(main). diff --git a/roles/bridge_firewall/meta/main.yml b/roles/bridge_firewall/meta/main.yml new file mode 100644 index 0000000..4e556cb --- /dev/null +++ b/roles/bridge_firewall/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Bridges with bridge-layer and IPv4 firewall policies per bridge + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, bridge, firewall] +dependencies: [] diff --git a/roles/bridge_firewall/tasks/main.yml b/roles/bridge_firewall/tasks/main.yml new file mode 100644 index 0000000..d947258 --- /dev/null +++ b/roles/bridge_firewall/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: Set member interface descriptions + vyos.vyos.vyos_interfaces: + config: "{{ _bridge_firewall_member_descriptions }}" + state: "{{ _bridge_firewall_state }}" + register: _bridge_firewall_r_ifaces + when: _bridge_firewall_member_descriptions | length > 0 + +- name: Create bridges, interface groups and the bridge firewall + # No vyos.vyos resource module covers bridges, interface groups or the + # bridge firewall yet, so these go through vyos_config. + vyos.vyos.vyos_config: + lines: "{{ _bridge_firewall_cli_lines }}" + register: _bridge_firewall_r_cli + when: not (vyos_blueprints_render_only | default(false) | bool) + +- name: Configure the IPv4 input and forward filters + vyos.vyos.vyos_firewall_rules: + config: "{{ _bridge_firewall_ipv4 }}" + state: "{{ _bridge_firewall_state }}" + vars: + _bridge_firewall_ipv4: "{{ lookup('ansible.builtin.template', 'ipv4_rules.yml.j2') | from_yaml }}" + register: _bridge_firewall_r_ipv4 + when: >- + bridge_firewall_bridges | selectattr('ip_forward', 'defined') | list | length > 0 + or bridge_firewall_bridges | selectattr('router_access', 'defined') | list | 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([]) + + (_bridge_firewall_r_ifaces.rendered | default([])) + + _bridge_firewall_cli_lines + + (_bridge_firewall_r_ipv4.rendered | default([])) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/bridge_firewall/tasks/verify.yml b/roles/bridge_firewall/tasks/verify.yml new file mode 100644 index 0000000..295b8af --- /dev/null +++ b/roles/bridge_firewall/tasks/verify.yml @@ -0,0 +1,27 @@ +--- +- name: Read bridges and bridge firewall + vyos.vyos.vyos_command: + commands: + - show configuration commands + - show firewall bridge + register: _bridge_firewall_v + +- name: Every member interface is in its bridge + ansible.builtin.assert: + that: >- + _bridge_firewall_v.stdout[0] is search('set interfaces bridge ' ~ (item.0.name | regex_escape) + ~ ' member interface ' ~ (item.1.interface | regex_escape) ~ '\b') + fail_msg: "{{ item.1.interface }} is not a member of {{ item.0.name }}" + quiet: true + loop: "{{ bridge_firewall_bridges | subelements('members') }}" + loop_control: + label: "{{ item.0.name }} {{ item.1.interface }}" + +- name: Every bridge ruleset is active + ansible.builtin.assert: + that: _bridge_firewall_v.stdout[1] is search('name ' ~ item) + fail_msg: "bridge ruleset {{ item }} is not active" + quiet: true + loop: >- + {{ (bridge_firewall_bridges | selectattr('prerouting', 'defined') | map(attribute='name') | map('regex_replace', '$', '-pre') | list) + + (bridge_firewall_bridges | selectattr('forward', 'defined') | map(attribute='name') | map('regex_replace', '$', '-fwd') | list) }} diff --git a/roles/bridge_firewall/templates/bridge_cli.j2 b/roles/bridge_firewall/templates/bridge_cli.j2 new file mode 100644 index 0000000..18cb1f3 --- /dev/null +++ b/roles/bridge_firewall/templates/bridge_cli.j2 @@ -0,0 +1,72 @@ +{#- Bridges, interface groups and the bridge firewall: no vyos.vyos resource + module covers these yet. Rule numbers for the jump rules follow the page. -#} +{%- macro bridge_rule(prefix, r) -%} +{{ prefix }} rule {{ r.number }} action '{{ r.action | default('accept') }}' +{% if r.description is defined %} +{{ prefix }} rule {{ r.number }} description '{{ r.description }}' +{% endif %} +{% for k in ['ethernet_type', 'protocol'] if r[k] is defined %} +{{ prefix }} rule {{ r.number }} {{ k | replace('_', '-') }} '{{ r[k] }}' +{% endfor %} +{% for side in ['source', 'destination'] if r[side] is defined %} +{% for k in ['address', 'port', 'mac_address'] if r[side][k] is defined %} +{{ prefix }} rule {{ r.number }} {{ side }} {{ k | replace('_', '-') }} '{{ r[side][k] }}' +{% endfor %} +{% endfor %} +{% if r.inbound_interface is defined %} +{{ prefix }} rule {{ r.number }} inbound-interface name '{{ r.inbound_interface }}' +{% endif %} +{% for s in r.state | default([]) %} +{{ prefix }} rule {{ r.number }} state '{{ s }}' +{% endfor %} +{% if r.log | default(false) | bool %} +{{ prefix }} rule {{ r.number }} log +{% endif %} +{%- endmacro -%} +{% for b in bridge_firewall_bridges %} +{% set i = loop.index %} +{% set grp = b.name ~ '-ifaces' %} +{% if b.description is defined %} +set interfaces bridge {{ b.name }} description '{{ b.description }}' +{% endif %} +{% for a in b.addresses | default([]) %} +set interfaces bridge {{ b.name }} address '{{ a }}' +{% endfor %} +{% for m in b.members %} +set interfaces bridge {{ b.name }} member interface {{ m.interface }} +{% endfor %} +set firewall group interface-group {{ grp }} interface '{{ b.name }}' +{% for m in b.members %} +set firewall group interface-group {{ grp }} interface '{{ m.interface }}' +{% endfor %} +{% if b.prerouting is defined %} +set firewall bridge prerouting filter rule {{ i * 10 }} action 'jump' +set firewall bridge prerouting filter rule {{ i * 10 }} description '{{ b.name }} traffic' +set firewall bridge prerouting filter rule {{ i * 10 }} inbound-interface group '{{ grp }}' +set firewall bridge prerouting filter rule {{ i * 10 }} jump-target '{{ b.name }}-pre' +set firewall bridge name {{ b.name }}-pre default-action '{{ b.prerouting.default_action | default('accept') }}' +{% for r in b.prerouting.rules | default([]) %} +{{ bridge_rule('set firewall bridge name ' ~ b.name ~ '-pre', r) }} +{% endfor %} +{% endif %} +{% if b.forward is defined %} +set firewall bridge forward filter rule {{ 100 + i * 10 }} action 'jump' +set firewall bridge forward filter rule {{ 100 + i * 10 }} description '{{ b.name }} traffic' +set firewall bridge forward filter rule {{ 100 + i * 10 }} inbound-interface group '{{ grp }}' +set firewall bridge forward filter rule {{ 100 + i * 10 }} jump-target '{{ b.name }}-fwd' +set firewall bridge name {{ b.name }}-fwd default-action '{{ b.forward.default_action | default('drop') }}' +{% for r in b.forward.rules | default([]) %} +{{ bridge_rule('set firewall bridge name ' ~ b.name ~ '-fwd', r) }} +{% endfor %} +{% endif %} +{% endfor %} +{% if bridge_firewall_bridges | selectattr('forward', 'defined') | list %} +{% if bridge_firewall_state_rules | bool %} +set firewall bridge forward filter rule 5 action 'accept' +set firewall bridge forward filter rule 5 state 'established' +set firewall bridge forward filter rule 5 state 'related' +set firewall bridge forward filter rule 10 action 'drop' +set firewall bridge forward filter rule 10 state 'invalid' +{% endif %} +set firewall bridge forward filter default-action '{{ bridge_firewall_bridge_forward_default_action }}' +{% endif %} diff --git a/roles/bridge_firewall/templates/ipv4_rules.yml.j2 b/roles/bridge_firewall/templates/ipv4_rules.yml.j2 new file mode 100644 index 0000000..dec5520 --- /dev/null +++ b/roles/bridge_firewall/templates/ipv4_rules.yml.j2 @@ -0,0 +1,54 @@ +{#- IPv4 firewall for routed traffic and router access, via vyos_firewall_rules. -#} +{% set routed = bridge_firewall_bridges | selectattr('ip_forward', 'defined') | list %} +{% set access = bridge_firewall_bridges | selectattr('router_access', 'defined') | list %} +- afi: ipv4 + rule_sets: +{% if access %} + - filter: input + rules: +{% if bridge_firewall_state_rules | bool %} + - {number: 10, action: accept, state: {established: true, related: true}} + - {number: 20, action: drop, state: {invalid: true}} +{% endif %} +{% for b in access %} + - number: {{ 100 + loop.index * 10 }} + action: {{ 'accept' if b.router_access == 'accept' else 'drop' }} + description: {{ ((b.router_access == 'accept') | ternary('Accept access from ', 'Deny access from ') ~ b.name) | to_json }} + inbound_interface: {group: {{ (b.name ~ '-ifaces') | to_json }}} +{% endfor %} +{% endif %} +{% if routed %} + - filter: forward + default_action: {{ bridge_firewall_ipv4_forward_default_action }} + rules: +{% if bridge_firewall_state_rules | bool %} + - {number: 5, action: accept, state: {established: true, related: true}} + - {number: 10, action: drop, state: {invalid: true}} +{% endif %} +{% for b in routed %} + - number: {{ 100 + loop.index * 10 }} + action: jump + description: {{ (b.name ~ ' traffic') | to_json }} + inbound_interface: {group: {{ (b.name ~ '-ifaces') | to_json }}} + jump_target: {{ ('ip-' ~ b.name ~ '-fwd') | to_json }} +{% endfor %} +{% for b in routed %} + - name: {{ ('ip-' ~ b.name ~ '-fwd') | to_json }} + default_action: {{ b.ip_forward.default_action | default('drop') }} +{% if b.ip_forward.rules | default([]) %} + rules: +{% for r in b.ip_forward.rules %} + - number: {{ r.number | int }} + action: {{ r.action | default('accept') }} +{% if r.description is defined %} + description: {{ r.description | to_json }} +{% endif %} +{% if r.outbound_interface is defined %} + outbound_interface: {name: {{ r.outbound_interface | to_json }}} +{% elif r.outbound_bridge is defined %} + outbound_interface: {group: {{ (r.outbound_bridge ~ '-ifaces') | to_json }}} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/roles/bridge_firewall/vars/main.yml b/roles/bridge_firewall/vars/main.yml new file mode 100644 index 0000000..7299574 --- /dev/null +++ b/roles/bridge_firewall/vars/main.yml @@ -0,0 +1,11 @@ +--- +_bridge_firewall_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_bridge_firewall_cli_lines: "{{ (lookup('ansible.builtin.template', 'bridge_cli.j2') or '').splitlines() | map('trim') | select | list }}" +_bridge_firewall_member_descriptions: >- + {%- set out = [] -%} + {%- for b in bridge_firewall_bridges -%} + {%- for m in b.members if m.description is defined -%} + {%- set _ = out.append({'name': m.interface, 'description': m.description}) -%} + {%- endfor -%} + {%- endfor -%} + {{ out }} diff --git a/roles/firewall/defaults/main.yml b/roles/firewall/defaults/main.yml new file mode 100644 index 0000000..d5bec3f --- /dev/null +++ b/roles/firewall/defaults/main.yml @@ -0,0 +1,4 @@ +--- +firewall_groups: {} +firewall_ipv4: {} +firewall_ipv6: {} diff --git a/roles/firewall/meta/argument_specs.yml b/roles/firewall/meta/argument_specs.yml new file mode 100644 index 0000000..d25fe5d --- /dev/null +++ b/roles/firewall/meta/argument_specs.yml @@ -0,0 +1,77 @@ +--- +argument_specs: + main: + short_description: Firewall groups and base-chain filters + description: + - Configures firewall network, address and port groups and the + C(input), C(forward) and C(output) filters for IPv4 and IPv6. + - Building block used by blueprints such as "Policy-Based Site-to-Site + VPN and Firewall Configuration"; for zone-based designs use + C(vyos.blueprints.zone_firewall). + - Rules use the rule format of vyos.vyos.vyos_firewall_rules (number, + action, description, protocol, state, source, destination, + inbound_interface, outbound_interface, log, ...), passed through unchanged. + - B(Lockout risk) - a C(drop) input filter must allow the address or + interface you manage the router from. + options: + firewall_groups: + type: dict + default: {} + description: Firewall groups. + options: + network: + type: list + elements: dict + default: [] + description: IPv4 network groups. + options: + name: {type: str, required: true, description: Group name.} + description: {type: str, description: Group description.} + networks: {type: list, elements: str, required: true, description: Member networks.} + address: + type: list + elements: dict + default: [] + description: IPv4 address groups. + options: + name: {type: str, required: true, description: Group name.} + description: {type: str, description: Group description.} + addresses: {type: list, elements: str, required: true, description: Member addresses or ranges.} + port: + type: list + elements: dict + default: [] + description: Port groups. + options: + name: {type: str, required: true, description: Group name.} + description: {type: str, description: Group description.} + ports: {type: list, elements: str, required: true, description: Member ports or ranges.} + firewall_ipv4: + type: dict + default: {} + description: IPv4 filters keyed by base chain (C(input), C(forward), C(output)). + options: &filters + input: &filter + type: dict + description: Filter for this chain. + options: + default_action: {type: str, choices: [accept, drop, reject], description: Default action.} + default_log: {type: bool, default: false, description: Log packets hitting the default action.} + rules: {type: list, elements: dict, default: [], description: Rules in vyos_firewall_rules format.} + forward: *filter + output: *filter + firewall_ipv6: + type: dict + default: {} + description: IPv6 filters keyed by base chain. + options: *filters + 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 firewall role + description: Run with C(tasks_from=verify). Checks every configured filter is active with its rules. + options: + firewall_ipv4: {type: dict, default: {}, description: Same value as for C(main).} + firewall_ipv6: {type: dict, default: {}, description: Same value as for C(main).} diff --git a/roles/firewall/meta/main.yml b/roles/firewall/meta/main.yml new file mode 100644 index 0000000..8c38572 --- /dev/null +++ b/roles/firewall/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Stateful firewall - groups and IPv4/IPv6 input, forward and output filters + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, firewall] +dependencies: [] diff --git a/roles/firewall/tasks/main.yml b/roles/firewall/tasks/main.yml new file mode 100644 index 0000000..fe99b15 --- /dev/null +++ b/roles/firewall/tasks/main.yml @@ -0,0 +1,22 @@ +--- +- name: Create firewall groups + vyos.vyos.vyos_firewall_global: + config: "{{ lookup('ansible.builtin.template', 'groups.yml.j2') | from_yaml }}" + state: "{{ _firewall_state }}" + register: _firewall_r_groups + when: firewall_groups | dict2items | map(attribute='value') | select | list | length > 0 + +- name: Configure the input, forward and output filters + vyos.vyos.vyos_firewall_rules: + config: "{{ lookup('ansible.builtin.template', 'rules.yml.j2') | from_yaml }}" + state: "{{ _firewall_state }}" + register: _firewall_r_rules + when: (firewall_ipv4 | length > 0) or (firewall_ipv6 | 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([]) + + ([_firewall_r_groups, _firewall_r_rules] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/firewall/tasks/verify.yml b/roles/firewall/tasks/verify.yml new file mode 100644 index 0000000..c2e3281 --- /dev/null +++ b/roles/firewall/tasks/verify.yml @@ -0,0 +1,23 @@ +--- +- name: Read the firewall + vyos.vyos.vyos_command: + commands: + - show firewall + register: _firewall_v + +- name: Every configured filter is active with its rules + ansible.builtin.assert: + that: >- + _firewall_v.stdout[0] is search((item.afi | upper | replace('V', 'v')) ~ ' Firewall "' ~ item.chain ~ ' filter"') + fail_msg: "{{ item.afi }} {{ item.chain }} filter is not active" + quiet: true + loop: >- + {%- set out = [] -%} + {%- for afi, filters in [('ipv4', firewall_ipv4), ('ipv6', firewall_ipv6)] -%} + {%- for chain in ['input', 'forward', 'output'] if filters[chain] is defined and filters[chain] -%} + {%- set _ = out.append({'afi': afi, 'chain': chain}) -%} + {%- endfor -%} + {%- endfor -%} + {{ out }} + loop_control: + label: "{{ item.afi }} {{ item.chain }}" diff --git a/roles/firewall/templates/groups.yml.j2 b/roles/firewall/templates/groups.yml.j2 new file mode 100644 index 0000000..5641e2a --- /dev/null +++ b/roles/firewall/templates/groups.yml.j2 @@ -0,0 +1,42 @@ +group: +{% if firewall_groups.network | default([]) %} + network_group: +{% for g in firewall_groups.network %} + - name: {{ g.name | to_json }} + afi: ipv4 +{% if g.description is defined %} + description: {{ g.description | to_json }} +{% endif %} + members: +{% for n in g.networks %} + - address: {{ n | to_json }} +{% endfor %} +{% endfor %} +{% endif %} +{% if firewall_groups.address | default([]) %} + address_group: +{% for g in firewall_groups.address %} + - name: {{ g.name | to_json }} + afi: ipv4 +{% if g.description is defined %} + description: {{ g.description | to_json }} +{% endif %} + members: +{% for a in g.addresses %} + - address: {{ a | to_json }} +{% endfor %} +{% endfor %} +{% endif %} +{% if firewall_groups.port | default([]) %} + port_group: +{% for g in firewall_groups.port %} + - name: {{ g.name | to_json }} +{% if g.description is defined %} + description: {{ g.description | to_json }} +{% endif %} + members: +{% for p in g.ports %} + - port: {{ p | string | to_json }} +{% endfor %} +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules.yml.j2 b/roles/firewall/templates/rules.yml.j2 new file mode 100644 index 0000000..f746c84 --- /dev/null +++ b/roles/firewall/templates/rules.yml.j2 @@ -0,0 +1,17 @@ +{% for afi, filters in [('ipv4', firewall_ipv4), ('ipv6', firewall_ipv6)] if filters %} +- afi: {{ afi }} + rule_sets: +{% for chain in _firewall_filters if filters[chain] is defined and filters[chain] %} +{% set f = filters[chain] %} + - filter: {{ chain }} +{% if f.default_action is defined %} + default_action: {{ f.default_action }} +{% endif %} +{% if f.default_log | default(false) | bool %} + enable_default_log: true +{% endif %} +{% if f.rules | default([]) %} + rules: {{ f.rules | to_json }} +{% endif %} +{% endfor %} +{% endfor %} diff --git a/roles/firewall/vars/main.yml b/roles/firewall/vars/main.yml new file mode 100644 index 0000000..eebd515 --- /dev/null +++ b/roles/firewall/vars/main.yml @@ -0,0 +1,3 @@ +--- +_firewall_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_firewall_filters: [input, forward, output] diff --git a/roles/gre_tunnel/defaults/main.yml b/roles/gre_tunnel/defaults/main.yml new file mode 100644 index 0000000..2bb0e43 --- /dev/null +++ b/roles/gre_tunnel/defaults/main.yml @@ -0,0 +1,2 @@ +--- +gre_tunnel_interfaces: [] diff --git a/roles/gre_tunnel/meta/argument_specs.yml b/roles/gre_tunnel/meta/argument_specs.yml new file mode 100644 index 0000000..7916ceb --- /dev/null +++ b/roles/gre_tunnel/meta/argument_specs.yml @@ -0,0 +1,37 @@ +--- +argument_specs: + main: + short_description: Tunnel interfaces (GRE and other encapsulations) + description: + - Creates C(interfaces tunnel) entries with their encapsulation, + endpoints, MTU and TCP MSS clamping, and addresses them. + - Building block used by "Site-to-Site IPSec VPN to Cisco using FlexVPN" + (GRE protected by IPsec); also usable for plain GRE. + - The tunnel itself is created with vyos.vyos.vyos_config in one commit, + because vyos_interfaces cannot set encapsulation or endpoints; + addresses use vyos_l3_interfaces. + options: + gre_tunnel_interfaces: + type: list + elements: dict + required: true + description: Tunnel interfaces. + options: + name: {type: str, required: true, description: "Interface name, e.g. C(tun1)."} + encapsulation: {type: str, default: gre, description: "Encapsulation, e.g. C(gre), C(gretap), C(ipip)."} + source_address: {type: str, description: Local endpoint address.} + source_interface: {type: str, description: Local endpoint interface (instead of C(source_address)).} + remote: {type: str, description: Remote endpoint address.} + mtu: {type: int, description: Interface MTU.} + adjust_mss: {type: str, description: "TCP MSS clamping value, e.g. C(1336) or C(clamp-mss-to-pmtu)."} + description: {type: str, description: Interface description.} + addresses: {type: list, elements: str, default: [], description: IPv4 addresses in CIDR notation.} + 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 gre_tunnel role + description: Run with C(tasks_from=verify). Checks every tunnel interface is up. + options: + gre_tunnel_interfaces: {type: list, elements: dict, required: true, description: Same value as for C(main).} diff --git a/roles/gre_tunnel/meta/main.yml b/roles/gre_tunnel/meta/main.yml new file mode 100644 index 0000000..e4408b4 --- /dev/null +++ b/roles/gre_tunnel/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: GRE (and other) tunnel interfaces + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, gre, tunnel] +dependencies: [] diff --git a/roles/gre_tunnel/tasks/main.yml b/roles/gre_tunnel/tasks/main.yml new file mode 100644 index 0000000..5f8a90a --- /dev/null +++ b/roles/gre_tunnel/tasks/main.yml @@ -0,0 +1,20 @@ +--- +- name: Create the tunnel interfaces + # vyos_interfaces cannot set encapsulation or endpoints, and a tunnel must + # be created with them in one commit, so this goes through vyos_config. + vyos.vyos.vyos_config: + lines: "{{ _gre_tunnel_cli_lines }}" + register: _gre_tunnel_r_cli + when: not (vyos_blueprints_render_only | default(false) | bool) + +- name: Address the tunnel interfaces + vyos.vyos.vyos_l3_interfaces: + config: "{{ _gre_tunnel_l3 }}" + state: "{{ _gre_tunnel_state }}" + register: _gre_tunnel_r_l3 + when: _gre_tunnel_l3 | 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([]) + _gre_tunnel_cli_lines + (_gre_tunnel_r_l3.rendered | default([])) }}" + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/gre_tunnel/tasks/verify.yml b/roles/gre_tunnel/tasks/verify.yml new file mode 100644 index 0000000..52cb45f --- /dev/null +++ b/roles/gre_tunnel/tasks/verify.yml @@ -0,0 +1,15 @@ +--- +- name: Read interfaces + vyos.vyos.vyos_command: + commands: + - show interfaces + register: _gre_tunnel_v + +- name: Every tunnel interface is up + ansible.builtin.assert: + that: _gre_tunnel_v.stdout[0] is search('(?m)^' ~ (item.name | regex_escape) ~ '\s.*\bu/u\b') + fail_msg: "tunnel {{ item.name }} is not up" + quiet: true + loop: "{{ gre_tunnel_interfaces }}" + loop_control: + label: "{{ item.name }}" diff --git a/roles/gre_tunnel/templates/tunnels.j2 b/roles/gre_tunnel/templates/tunnels.j2 new file mode 100644 index 0000000..fb23487 --- /dev/null +++ b/roles/gre_tunnel/templates/tunnels.j2 @@ -0,0 +1,22 @@ +{% for t in gre_tunnel_interfaces %} +{% set b = 'set interfaces tunnel ' ~ t.name %} +{{ b }} encapsulation '{{ t.encapsulation | default('gre') }}' +{% if t.description is defined %} +{{ b }} description '{{ t.description }}' +{% endif %} +{% if t.adjust_mss is defined %} +{{ b }} ip adjust-mss '{{ t.adjust_mss }}' +{% endif %} +{% if t.mtu is defined %} +{{ b }} mtu '{{ t.mtu }}' +{% endif %} +{% if t.remote is defined %} +{{ b }} remote '{{ t.remote }}' +{% endif %} +{% if t.source_address is defined %} +{{ b }} source-address '{{ t.source_address }}' +{% endif %} +{% if t.source_interface is defined %} +{{ b }} source-interface '{{ t.source_interface }}' +{% endif %} +{% endfor %} diff --git a/roles/gre_tunnel/vars/main.yml b/roles/gre_tunnel/vars/main.yml new file mode 100644 index 0000000..87be41e --- /dev/null +++ b/roles/gre_tunnel/vars/main.yml @@ -0,0 +1,11 @@ +--- +_gre_tunnel_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_gre_tunnel_cli_lines: "{{ (lookup('ansible.builtin.template', 'tunnels.j2') or '').splitlines() | select | list }}" +_gre_tunnel_l3: >- + {%- set out = [] -%} + {%- for t in gre_tunnel_interfaces if t.addresses | default([]) -%} + {%- set addrs = [] -%} + {%- for a in t.addresses -%}{%- set _ = addrs.append({'address': a}) -%}{%- endfor -%} + {%- set _ = out.append({'name': t.name, 'ipv4': addrs}) -%} + {%- endfor -%} + {{ out }} diff --git a/roles/ha_vrrp/tasks/main.yml b/roles/ha_vrrp/tasks/main.yml index 5df97b9..7f0b692 100644 --- a/roles/ha_vrrp/tasks/main.yml +++ b/roles/ha_vrrp/tasks/main.yml @@ -21,7 +21,7 @@ vyos.vyos.vyos_config: lines: "{{ _ha_vrrp_conntrack_lines }}" vars: - _ha_vrrp_conntrack_lines: "{{ lookup('ansible.builtin.template', 'conntrack_sync.j2').splitlines() | select | list }}" + _ha_vrrp_conntrack_lines: "{{ (lookup('ansible.builtin.template', 'conntrack_sync.j2') or '').splitlines() | select | list }}" register: _ha_vrrp_r_conntrack when: - _ha_vrrp_cs | length > 0 @@ -32,6 +32,6 @@ vyos_blueprints_rendered: >- {{ vyos_blueprints_rendered | default([]) + _ha_vrrp_r_vrrp.rendered | default([]) - + ((lookup('ansible.builtin.template', 'conntrack_sync.j2').splitlines() | select | list) + + (((lookup('ansible.builtin.template', 'conntrack_sync.j2') or '').splitlines() | select | list) if _ha_vrrp_cs | length > 0 else []) }} when: vyos_blueprints_render_only | default(false) | bool 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' }}" 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 }} diff --git a/roles/nat/defaults/main.yml b/roles/nat/defaults/main.yml new file mode 100644 index 0000000..6ba6f0a --- /dev/null +++ b/roles/nat/defaults/main.yml @@ -0,0 +1,3 @@ +--- +nat_source_rules: [] +nat_destination_rules: [] diff --git a/roles/nat/meta/argument_specs.yml b/roles/nat/meta/argument_specs.yml new file mode 100644 index 0000000..43c260e --- /dev/null +++ b/roles/nat/meta/argument_specs.yml @@ -0,0 +1,33 @@ +--- +argument_specs: + main: + short_description: Source and destination NAT + description: + - Configures source NAT (masquerade, SNAT, exclusions such as + traffic into a VPN) and destination NAT (port forwarding). + - Rules use the rule format of vyos.vyos.vyos_nat (id, description, + protocol, exclude, outbound_interface / inbound_interface, source, + destination, translation, ...), passed through unchanged, so every + option of the module is available. + - For a simple small-office edge, C(vyos.blueprints.edge_nat) is shorter. + options: + nat_source_rules: + type: list + elements: dict + default: [] + description: "Source NAT rules in vyos_nat format, e.g. C({id: 20, outbound_interface: {name: eth0}, translation: {address: masquerade}})." + nat_destination_rules: + type: list + elements: dict + default: [] + description: Destination NAT rules in vyos_nat format. + 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 nat role + description: Run with C(tasks_from=verify). Checks every rule is installed. + options: + nat_source_rules: {type: list, elements: dict, default: [], description: Same value as for C(main).} + nat_destination_rules: {type: list, elements: dict, default: [], description: Same value as for C(main).} diff --git a/roles/nat/meta/main.yml b/roles/nat/meta/main.yml new file mode 100644 index 0000000..682f983 --- /dev/null +++ b/roles/nat/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Source and destination NAT rules + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, nat] +dependencies: [] diff --git a/roles/nat/tasks/main.yml b/roles/nat/tasks/main.yml new file mode 100644 index 0000000..6f5007b --- /dev/null +++ b/roles/nat/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Configure source and destination NAT + vyos.vyos.vyos_nat: + config: + nat: >- + {{ ({'source': {'rule': nat_source_rules}} if nat_source_rules else {}) + | combine({'destination': {'rule': nat_destination_rules}} if nat_destination_rules else {}) }} + state: "{{ _nat_state }}" + register: _nat_r + when: (nat_source_rules | length > 0) or (nat_destination_rules | 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([]) + (_nat_r.rendered | default([])) }}" + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/nat/tasks/verify.yml b/roles/nat/tasks/verify.yml new file mode 100644 index 0000000..a100d3a --- /dev/null +++ b/roles/nat/tasks/verify.yml @@ -0,0 +1,25 @@ +--- +- name: Read NAT rules + vyos.vyos.vyos_command: + commands: + - show nat source rules + - show nat destination rules + register: _nat_v + +- name: Every source NAT rule is installed + ansible.builtin.assert: + that: _nat_v.stdout[0] is search('(?m)^\s*' ~ item.id ~ '\b') + fail_msg: "source NAT rule {{ item.id }} is not installed" + quiet: true + loop: "{{ nat_source_rules }}" + loop_control: + label: "{{ item.id }}" + +- name: Every destination NAT rule is installed + ansible.builtin.assert: + that: _nat_v.stdout[1] is search('(?m)^\s*' ~ item.id ~ '\b') + fail_msg: "destination NAT rule {{ item.id }} is not installed" + quiet: true + loop: "{{ nat_destination_rules }}" + loop_control: + label: "{{ item.id }}" diff --git a/roles/nat/vars/main.yml b/roles/nat/vars/main.yml new file mode 100644 index 0000000..a354487 --- /dev/null +++ b/roles/nat/vars/main.yml @@ -0,0 +1,2 @@ +--- +_nat_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" diff --git a/roles/ospf_unnumbered/defaults/main.yml b/roles/ospf_unnumbered/defaults/main.yml new file mode 100644 index 0000000..b1ed553 --- /dev/null +++ b/roles/ospf_unnumbered/defaults/main.yml @@ -0,0 +1,6 @@ +--- +ospf_unnumbered_area: 0.0.0.0 +ospf_unnumbered_md5_key: "" +ospf_unnumbered_md5_key_id: 1 +ospf_unnumbered_redistribute: [connected] +ospf_unnumbered_group: "" diff --git a/roles/ospf_unnumbered/meta/argument_specs.yml b/roles/ospf_unnumbered/meta/argument_specs.yml new file mode 100644 index 0000000..c5c5407 --- /dev/null +++ b/roles/ospf_unnumbered/meta/argument_specs.yml @@ -0,0 +1,68 @@ +--- +argument_specs: + main: + short_description: OSPF unnumbered with ECMP (docs blueprint "OSPF unnumbered with ECMP") + description: + - Puts the router-id as a /32 on the loopback and on every listed link, + runs OSPF point-to-point over those links and redistributes connected + routes. Parallel links to the same neighbour give ECMP. + - Works for any number of routers; each router lists only its own links. + - LAN addressing (C(eth0) on the docs page) belongs to C(vyos.blueprints.base). + options: + ospf_unnumbered_router_id: + type: str + required: true + description: OSPF router-id; also used as the /32 address on C(lo) and on each link. + ospf_unnumbered_interfaces: + type: list + elements: str + required: true + description: Unnumbered point-to-point links, e.g. C([eth1, eth2]). + ospf_unnumbered_area: + type: str + default: 0.0.0.0 + description: OSPF area. + ospf_unnumbered_md5_key: + type: str + default: "" + no_log: true + description: MD5 key for OSPF authentication. Empty disables authentication. + ospf_unnumbered_md5_key_id: + type: int + default: 1 + description: MD5 key id. + ospf_unnumbered_redistribute: + type: list + elements: str + default: [connected] + description: Route types redistributed into OSPF. + ospf_unnumbered_group: + type: str + default: "" + description: Inventory group of all routers in the fabric; used only by C(verify). + 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 ospf_unnumbered role + description: + - Run with C(tasks_from=verify) against all routers in the same play. + - Checks one Full adjacency per listed link and, with + C(ospf_unnumbered_group), an OSPF route to every other router-id. + When the group has exactly two routers, also checks one ECMP next + hop per link. + options: + ospf_unnumbered_router_id: + type: str + required: true + description: Same value as for C(main). + ospf_unnumbered_interfaces: + type: list + elements: str + required: true + description: Same value as for C(main). + ospf_unnumbered_group: + type: str + default: "" + description: Same value as for C(main). diff --git a/roles/ospf_unnumbered/meta/main.yml b/roles/ospf_unnumbered/meta/main.yml new file mode 100644 index 0000000..c24d6e2 --- /dev/null +++ b/roles/ospf_unnumbered/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: OSPF over unnumbered point-to-point links with ECMP + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, ospf] +dependencies: [] diff --git a/roles/ospf_unnumbered/tasks/main.yml b/roles/ospf_unnumbered/tasks/main.yml new file mode 100644 index 0000000..1ac2c5c --- /dev/null +++ b/roles/ospf_unnumbered/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Put the router-id on the loopback and on every unnumbered link + vyos.vyos.vyos_l3_interfaces: + config: "{{ lookup('ansible.builtin.template', 'l3_interfaces.yml.j2') | from_yaml }}" + state: "{{ _ospf_unnumbered_state }}" + register: _ospf_unnumbered_r_l3 + +- name: Run OSPF point-to-point on the links + vyos.vyos.vyos_ospf_interfaces: + config: "{{ lookup('ansible.builtin.template', 'ospf_interfaces.yml.j2') | from_yaml }}" + state: "{{ _ospf_unnumbered_state }}" + register: _ospf_unnumbered_r_ifaces + no_log: "{{ ospf_unnumbered_md5_key | length > 0 }}" + +- name: Configure the OSPF process + vyos.vyos.vyos_ospfv2: + config: "{{ lookup('ansible.builtin.template', 'ospfv2.yml.j2') | from_yaml }}" + state: "{{ _ospf_unnumbered_state }}" + register: _ospf_unnumbered_r_ospf + +- 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([]) + + ([_ospf_unnumbered_r_l3, _ospf_unnumbered_r_ifaces, _ospf_unnumbered_r_ospf] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten + | map('regex_replace', '(md5-key) \S+', "\1 '********'")) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/ospf_unnumbered/tasks/verify.yml b/roles/ospf_unnumbered/tasks/verify.yml new file mode 100644 index 0000000..50b96fd --- /dev/null +++ b/roles/ospf_unnumbered/tasks/verify.yml @@ -0,0 +1,42 @@ +--- +- name: Read OSPF neighbours + vyos.vyos.vyos_command: + commands: + - show ip ospf neighbor + register: _ospf_unnumbered_v_nbr + +- name: One Full adjacency per unnumbered link + ansible.builtin.assert: + that: _ospf_unnumbered_v_nbr.stdout[0] is search('Full.*\s' ~ item ~ ':') + fail_msg: "no Full OSPF adjacency on {{ item }}" + quiet: true + loop: "{{ ospf_unnumbered_interfaces }}" + +- name: Read routes to the other routers + vyos.vyos.vyos_command: + commands: "{{ _ospf_unnumbered_peers | map('regex_replace', '^(.*)$', 'show ip route \\1/32') | list }}" + vars: + _ospf_unnumbered_peers: >- + {{ groups[ospf_unnumbered_group] | difference([inventory_hostname]) + | map('extract', hostvars, 'ospf_unnumbered_router_id') | list }} + register: _ospf_unnumbered_v_routes + when: ospf_unnumbered_group | length > 0 + +- name: Every other router-id is reached via OSPF + ansible.builtin.assert: + that: item is search('Known via "ospf"') + fail_msg: "{{ item | regex_search('Routing entry for \\S+') | default('a peer', true) }} is not learned via OSPF" + quiet: true + loop: "{{ _ospf_unnumbered_v_routes.stdout | default([]) }}" + loop_control: + label: "{{ item | regex_search('Routing entry for \\S+') | default('route', true) }}" + when: ospf_unnumbered_group | length > 0 + +- name: With two routers, one ECMP next hop per link + ansible.builtin.assert: + that: _ospf_unnumbered_v_routes.stdout[0] | regex_findall('via \S+ onlink') | length == ospf_unnumbered_interfaces | length + fail_msg: "expected {{ ospf_unnumbered_interfaces | length }} ECMP next hops to the peer" + quiet: true + when: + - ospf_unnumbered_group | length > 0 + - groups[ospf_unnumbered_group] | length == 2 diff --git a/roles/ospf_unnumbered/templates/l3_interfaces.yml.j2 b/roles/ospf_unnumbered/templates/l3_interfaces.yml.j2 new file mode 100644 index 0000000..ccd1962 --- /dev/null +++ b/roles/ospf_unnumbered/templates/l3_interfaces.yml.j2 @@ -0,0 +1,8 @@ +- name: lo + ipv4: + - address: {{ _ospf_unnumbered_prefix | to_json }} +{% for i in ospf_unnumbered_interfaces %} +- name: {{ i }} + ipv4: + - address: {{ _ospf_unnumbered_prefix | to_json }} +{% endfor %} diff --git a/roles/ospf_unnumbered/templates/ospf_interfaces.yml.j2 b/roles/ospf_unnumbered/templates/ospf_interfaces.yml.j2 new file mode 100644 index 0000000..c119640 --- /dev/null +++ b/roles/ospf_unnumbered/templates/ospf_interfaces.yml.j2 @@ -0,0 +1,12 @@ +{% for i in ospf_unnumbered_interfaces %} +- name: {{ i }} + address_family: + - afi: ipv4 + network: point-to-point +{% if ospf_unnumbered_md5_key %} + authentication: + md5_key: + key_id: {{ ospf_unnumbered_md5_key_id | int }} + key: {{ ospf_unnumbered_md5_key | to_json }} +{% endif %} +{% endfor %} diff --git a/roles/ospf_unnumbered/templates/ospfv2.yml.j2 b/roles/ospf_unnumbered/templates/ospfv2.yml.j2 new file mode 100644 index 0000000..0b24f2b --- /dev/null +++ b/roles/ospf_unnumbered/templates/ospfv2.yml.j2 @@ -0,0 +1,15 @@ +parameters: + router_id: {{ ospf_unnumbered_router_id | to_json }} +areas: + - area_id: {{ ospf_unnumbered_area | string | to_json }} +{% if ospf_unnumbered_md5_key %} + authentication: md5 +{% endif %} + network: + - address: {{ _ospf_unnumbered_prefix | to_json }} +{% if ospf_unnumbered_redistribute %} +redistribute: +{% for r in ospf_unnumbered_redistribute %} + - route_type: {{ r }} +{% endfor %} +{% endif %} diff --git a/roles/ospf_unnumbered/vars/main.yml b/roles/ospf_unnumbered/vars/main.yml new file mode 100644 index 0000000..3bf0497 --- /dev/null +++ b/roles/ospf_unnumbered/vars/main.yml @@ -0,0 +1,3 @@ +--- +_ospf_unnumbered_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_ospf_unnumbered_prefix: "{{ ospf_unnumbered_router_id }}/32" diff --git a/roles/vrf_firewall/defaults/main.yml b/roles/vrf_firewall/defaults/main.yml new file mode 100644 index 0000000..204728e --- /dev/null +++ b/roles/vrf_firewall/defaults/main.yml @@ -0,0 +1,11 @@ +--- +vrf_firewall_bind_to_all: true +vrf_firewall_forward_rules: [] +vrf_firewall_forward_default_action: drop +vrf_firewall_input_rules: [] +vrf_firewall_input_default_action: drop +vrf_firewall_default_log: true +vrf_firewall_state_policy: + established: accept + related: accept + invalid: drop diff --git a/roles/vrf_firewall/meta/argument_specs.yml b/roles/vrf_firewall/meta/argument_specs.yml new file mode 100644 index 0000000..91f569d --- /dev/null +++ b/roles/vrf_firewall/meta/argument_specs.yml @@ -0,0 +1,139 @@ +--- +argument_specs: + main: + short_description: VRFs with route leaking and firewall (docs blueprint "VRF and firewall example") + description: + - Creates VRFs with their routing tables, puts interfaces (including + VLANs) into them, adds static routes including routes into another VRF + (route leaking), sets the global state policy and configures the IPv4 + forward and input filters. + - Interface addressing belongs to C(vyos.blueprints.base); PPPoE is not + configured here. + - VLAN, PPPoE and other non-ethernet VRF bindings and inter-VRF routes + use vyos.vyos.vyos_config, because vyos_interfaces and vyos_vrf cannot + express them yet. Everything else uses resource modules. + - In firewall rules, match an interface in a non-default VRF by the VRF + name for C(inbound_interface) and by the interface name for + C(outbound_interface), as the docs page explains. + - B(Lockout risk) - the input filter defaults to drop. Allow the + interface or VRF you manage the router through before running the role. + options: + vrf_firewall_vrfs: + type: list + elements: dict + required: true + description: VRFs. + options: + name: + type: str + required: true + description: VRF name, e.g. C(MGMT). + table: + type: int + required: true + description: Routing table id. + description: + type: str + description: VRF description. + interfaces: + type: list + elements: str + default: [] + description: Member interfaces, e.g. C(eth1), C(eth2.150), C(pppoe0). + routes: + type: list + elements: dict + default: [] + description: IPv4 static routes in this VRF. + options: + dest: + type: str + required: true + description: Destination prefix. + interface: + type: str + description: Outgoing interface. + vrf: + type: str + description: VRF the interface belongs to, for routes into another VRF. + next_hop: + type: str + description: Next-hop address (routes within this VRF only). + vrf_firewall_bind_to_all: + type: bool + default: true + description: Set C(vrf bind-to-all), as on the docs page. + vrf_firewall_forward_rules: + type: list + elements: dict + default: [] + description: Rules for traffic between VRFs (C(firewall ipv4 forward filter)). + options: &rule + number: + type: int + required: true + description: Rule number. + action: + type: str + choices: [accept, drop, reject] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + inbound_interface: + type: str + description: Inbound interface or VRF name; wildcards like C(eth2*) allowed. + outbound_interface: + type: str + description: Outbound interface name; wildcards allowed. + protocol: + type: str + description: Protocol. + source: + type: dict + description: Source match (C(address), C(port)). + destination: + type: dict + description: Destination match (C(address), C(port)). + log: + type: bool + default: false + description: Log matches. + vrf_firewall_forward_default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Forward filter default action. + vrf_firewall_input_rules: + type: list + elements: dict + default: [] + description: Rules for traffic to the router itself (C(firewall ipv4 input filter)). + options: *rule + vrf_firewall_input_default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Input filter default action. + vrf_firewall_default_log: + type: bool + default: true + description: Log packets hitting the filters' default action. + vrf_firewall_state_policy: + type: dict + default: {established: accept, related: accept, invalid: drop} + description: Global state policy, connection type to action. + 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 vrf_firewall role + description: Run with C(tasks_from=verify). Checks every VRF exists with its member interfaces. + options: + vrf_firewall_vrfs: + type: list + elements: dict + required: true + description: Same value as for C(main). diff --git a/roles/vrf_firewall/meta/main.yml b/roles/vrf_firewall/meta/main.yml new file mode 100644 index 0000000..63f2c52 --- /dev/null +++ b/roles/vrf_firewall/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: VRFs with inter-VRF route leaking and forward/input firewall filters + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, vrf, firewall] +dependencies: [] diff --git a/roles/vrf_firewall/tasks/main.yml b/roles/vrf_firewall/tasks/main.yml new file mode 100644 index 0000000..2e38f04 --- /dev/null +++ b/roles/vrf_firewall/tasks/main.yml @@ -0,0 +1,60 @@ +--- +- name: Check interface names + ansible.builtin.assert: + that: item.1 is match('^(' ~ (_vrf_firewall_iftypes.keys() | join('|')) ~ ')\d+(\.\d+)?$') + fail_msg: >- + {{ item.1 }} (VRF {{ item.0.name }}) is not a supported interface name; + expected one of {{ _vrf_firewall_iftypes.keys() | join(', ') }} followed by a number, optionally .VLAN + quiet: true + loop: "{{ vrf_firewall_vrfs | subelements('interfaces', skip_missing=true) }}" + loop_control: + label: "{{ item.0.name }} {{ item.1 }}" + +- name: Create VRFs and their tables + vyos.vyos.vyos_vrf: + config: "{{ lookup('ansible.builtin.template', 'vrf.yml.j2') | from_yaml }}" + state: "{{ _vrf_firewall_state }}" + register: _vrf_firewall_r_vrf + +- name: Put ethernet interfaces into their VRFs + vyos.vyos.vyos_interfaces: + config: "{{ _vrf_firewall_eth_bindings }}" + state: "{{ _vrf_firewall_state }}" + register: _vrf_firewall_r_eth + when: _vrf_firewall_eth_bindings | length > 0 + +- name: Bind VLAN and other interfaces, add inter-VRF routes + # vyos_interfaces cannot set a VRF on a VLAN, and vyos_vrf static routes + # cannot point into another VRF, so these go through vyos_config. + vyos.vyos.vyos_config: + lines: "{{ _vrf_firewall_cli_lines }}" + register: _vrf_firewall_r_cli + when: + - _vrf_firewall_cli_lines | length > 0 + - not (vyos_blueprints_render_only | default(false) | bool) + +- name: Set the global state policy + vyos.vyos.vyos_firewall_global: + config: + state_policy: >- + {{ vrf_firewall_state_policy | dict2items(key_name='connection_type', value_name='action') }} + state: "{{ _vrf_firewall_state }}" + register: _vrf_firewall_r_state + when: vrf_firewall_state_policy | length > 0 + +- name: Configure the forward and input filters + vyos.vyos.vyos_firewall_rules: + config: "{{ lookup('ansible.builtin.template', 'firewall_rules.yml.j2') | from_yaml }}" + state: "{{ _vrf_firewall_state }}" + register: _vrf_firewall_r_fw + +- 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([]) + + ([_vrf_firewall_r_vrf, _vrf_firewall_r_eth] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten) + + _vrf_firewall_cli_lines + + ([_vrf_firewall_r_state, _vrf_firewall_r_fw] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/vrf_firewall/tasks/verify.yml b/roles/vrf_firewall/tasks/verify.yml new file mode 100644 index 0000000..1732ced --- /dev/null +++ b/roles/vrf_firewall/tasks/verify.yml @@ -0,0 +1,26 @@ +--- +- name: Read VRFs + vyos.vyos.vyos_command: + commands: + - show vrf + register: _vrf_firewall_v + +- name: Every VRF exists + ansible.builtin.assert: + that: _vrf_firewall_v.stdout[0] is search('(?m)^\s*' ~ (item.name | regex_escape) ~ '\s') + fail_msg: "VRF {{ item.name }} is missing from 'show vrf'" + quiet: true + loop: "{{ vrf_firewall_vrfs }}" + loop_control: + label: "{{ item.name }}" + +- name: Every interface is in its VRF + ansible.builtin.assert: + that: >- + _vrf_firewall_v.stdout[0] is search('(?m)^\s*' ~ (item.0.name | regex_escape) + ~ '\s.*\b' ~ (item.1 | regex_escape) ~ '\b') + fail_msg: "{{ item.1 }} is not a member of VRF {{ item.0.name }}" + quiet: true + loop: "{{ vrf_firewall_vrfs | subelements('interfaces', skip_missing=true) }}" + loop_control: + label: "{{ item.0.name }} {{ item.1 }}" diff --git a/roles/vrf_firewall/templates/firewall_rules.yml.j2 b/roles/vrf_firewall/templates/firewall_rules.yml.j2 new file mode 100644 index 0000000..71f5ae0 --- /dev/null +++ b/roles/vrf_firewall/templates/firewall_rules.yml.j2 @@ -0,0 +1,45 @@ +{% macro rules(lst) %} +{% for r in lst %} + - number: {{ r.number | int }} + action: {{ r.action | default('accept') }} +{% if r.description is defined %} + description: {{ r.description | to_json }} +{% endif %} +{% if r.protocol is defined %} + protocol: {{ r.protocol | to_json }} +{% endif %} +{% if r.log | default(false) | bool %} + log: enable +{% endif %} +{% for side in ['inbound_interface', 'outbound_interface'] if r[side] is defined %} + {{ side }}: + name: {{ r[side] | to_json }} +{% endfor %} +{% for side in ['source', 'destination'] if r[side] is defined and r[side] %} + {{ side }}: +{% for k in ['address', 'port'] if r[side][k] is defined %} + {{ k }}: {{ r[side][k] | string | to_json }} +{% endfor %} +{% endfor %} +{% endfor %} +{% endmacro %} +- afi: ipv4 + rule_sets: + - filter: forward + default_action: {{ vrf_firewall_forward_default_action }} +{% if vrf_firewall_default_log | bool %} + enable_default_log: true +{% endif %} +{% if vrf_firewall_forward_rules %} + rules: +{{ rules(vrf_firewall_forward_rules) -}} +{% endif %} + - filter: input + default_action: {{ vrf_firewall_input_default_action }} +{% if vrf_firewall_default_log | bool %} + enable_default_log: true +{% endif %} +{% if vrf_firewall_input_rules %} + rules: +{{ rules(vrf_firewall_input_rules) -}} +{% endif %} diff --git a/roles/vrf_firewall/templates/vrf.yml.j2 b/roles/vrf_firewall/templates/vrf.yml.j2 new file mode 100644 index 0000000..808dde2 --- /dev/null +++ b/roles/vrf_firewall/templates/vrf.yml.j2 @@ -0,0 +1,23 @@ +bind_to_all: {{ vrf_firewall_bind_to_all | bool }} +instances: +{% for v in vrf_firewall_vrfs %} + - name: {{ v.name | to_json }} + table_id: {{ v.table | int }} +{% if v.description is defined %} + description: {{ v.description | to_json }} +{% endif %} +{% set plain = v.routes | default([]) | rejectattr('vrf', 'defined') | list %} +{% if plain %} + protocols: + static: + - address_families: + - afi: ipv4 + routes: +{% for r in plain %} + - dest: {{ r.dest | to_json }} + next_hops: + - {% if r.next_hop is defined %}forward_router_address: {{ r.next_hop | to_json }}{% else %}interface: {{ r.interface | to_json }}{% endif %} + +{% endfor %} +{% endif %} +{% endfor %} diff --git a/roles/vrf_firewall/templates/vrf_cli.j2 b/roles/vrf_firewall/templates/vrf_cli.j2 new file mode 100644 index 0000000..2470e4b --- /dev/null +++ b/roles/vrf_firewall/templates/vrf_cli.j2 @@ -0,0 +1,15 @@ +{# VRF bindings for non-plain-ethernet interfaces, and inter-VRF leak routes. #} +{% for v in vrf_firewall_vrfs %} +{% for i in v.interfaces | default([]) if i is not match('^eth\d+$') %} +{% set m = i | regex_search('^([a-z]+)(\d+)(?:\.(\d+))?$', '\\1', '\\2', '\\3') %} +{% set kind = _vrf_firewall_iftypes[m[0]] %} +{% if m[2] %} +set interfaces {{ kind }} {{ m[0] ~ m[1] }} vif {{ m[2] }} vrf '{{ v.name }}' +{% else %} +set interfaces {{ kind }} {{ i }} vrf '{{ v.name }}' +{% endif %} +{% endfor %} +{% for r in v.routes | default([]) if r.vrf is defined %} +set vrf name {{ v.name }} protocols static route {{ r.dest }} interface {{ r.interface }} vrf '{{ r.vrf }}' +{% endfor %} +{% endfor %} diff --git a/roles/vrf_firewall/vars/main.yml b/roles/vrf_firewall/vars/main.yml new file mode 100644 index 0000000..eb31c3d --- /dev/null +++ b/roles/vrf_firewall/vars/main.yml @@ -0,0 +1,24 @@ +--- +_vrf_firewall_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +# Interface name prefix -> CLI interface type, for VRF bindings the resource +# modules cannot express (VLANs, PPPoE, tunnels, ...). +_vrf_firewall_iftypes: + eth: ethernet + bond: bonding + br: bridge + dum: dummy + pppoe: pppoe + vtun: openvpn + wg: wireguard + tun: tunnel + vti: vti +# Ethernet interfaces without a VLAN go through vyos_interfaces; the rest via CLI. +_vrf_firewall_eth_bindings: >- + {%- set out = [] -%} + {%- for v in vrf_firewall_vrfs -%} + {%- for i in v.interfaces | default([]) if i is match('^eth\\d+$') -%} + {%- set _ = out.append({'name': i, 'vrf': v.name}) -%} + {%- endfor -%} + {%- endfor -%} + {{ out }} +_vrf_firewall_cli_lines: "{{ (lookup('ansible.builtin.template', 'vrf_cli.j2') or '').splitlines() | select | list }}" diff --git a/roles/zone_firewall/defaults/main.yml b/roles/zone_firewall/defaults/main.yml new file mode 100644 index 0000000..6bb4a1a --- /dev/null +++ b/roles/zone_firewall/defaults/main.yml @@ -0,0 +1,6 @@ +--- +zone_firewall_policies: [] +zone_firewall_ipv6: true +zone_firewall_all_pairs: true +zone_firewall_base_rules: true +zone_firewall_log_rules: true diff --git a/roles/zone_firewall/meta/argument_specs.yml b/roles/zone_firewall/meta/argument_specs.yml new file mode 100644 index 0000000..fa47473 --- /dev/null +++ b/roles/zone_firewall/meta/argument_specs.yml @@ -0,0 +1,151 @@ +--- +argument_specs: + main: + short_description: Zone-based firewall (docs blueprint "Zone-Policy example") + description: + - Defines zones, one ruleset per zone-pair-direction named + C(<from>-<to>) for IPv4 and C(<from>-<to>-6) for IPv6, and binds the + rulesets to the zones, following the practices on the docs page. + - Every ruleset gets C(default-action drop), C(default-log) and the two + base rules from the page (1 accept established/related, 2 drop and log + invalid). Rules 1 and 2 are therefore reserved. + - With C(zone_firewall_all_pairs), rulesets are created for every zone + pair, including pairs that never communicate, so attempts are logged. + - Uses vyos.vyos.vyos_firewall_rules and vyos.vyos.vyos_firewall_global + (zone support requires a vyos.vyos release that includes it). + - B(Lockout risk) as the page warns - put the interface you manage the + router through into a zone with rules allowing SSH to the local zone + before running this role. + options: + zone_firewall_zones: + type: list + elements: dict + required: true + description: Zones. Exactly one may be the local zone (the router itself). + options: + name: + type: str + required: true + description: Zone name, e.g. C(lan). + interfaces: + type: list + elements: str + default: [] + description: Member interfaces, e.g. C([eth0.20]). Not used for the local zone. + local: + type: bool + default: false + description: This zone is the router itself. + default_action: + type: str + choices: [drop, reject] + default: drop + description: Zone default action (zones cannot accept). + default_log: + type: bool + default: true + description: Log packets hitting the zone default action. + description: + type: str + description: Zone description. + zone_firewall_policies: + type: list + elements: dict + default: [] + description: Rules per zone-pair-direction (traffic from C(from) to C(to)). + options: + from: + type: str + required: true + description: Source zone. + to: + type: str + required: true + description: Destination zone. + default_action: + type: str + choices: [drop, reject, accept] + default: drop + description: Ruleset default action, e.g. C(accept) for a management zone. + rules: + type: list + elements: dict + default: [] + description: Rules added after the base rules. + options: + number: + type: int + required: true + description: Rule number (not 1 or 2 while base rules are on). + action: + type: str + choices: [accept, drop, reject] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + protocol: + type: str + description: + - Protocol, e.g. C(tcp), C(udp), C(tcp_udp), C(icmp). + - C(icmp) becomes C(ipv6-icmp) in the IPv6 ruleset. + source: + type: dict + description: Source match. + options: + address: + type: str + description: Address or network. + port: + type: str + description: Port(s), e.g. C(22) or C(20,21). + destination: + type: dict + description: Destination match. + options: + address: + type: str + description: Address or network. + port: + type: str + description: Port(s), e.g. C(80,443). + family: + type: str + choices: [ipv4, ipv6, both] + description: + - Rulesets the rule goes into. Inferred when omitted - an + IPv6 address means C(ipv6), an IPv4 address C(ipv4), no + address C(both). + log: + type: bool + description: Log matches. Defaults to C(zone_firewall_log_rules). + zone_firewall_ipv6: + type: bool + default: true + description: Also create IPv6 rulesets and bind them. + zone_firewall_all_pairs: + type: bool + default: true + description: Create a (base rules only) ruleset for every zone pair not listed. + zone_firewall_base_rules: + type: bool + default: true + description: Add the page's rules 1 (established/related) and 2 (invalid) to every ruleset. + zone_firewall_log_rules: + type: bool + default: true + description: Default for C(log) on user rules, as the page recommends. + 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 zone_firewall role + description: Run with C(tasks_from=verify). Checks every zone and its member interfaces are active. + options: + zone_firewall_zones: + type: list + elements: dict + required: true + description: Same value as for C(main). diff --git a/roles/zone_firewall/meta/main.yml b/roles/zone_firewall/meta/main.yml new file mode 100644 index 0000000..c83c9f4 --- /dev/null +++ b/roles/zone_firewall/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Zone-based firewall with per zone-pair rulesets for IPv4 and IPv6 + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, firewall, zone] +dependencies: [] diff --git a/roles/zone_firewall/tasks/main.yml b/roles/zone_firewall/tasks/main.yml new file mode 100644 index 0000000..469dc24 --- /dev/null +++ b/roles/zone_firewall/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Check zones and policies + ansible.builtin.assert: + that: + - zone_firewall_zones | selectattr('local', 'defined') | selectattr('local') | list | length <= 1 + - (zone_firewall_policies | map(attribute='from') | list + zone_firewall_policies | map(attribute='to') | list) + | difference(_zone_firewall_names) | length == 0 + - not (zone_firewall_base_rules | bool) + or (zone_firewall_policies | map(attribute='rules', default=[]) | flatten + | map(attribute='number') | select('in', [1, 2]) | list | length == 0) + fail_msg: >- + At most one local zone; every policy must reference defined zones; + rule numbers 1 and 2 are reserved for the base rules. + quiet: true + +- name: Create one ruleset per zone-pair-direction + vyos.vyos.vyos_firewall_rules: + config: "{{ lookup('ansible.builtin.template', 'firewall_rules.yml.j2') | from_yaml }}" + state: "{{ _zone_firewall_state }}" + register: _zone_firewall_r_rules + +- name: Define zones and bind the rulesets + vyos.vyos.vyos_firewall_global: + config: "{{ lookup('ansible.builtin.template', 'zones.yml.j2') | from_yaml }}" + state: "{{ _zone_firewall_state }}" + register: _zone_firewall_r_zones + +- 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([]) + + ([_zone_firewall_r_rules, _zone_firewall_r_zones] + | selectattr('rendered', 'defined') | map(attribute='rendered') | flatten) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/zone_firewall/tasks/verify.yml b/roles/zone_firewall/tasks/verify.yml new file mode 100644 index 0000000..110eeac --- /dev/null +++ b/roles/zone_firewall/tasks/verify.yml @@ -0,0 +1,24 @@ +--- +- name: Read zone policy + vyos.vyos.vyos_command: + commands: + - show firewall zone-policy + register: _zone_firewall_v + +- name: Every zone is active + ansible.builtin.assert: + that: _zone_firewall_v.stdout[0] is search('(?m)^\s*' ~ (item.name | regex_escape) ~ '\s') + fail_msg: "zone {{ item.name }} is missing from 'show firewall zone-policy'" + quiet: true + loop: "{{ zone_firewall_zones }}" + loop_control: + label: "{{ item.name }}" + +- name: Member interfaces are bound + ansible.builtin.assert: + that: _zone_firewall_v.stdout[0] is search('\b' ~ (item.1 | regex_escape) ~ '\b') + fail_msg: "interface {{ item.1 }} of zone {{ item.0.name }} is not bound" + quiet: true + loop: "{{ zone_firewall_zones | subelements('interfaces', skip_missing=true) }}" + loop_control: + label: "{{ item.0.name }} {{ item.1 }}" diff --git a/roles/zone_firewall/templates/firewall_rules.yml.j2 b/roles/zone_firewall/templates/firewall_rules.yml.j2 new file mode 100644 index 0000000..0e083f3 --- /dev/null +++ b/roles/zone_firewall/templates/firewall_rules.yml.j2 @@ -0,0 +1,47 @@ +{%- macro rule_family(r) -%} +{%- set addrs = [r.source.address | default('') if r.source is defined and r.source else '', + r.destination.address | default('') if r.destination is defined and r.destination else ''] | select | list -%} +{%- if r.family is defined and r.family -%}{{ r.family }} +{%- elif addrs | select('search', ':') | list -%}ipv6 +{%- elif addrs -%}ipv4 +{%- else -%}both +{%- endif -%} +{%- endmacro -%} +{% for afi in _zone_firewall_families %} +- afi: {{ afi }} + rule_sets: +{% for p in _zone_firewall_policies %} + - name: {{ (p['from'] ~ '-' ~ p['to'] ~ ('-6' if afi == 'ipv6' else '')) | to_json }} + default_action: {{ p.default_action | default('drop') }} + enable_default_log: true + rules: +{% if zone_firewall_base_rules | bool %} + - number: 1 + action: accept + state: {established: true, related: true} + - number: 2 + action: drop + log: enable + state: {invalid: true} +{% endif %} +{% for r in p.rules | default([]) if rule_family(r) in [afi, 'both'] %} + - number: {{ r.number | int }} + action: {{ r.action | default('accept') }} +{% if r.description is defined %} + description: {{ r.description | to_json }} +{% endif %} +{% if r.protocol is defined %} + protocol: {{ ('ipv6-icmp' if (afi == 'ipv6' and r.protocol == 'icmp') else r.protocol) | to_json }} +{% endif %} +{% if r.log | default(zone_firewall_log_rules) | bool %} + log: enable +{% endif %} +{% for side in ['source', 'destination'] if r[side] is defined and r[side] %} + {{ side }}: +{% for k in ['address', 'port'] if r[side][k] is defined %} + {{ k }}: {{ r[side][k] | string | to_json }} +{% endfor %} +{% endfor %} +{% endfor %} +{% endfor %} +{% endfor %} diff --git a/roles/zone_firewall/templates/zones.yml.j2 b/roles/zone_firewall/templates/zones.yml.j2 new file mode 100644 index 0000000..3af0e99 --- /dev/null +++ b/roles/zone_firewall/templates/zones.yml.j2 @@ -0,0 +1,28 @@ +zone: +{% for z in zone_firewall_zones %} + - name: {{ z.name | to_json }} + default_action: {{ z.default_action | default('drop') }} +{% if z.default_log | default(true) | bool %} + default_log: true +{% endif %} +{% if z.description is defined %} + description: {{ z.description | to_json }} +{% endif %} +{% if z.local | default(false) | bool %} + local_zone: true +{% else %} + interfaces: {{ z.interfaces | default([]) | to_json }} +{% endif %} +{% set srcs = _zone_firewall_policies | selectattr('to', 'equalto', z.name) | list %} +{% if srcs %} + sources: +{% for p in srcs %} + - zone: {{ p['from'] | to_json }} + firewall: + name: {{ (p['from'] ~ '-' ~ z.name) | to_json }} +{% if zone_firewall_ipv6 | bool %} + ipv6_name: {{ (p['from'] ~ '-' ~ z.name ~ '-6') | to_json }} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} diff --git a/roles/zone_firewall/vars/main.yml b/roles/zone_firewall/vars/main.yml new file mode 100644 index 0000000..14ffa31 --- /dev/null +++ b/roles/zone_firewall/vars/main.yml @@ -0,0 +1,17 @@ +--- +_zone_firewall_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_zone_firewall_families: "{{ ['ipv4', 'ipv6'] if zone_firewall_ipv6 | bool else ['ipv4'] }}" +_zone_firewall_names: "{{ zone_firewall_zones | map(attribute='name') | list }}" +# Listed policies plus, with zone_firewall_all_pairs, an empty policy for every +# other ordered zone pair, so every zone-pair-direction has a logged ruleset. +_zone_firewall_policies: >- + {%- set out = zone_firewall_policies | list -%} + {%- if zone_firewall_all_pairs | bool -%} + {%- set listed = zone_firewall_policies | map(attribute='from') | zip(zone_firewall_policies | map(attribute='to')) | map('join', '>') | list -%} + {%- for a in _zone_firewall_names -%} + {%- for b in _zone_firewall_names if b != a and (a ~ '>' ~ b) not in listed -%} + {%- set _ = out.append({'from': a, 'to': b, 'rules': []}) -%} + {%- endfor -%} + {%- endfor -%} + {%- endif -%} + {{ out }} diff --git a/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerA.txt b/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerA.txt new file mode 100644 index 0000000..9e27ddc --- /dev/null +++ b/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerA.txt @@ -0,0 +1,16 @@ +set interfaces loopback lo address '192.168.0.1/32' +set protocols bgp address-family ipv4-unicast redistribute connected +set protocols bgp address-family ipv6-unicast redistribute connected +set protocols bgp neighbor eth1 interface v6only +set protocols bgp neighbor eth1 interface v6only peer-group 'fabric' +set protocols bgp neighbor eth2 interface v6only +set protocols bgp neighbor eth2 interface v6only peer-group 'fabric' +set protocols bgp parameters bestpath as-path multipath-relax +set protocols bgp parameters bestpath compare-routerid +set protocols bgp parameters default no-ipv4-unicast +set protocols bgp parameters router-id 192.168.0.1 +set protocols bgp peer-group fabric address-family ipv4-unicast +set protocols bgp peer-group fabric address-family ipv6-unicast +set protocols bgp peer-group fabric capability extended-nexthop +set protocols bgp peer-group fabric remote-as 'external' +set protocols bgp system-as 64496 diff --git a/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerB.txt b/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerB.txt new file mode 100644 index 0000000..05ae943 --- /dev/null +++ b/tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerB.txt @@ -0,0 +1,16 @@ +set interfaces loopback lo address '192.168.0.2/32' +set protocols bgp address-family ipv4-unicast redistribute connected +set protocols bgp address-family ipv6-unicast redistribute connected +set protocols bgp neighbor eth1 interface v6only +set protocols bgp neighbor eth1 interface v6only peer-group 'fabric' +set protocols bgp neighbor eth2 interface v6only +set protocols bgp neighbor eth2 interface v6only peer-group 'fabric' +set protocols bgp parameters bestpath as-path multipath-relax +set protocols bgp parameters bestpath compare-routerid +set protocols bgp parameters default no-ipv4-unicast +set protocols bgp parameters router-id 192.168.0.2 +set protocols bgp peer-group fabric address-family ipv4-unicast +set protocols bgp peer-group fabric address-family ipv6-unicast +set protocols bgp peer-group fabric capability extended-nexthop +set protocols bgp peer-group fabric remote-as 'external' +set protocols bgp system-as 64499 diff --git a/tests/render/cases/bgp_unnumbered/docs_two_routers/inventory.yml b/tests/render/cases/bgp_unnumbered/docs_two_routers/inventory.yml new file mode 100644 index 0000000..ee03ea7 --- /dev/null +++ b/tests/render/cases/bgp_unnumbered/docs_two_routers/inventory.yml @@ -0,0 +1,19 @@ +--- +all: + children: + fabric: + hosts: + routerA: + ansible_host: 192.0.2.1 + bgp_unnumbered_asn: 64496 + bgp_unnumbered_router_id: 192.168.0.1 + routerB: + ansible_host: 192.0.2.2 + bgp_unnumbered_asn: 64499 + bgp_unnumbered_router_id: 192.168.0.2 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_connect_timeout: 5 + ansible_command_timeout: 5 diff --git a/tests/render/cases/bgp_unnumbered/docs_two_routers/vars.yml b/tests/render/cases/bgp_unnumbered/docs_two_routers/vars.yml new file mode 100644 index 0000000..7b92717 --- /dev/null +++ b/tests/render/cases/bgp_unnumbered/docs_two_routers/vars.yml @@ -0,0 +1,3 @@ +--- +# docs.vyos.io/en/1.5/configexamples/bgp-ipv6-unnumbered.html - shared settings +bgp_unnumbered_interfaces: [eth1, eth2] diff --git a/tests/render/cases/blueprints/flexvpn_cisco/expected/r1.txt b/tests/render/cases/blueprints/flexvpn_cisco/expected/r1.txt new file mode 100644 index 0000000..2dbd927 --- /dev/null +++ b/tests/render/cases/blueprints/flexvpn_cisco/expected/r1.txt @@ -0,0 +1,42 @@ +set interfaces tunnel tun1 encapsulation 'gre' +set interfaces tunnel tun1 ip adjust-mss '1336' +set interfaces tunnel tun1 mtu '1376' +set interfaces tunnel tun1 remote '10.1.1.6' +set interfaces tunnel tun1 source-address '198.51.100.1' +set vpn ipsec authentication psk vyos_cisco_l +set vpn ipsec authentication psk vyos_cisco_l ******** '********' +set vpn ipsec authentication psk vyos_cisco_l id cisco.hub.net +set vpn ipsec authentication psk vyos_cisco_l id vyos.net +set vpn ipsec esp-group e1 +set vpn ipsec esp-group e1 lifetime 3600 +set vpn ipsec esp-group e1 mode tunnel +set vpn ipsec esp-group e1 pfs disable +set vpn ipsec esp-group e1 proposal 1 +set vpn ipsec esp-group e1 proposal 1 encryption aes128 +set vpn ipsec esp-group e1 proposal 1 hash sha256 +set vpn ipsec ike-group i1 +set vpn ipsec ike-group i1 key-exchange ikev2 +set vpn ipsec ike-group i1 lifetime 28800 +set vpn ipsec ike-group i1 proposal 1 +set vpn ipsec ike-group i1 proposal 1 dh-group 5 +set vpn ipsec ike-group i1 proposal 1 encryption aes256 +set vpn ipsec ike-group i1 proposal 1 hash sha256 +set vpn ipsec interface eth2 +set vpn ipsec options disable-route-autoinstall +set vpn ipsec options flexvpn +set vpn ipsec options interface tun1 +set vpn ipsec options virtual-ip +set vpn ipsec site-to-site peer cisco_hub +set vpn ipsec site-to-site peer cisco_hub authentication local-id 'vyos.net' +set vpn ipsec site-to-site peer cisco_hub authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer cisco_hub authentication remote-id 'cisco.hub.net' +set vpn ipsec site-to-site peer cisco_hub connection-type 'initiate' +set vpn ipsec site-to-site peer cisco_hub default-esp-group 'e1' +set vpn ipsec site-to-site peer cisco_hub ike-group 'i1' +set vpn ipsec site-to-site peer cisco_hub local-address '198.51.100.1' +set vpn ipsec site-to-site peer cisco_hub remote-address '10.1.1.6' +set vpn ipsec site-to-site peer cisco_hub tunnel 1 +set vpn ipsec site-to-site peer cisco_hub tunnel 1 local prefix '198.51.100.1/32' +set vpn ipsec site-to-site peer cisco_hub tunnel 1 protocol 'gre' +set vpn ipsec site-to-site peer cisco_hub tunnel 1 remote prefix '10.1.1.6/32' +set vpn ipsec site-to-site peer cisco_hub virtual-address '0.0.0.0' diff --git a/tests/render/cases/blueprints/flexvpn_cisco/roles b/tests/render/cases/blueprints/flexvpn_cisco/roles new file mode 100644 index 0000000..2a14e05 --- /dev/null +++ b/tests/render/cases/blueprints/flexvpn_cisco/roles @@ -0,0 +1 @@ +gre_tunnel,ipsec_policy_based diff --git a/tests/render/cases/blueprints/flexvpn_cisco/vars.yml b/tests/render/cases/blueprints/flexvpn_cisco/vars.yml new file mode 100644 index 0000000..ed7c51b --- /dev/null +++ b/tests/render/cases/blueprints/flexvpn_cisco/vars.yml @@ -0,0 +1,46 @@ +--- +# docs.vyos.io/en/1.5/configexamples/site-2-site-cisco.html - VyOS side +# The page's PSK is the word "secret". Ansible masks every occurrence of a +# no_log value, so the golden file shows the PSK line as "******** ********". +gre_tunnel_interfaces: + - name: tun1 + encapsulation: gre + adjust_mss: "1336" + mtu: 1376 + remote: 10.1.1.6 + source_address: 198.51.100.1 + +ipsec_policy_based_peers: + - name: cisco_hub + psk_name: vyos_cisco_l + psk: secret + local_address: 198.51.100.1 + remote_address: 10.1.1.6 + local_id: vyos.net + remote_id: cisco.hub.net + connection_type: initiate + virtual_address: 0.0.0.0 + tunnels: + - {id: 1, local_prefix: 198.51.100.1/32, remote_prefix: 10.1.1.6/32, protocol: gre} +ipsec_policy_based_ike_group: + name: i1 + key_exchange: ikev2 + lifetime: 28800 + proposal_id: 1 + dh_group: 5 + encryption: aes256 + hash: sha256 +ipsec_policy_based_esp_group: + name: e1 + lifetime: 3600 + mode: tunnel + pfs: disable + proposal_id: 1 + encryption: aes128 + hash: sha256 +ipsec_policy_based_interfaces: [eth2] +ipsec_policy_based_options: + disable_route_autoinstall: true + flexvpn: true + interface: tun1 + virtual_ip: true diff --git a/tests/render/cases/blueprints/policy_ipsec_firewall/expected/LEFT.txt b/tests/render/cases/blueprints/policy_ipsec_firewall/expected/LEFT.txt new file mode 100644 index 0000000..0f05a70 --- /dev/null +++ b/tests/render/cases/blueprints/policy_ipsec_firewall/expected/LEFT.txt @@ -0,0 +1,100 @@ +set firewall group network-group LOCAL-NETS +set firewall group network-group LOCAL-NETS network 10.1.11.0/24 +set firewall group network-group LOCAL-NETS network 10.1.12.0/24 +set firewall group network-group REMOTE-NETS +set firewall group network-group REMOTE-NETS network 10.2.21.0/24 +set firewall group network-group REMOTE-NETS network 10.2.22.0/24 +set firewall group network-group TRUSTED +set firewall group network-group TRUSTED network 10.1.11.0/24 +set firewall group network-group TRUSTED network 192.168.70.0/24 +set firewall group network-group TRUSTED network 198.51.100.125/32 +set firewall group network-group TRUSTED network 203.0.113.0/24 +set firewall ipv4 forward filter default-action 'drop' +set firewall ipv4 forward filter rule 1 +set firewall ipv4 forward filter rule 1 action 'accept' +set firewall ipv4 forward filter rule 1 state established +set firewall ipv4 forward filter rule 1 state related +set firewall ipv4 forward filter rule 10 +set firewall ipv4 forward filter rule 10 action 'accept' +set firewall ipv4 forward filter rule 10 source group network-group LOCAL-NETS +set firewall ipv4 forward filter rule 2 +set firewall ipv4 forward filter rule 2 action 'drop' +set firewall ipv4 forward filter rule 2 state invalid +set firewall ipv4 forward filter rule 20 +set firewall ipv4 forward filter rule 20 action 'accept' +set firewall ipv4 forward filter rule 20 destination group network-group LOCAL-NETS +set firewall ipv4 forward filter rule 20 source group network-group REMOTE-NETS +set firewall ipv4 input filter default-action 'drop' +set firewall ipv4 input filter rule 1 +set firewall ipv4 input filter rule 1 action 'accept' +set firewall ipv4 input filter rule 1 state established +set firewall ipv4 input filter rule 1 state related +set firewall ipv4 input filter rule 10 +set firewall ipv4 input filter rule 10 action 'accept' +set firewall ipv4 input filter rule 10 destination port 500,4500 +set firewall ipv4 input filter rule 10 inbound-interface name eth0 +set firewall ipv4 input filter rule 10 protocol 'udp' +set firewall ipv4 input filter rule 15 +set firewall ipv4 input filter rule 15 action 'accept' +set firewall ipv4 input filter rule 15 inbound-interface name eth0 +set firewall ipv4 input filter rule 15 protocol 'esp' +set firewall ipv4 input filter rule 2 +set firewall ipv4 input filter rule 2 action 'drop' +set firewall ipv4 input filter rule 2 state invalid +set firewall ipv4 input filter rule 20 +set firewall ipv4 input filter rule 20 action 'accept' +set firewall ipv4 input filter rule 20 destination port 22 +set firewall ipv4 input filter rule 20 protocol 'tcp' +set firewall ipv4 input filter rule 20 source group network-group TRUSTED +set firewall ipv4 input filter rule 25 +set firewall ipv4 input filter rule 25 action 'accept' +set firewall ipv4 input filter rule 25 destination port 53 +set firewall ipv4 input filter rule 25 protocol 'udp' +set firewall ipv4 input filter rule 25 source group network-group LOCAL-NETS +set firewall ipv4 input filter rule 30 +set firewall ipv4 input filter rule 30 action 'accept' +set firewall ipv4 input filter rule 30 protocol 'icmp' +set nat source rule 10 destination group network-group REMOTE-NETS +set nat source rule 10 exclude +set nat source rule 10 outbound-interface name eth0 +set nat source rule 10 source group network-group LOCAL-NETS +set nat source rule 20 outbound-interface name eth0 +set nat source rule 20 source group network-group LOCAL-NETS +set nat source rule 20 translation address masquerade +set protocols static route 0.0.0.0/0 +set protocols static route 0.0.0.0/0 next-hop '198.51.100.13' +set vpn ipsec authentication psk RIGHT +set vpn ipsec authentication psk RIGHT id 192.0.2.130 +set vpn ipsec authentication psk RIGHT id 198.51.100.14 +set vpn ipsec authentication psk RIGHT secret '********' +set vpn ipsec esp-group ESP-GROUP +set vpn ipsec esp-group ESP-GROUP mode tunnel +set vpn ipsec esp-group ESP-GROUP proposal 1 +set vpn ipsec esp-group ESP-GROUP proposal 1 encryption aes256 +set vpn ipsec esp-group ESP-GROUP proposal 1 hash sha256 +set vpn ipsec ike-group IKE-GROUP +set vpn ipsec ike-group IKE-GROUP key-exchange ikev2 +set vpn ipsec ike-group IKE-GROUP proposal 1 +set vpn ipsec ike-group IKE-GROUP proposal 1 dh-group 14 +set vpn ipsec ike-group IKE-GROUP proposal 1 encryption aes256 +set vpn ipsec ike-group IKE-GROUP proposal 1 hash sha256 +set vpn ipsec interface eth0 +set vpn ipsec site-to-site peer RIGHT +set vpn ipsec site-to-site peer RIGHT authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer RIGHT connection-type 'initiate' +set vpn ipsec site-to-site peer RIGHT default-esp-group 'ESP-GROUP' +set vpn ipsec site-to-site peer RIGHT ike-group 'IKE-GROUP' +set vpn ipsec site-to-site peer RIGHT local-address '198.51.100.14' +set vpn ipsec site-to-site peer RIGHT remote-address '192.0.2.130' +set vpn ipsec site-to-site peer RIGHT tunnel 0 +set vpn ipsec site-to-site peer RIGHT tunnel 0 local prefix '10.1.11.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 0 remote prefix '10.2.21.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 1 +set vpn ipsec site-to-site peer RIGHT tunnel 1 local prefix '10.1.11.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 1 remote prefix '10.2.22.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 2 +set vpn ipsec site-to-site peer RIGHT tunnel 2 local prefix '10.1.12.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 2 remote prefix '10.2.21.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 3 +set vpn ipsec site-to-site peer RIGHT tunnel 3 local prefix '10.1.12.0/24' +set vpn ipsec site-to-site peer RIGHT tunnel 3 remote prefix '10.2.22.0/24' diff --git a/tests/render/cases/blueprints/policy_ipsec_firewall/expected/RIGHT.txt b/tests/render/cases/blueprints/policy_ipsec_firewall/expected/RIGHT.txt new file mode 100644 index 0000000..547e447 --- /dev/null +++ b/tests/render/cases/blueprints/policy_ipsec_firewall/expected/RIGHT.txt @@ -0,0 +1,35 @@ +set vpn ipsec authentication psk LEFT +set vpn ipsec authentication psk LEFT id 192.0.2.130 +set vpn ipsec authentication psk LEFT id 198.51.100.14 +set vpn ipsec authentication psk LEFT secret '********' +set vpn ipsec esp-group ESP-GROUP +set vpn ipsec esp-group ESP-GROUP mode tunnel +set vpn ipsec esp-group ESP-GROUP proposal 1 +set vpn ipsec esp-group ESP-GROUP proposal 1 encryption aes256 +set vpn ipsec esp-group ESP-GROUP proposal 1 hash sha256 +set vpn ipsec ike-group IKE-GROUP +set vpn ipsec ike-group IKE-GROUP key-exchange ikev2 +set vpn ipsec ike-group IKE-GROUP proposal 1 +set vpn ipsec ike-group IKE-GROUP proposal 1 dh-group 14 +set vpn ipsec ike-group IKE-GROUP proposal 1 encryption aes256 +set vpn ipsec ike-group IKE-GROUP proposal 1 hash sha256 +set vpn ipsec interface eth0 +set vpn ipsec site-to-site peer LEFT +set vpn ipsec site-to-site peer LEFT authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer LEFT connection-type 'none' +set vpn ipsec site-to-site peer LEFT default-esp-group 'ESP-GROUP' +set vpn ipsec site-to-site peer LEFT ike-group 'IKE-GROUP' +set vpn ipsec site-to-site peer LEFT local-address '192.0.2.130' +set vpn ipsec site-to-site peer LEFT remote-address '198.51.100.14' +set vpn ipsec site-to-site peer LEFT tunnel 0 +set vpn ipsec site-to-site peer LEFT tunnel 0 local prefix '10.2.21.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 0 remote prefix '10.1.11.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 1 +set vpn ipsec site-to-site peer LEFT tunnel 1 local prefix '10.2.22.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 1 remote prefix '10.1.11.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 2 +set vpn ipsec site-to-site peer LEFT tunnel 2 local prefix '10.2.21.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 2 remote prefix '10.1.12.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 3 +set vpn ipsec site-to-site peer LEFT tunnel 3 local prefix '10.2.22.0/24' +set vpn ipsec site-to-site peer LEFT tunnel 3 remote prefix '10.1.12.0/24' diff --git a/tests/render/cases/blueprints/policy_ipsec_firewall/inventory.yml b/tests/render/cases/blueprints/policy_ipsec_firewall/inventory.yml new file mode 100644 index 0000000..42adead --- /dev/null +++ b/tests/render/cases/blueprints/policy_ipsec_firewall/inventory.yml @@ -0,0 +1,67 @@ +--- +all: + hosts: + LEFT: + ansible_host: 192.0.2.1 + ipsec_policy_based_peers: + - name: RIGHT + psk_name: RIGHT + psk: p4ssw0rd + authentication_ids: false + local_address: 198.51.100.14 + remote_address: 192.0.2.130 + connection_type: initiate + tunnels: + - {id: 0, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.21.0/24} + - {id: 1, local_prefix: 10.1.11.0/24, remote_prefix: 10.2.22.0/24} + - {id: 2, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.21.0/24} + - {id: 3, local_prefix: 10.1.12.0/24, remote_prefix: 10.2.22.0/24} + ipsec_policy_based_default_gateway: 198.51.100.13 + # Firewall and NAT exist only on LEFT, as on the page + firewall_groups: + network: + - {name: LOCAL-NETS, networks: [10.1.11.0/24, 10.1.12.0/24]} + - {name: REMOTE-NETS, networks: [10.2.21.0/24, 10.2.22.0/24]} + - {name: TRUSTED, networks: [198.51.100.125/32, 203.0.113.0/24, 10.1.11.0/24, 192.168.70.0/24]} + firewall_ipv4: + forward: + default_action: drop + rules: + - {number: 1, action: accept, state: {established: true, related: true}} + - {number: 2, action: drop, state: {invalid: true}} + - {number: 10, action: accept, source: {group: {network_group: LOCAL-NETS}}} + - {number: 20, action: accept, source: {group: {network_group: REMOTE-NETS}}, destination: {group: {network_group: LOCAL-NETS}}} + input: + default_action: drop + rules: + - {number: 1, action: accept, state: {established: true, related: true}} + - {number: 2, action: drop, state: {invalid: true}} + - {number: 10, action: accept, protocol: udp, destination: {port: "500,4500"}, inbound_interface: {name: eth0}} + - {number: 15, action: accept, protocol: esp, inbound_interface: {name: eth0}} + - {number: 20, action: accept, protocol: tcp, destination: {port: "22"}, source: {group: {network_group: TRUSTED}}} + - {number: 25, action: accept, protocol: udp, destination: {port: "53"}, source: {group: {network_group: LOCAL-NETS}}} + - {number: 30, action: accept, protocol: icmp} + nat_source_rules: + - {id: 10, exclude: true, outbound_interface: {name: eth0}, source: {network_group: LOCAL-NETS}, destination: {network_group: REMOTE-NETS}} + - {id: 20, outbound_interface: {name: eth0}, source: {network_group: LOCAL-NETS}, translation: {address: masquerade}} + RIGHT: + ansible_host: 192.0.2.2 + ipsec_policy_based_peers: + - name: LEFT + psk_name: LEFT + psk: p4ssw0rd + authentication_ids: false + local_address: 192.0.2.130 + remote_address: 198.51.100.14 + connection_type: none + tunnels: + - {id: 0, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.11.0/24} + - {id: 1, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.11.0/24} + - {id: 2, local_prefix: 10.2.21.0/24, remote_prefix: 10.1.12.0/24} + - {id: 3, local_prefix: 10.2.22.0/24, remote_prefix: 10.1.12.0/24} + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_connect_timeout: 5 + ansible_command_timeout: 5 diff --git a/tests/render/cases/blueprints/policy_ipsec_firewall/roles b/tests/render/cases/blueprints/policy_ipsec_firewall/roles new file mode 100644 index 0000000..21fdad9 --- /dev/null +++ b/tests/render/cases/blueprints/policy_ipsec_firewall/roles @@ -0,0 +1 @@ +ipsec_policy_based,firewall,nat diff --git a/tests/render/cases/blueprints/policy_ipsec_firewall/vars.yml b/tests/render/cases/blueprints/policy_ipsec_firewall/vars.yml new file mode 100644 index 0000000..9296601 --- /dev/null +++ b/tests/render/cases/blueprints/policy_ipsec_firewall/vars.yml @@ -0,0 +1,17 @@ +--- +# docs.vyos.io/en/1.5/configexamples/policy-based-ipsec-and-firewall.html +# Shared IPsec settings, as on the page (both routers) +ipsec_policy_based_ike_group: + name: IKE-GROUP + key_exchange: ikev2 + proposal_id: 1 + dh_group: 14 + encryption: aes256 + hash: sha256 +ipsec_policy_based_esp_group: + name: ESP-GROUP + mode: tunnel + proposal_id: 1 + encryption: aes256 + hash: sha256 +ipsec_policy_based_interfaces: [eth0] diff --git a/tests/render/cases/bridge_firewall/docs_three_bridges/expected/r1.txt b/tests/render/cases/bridge_firewall/docs_three_bridges/expected/r1.txt new file mode 100644 index 0000000..e9b871a --- /dev/null +++ b/tests/render/cases/bridge_firewall/docs_three_bridges/expected/r1.txt @@ -0,0 +1,159 @@ +set firewall bridge forward filter default-action 'drop' +set firewall bridge forward filter rule 10 action 'drop' +set firewall bridge forward filter rule 10 state 'invalid' +set firewall bridge forward filter rule 110 action 'jump' +set firewall bridge forward filter rule 110 description 'br0 traffic' +set firewall bridge forward filter rule 110 inbound-interface group 'br0-ifaces' +set firewall bridge forward filter rule 110 jump-target 'br0-fwd' +set firewall bridge forward filter rule 120 action 'jump' +set firewall bridge forward filter rule 120 description 'br1 traffic' +set firewall bridge forward filter rule 120 inbound-interface group 'br1-ifaces' +set firewall bridge forward filter rule 120 jump-target 'br1-fwd' +set firewall bridge forward filter rule 130 action 'jump' +set firewall bridge forward filter rule 130 description 'br2 traffic' +set firewall bridge forward filter rule 130 inbound-interface group 'br2-ifaces' +set firewall bridge forward filter rule 130 jump-target 'br2-fwd' +set firewall bridge forward filter rule 5 action 'accept' +set firewall bridge forward filter rule 5 state 'established' +set firewall bridge forward filter rule 5 state 'related' +set firewall bridge name br0-fwd default-action 'accept' +set firewall bridge name br0-pre default-action 'drop' +set firewall bridge name br0-pre rule 10 action 'accept' +set firewall bridge name br0-pre rule 10 description 'Accept IPv6 traffic' +set firewall bridge name br0-pre rule 10 ethernet-type 'ipv6' +set firewall bridge name br1-fwd default-action 'drop' +set firewall bridge name br1-fwd rule 10 action 'accept' +set firewall bridge name br1-fwd rule 10 description 'Accept ARP' +set firewall bridge name br1-fwd rule 10 ethernet-type 'arp' +set firewall bridge name br1-fwd rule 20 action 'accept' +set firewall bridge name br1-fwd rule 20 description 'Accept ipv4 from host' +set firewall bridge name br1-fwd rule 20 source address '10.1.1.102' +set firewall bridge name br1-fwd rule 20 state 'new' +set firewall bridge name br1-pre default-action 'accept' +set firewall bridge name br1-pre rule 10 action 'drop' +set firewall bridge name br1-pre rule 10 description 'Drop DHCP discover' +set firewall bridge name br1-pre rule 10 destination mac-address 'ff:ff:ff:ff:ff:ff' +set firewall bridge name br1-pre rule 10 destination port '67' +set firewall bridge name br1-pre rule 10 log +set firewall bridge name br1-pre rule 10 protocol 'udp' +set firewall bridge name br1-pre rule 10 source port '68' +set firewall bridge name br1-pre rule 20 action 'drop' +set firewall bridge name br1-pre rule 20 description 'Drop IPv6 traffic' +set firewall bridge name br1-pre rule 20 ethernet-type 'ipv6' +set firewall bridge name br2-fwd default-action 'drop' +set firewall bridge name br2-fwd rule 10 action 'accept' +set firewall bridge name br2-fwd rule 10 description 'Accept DHCP discover' +set firewall bridge name br2-fwd rule 10 destination mac-address 'ff:ff:ff:ff:ff:ff' +set firewall bridge name br2-fwd rule 10 destination port '67' +set firewall bridge name br2-fwd rule 10 protocol 'udp' +set firewall bridge name br2-fwd rule 10 source port '68' +set firewall bridge name br2-fwd rule 20 action 'accept' +set firewall bridge name br2-fwd rule 20 description 'Accept DHCP offers from trusted interface' +set firewall bridge name br2-fwd rule 20 destination port '68' +set firewall bridge name br2-fwd rule 20 inbound-interface name 'eth6' +set firewall bridge name br2-fwd rule 20 protocol 'udp' +set firewall bridge name br2-fwd rule 20 source port '67' +set firewall bridge name br2-fwd rule 22 action 'drop' +set firewall bridge name br2-fwd rule 22 description 'Drop all other DHCP offers' +set firewall bridge name br2-fwd rule 22 destination port '68' +set firewall bridge name br2-fwd rule 22 log +set firewall bridge name br2-fwd rule 22 protocol 'udp' +set firewall bridge name br2-fwd rule 22 source port '67' +set firewall bridge name br2-fwd rule 30 action 'accept' +set firewall bridge name br2-fwd rule 30 description 'Accept ARP' +set firewall bridge name br2-fwd rule 30 ethernet-type 'arp' +set firewall bridge name br2-fwd rule 40 action 'accept' +set firewall bridge name br2-fwd rule 40 description 'Accept ipv4' +set firewall bridge name br2-fwd rule 40 ethernet-type 'ipv4' +set firewall bridge name br2-pre default-action 'accept' +set firewall bridge name br2-pre rule 10 action 'drop' +set firewall bridge name br2-pre rule 10 description 'Drop IPv6 traffic' +set firewall bridge name br2-pre rule 10 ethernet-type 'ipv6' +set firewall bridge prerouting filter rule 10 action 'jump' +set firewall bridge prerouting filter rule 10 description 'br0 traffic' +set firewall bridge prerouting filter rule 10 inbound-interface group 'br0-ifaces' +set firewall bridge prerouting filter rule 10 jump-target 'br0-pre' +set firewall bridge prerouting filter rule 20 action 'jump' +set firewall bridge prerouting filter rule 20 description 'br1 traffic' +set firewall bridge prerouting filter rule 20 inbound-interface group 'br1-ifaces' +set firewall bridge prerouting filter rule 20 jump-target 'br1-pre' +set firewall bridge prerouting filter rule 30 action 'jump' +set firewall bridge prerouting filter rule 30 description 'br2 traffic' +set firewall bridge prerouting filter rule 30 inbound-interface group 'br2-ifaces' +set firewall bridge prerouting filter rule 30 jump-target 'br2-pre' +set firewall group interface-group br0-ifaces interface 'br0' +set firewall group interface-group br0-ifaces interface 'eth1' +set firewall group interface-group br0-ifaces interface 'eth2' +set firewall group interface-group br1-ifaces interface 'br1' +set firewall group interface-group br1-ifaces interface 'eth3' +set firewall group interface-group br1-ifaces interface 'eth4' +set firewall group interface-group br2-ifaces interface 'br2' +set firewall group interface-group br2-ifaces interface 'eth5' +set firewall group interface-group br2-ifaces interface 'eth6' +set firewall group interface-group br2-ifaces interface 'eth7' +set firewall ipv4 forward filter default-action 'drop' +set firewall ipv4 forward filter rule 10 +set firewall ipv4 forward filter rule 10 action 'drop' +set firewall ipv4 forward filter rule 10 state invalid +set firewall ipv4 forward filter rule 110 +set firewall ipv4 forward filter rule 110 action 'jump' +set firewall ipv4 forward filter rule 110 description 'br1 traffic' +set firewall ipv4 forward filter rule 110 inbound-interface group br1-ifaces +set firewall ipv4 forward filter rule 110 jump-target 'ip-br1-fwd' +set firewall ipv4 forward filter rule 120 +set firewall ipv4 forward filter rule 120 action 'jump' +set firewall ipv4 forward filter rule 120 description 'br2 traffic' +set firewall ipv4 forward filter rule 120 inbound-interface group br2-ifaces +set firewall ipv4 forward filter rule 120 jump-target 'ip-br2-fwd' +set firewall ipv4 forward filter rule 5 +set firewall ipv4 forward filter rule 5 action 'accept' +set firewall ipv4 forward filter rule 5 state established +set firewall ipv4 forward filter rule 5 state related +set firewall ipv4 input filter rule 10 +set firewall ipv4 input filter rule 10 action 'accept' +set firewall ipv4 input filter rule 10 state established +set firewall ipv4 input filter rule 10 state related +set firewall ipv4 input filter rule 110 +set firewall ipv4 input filter rule 110 action 'accept' +set firewall ipv4 input filter rule 110 description 'Accept access from br1' +set firewall ipv4 input filter rule 110 inbound-interface group br1-ifaces +set firewall ipv4 input filter rule 120 +set firewall ipv4 input filter rule 120 action 'drop' +set firewall ipv4 input filter rule 120 description 'Deny access from br2' +set firewall ipv4 input filter rule 120 inbound-interface group br2-ifaces +set firewall ipv4 input filter rule 20 +set firewall ipv4 input filter rule 20 action 'drop' +set firewall ipv4 input filter rule 20 state invalid +set firewall ipv4 name ip-br1-fwd default-action 'drop' +set firewall ipv4 name ip-br1-fwd rule 10 +set firewall ipv4 name ip-br1-fwd rule 10 action 'accept' +set firewall ipv4 name ip-br1-fwd rule 10 description 'br1 - allow internet access' +set firewall ipv4 name ip-br1-fwd rule 10 outbound-interface name eth0 +set firewall ipv4 name ip-br2-fwd default-action 'drop' +set firewall ipv4 name ip-br2-fwd rule 10 +set firewall ipv4 name ip-br2-fwd rule 10 action 'accept' +set firewall ipv4 name ip-br2-fwd rule 10 description 'br2 - allow internet access' +set firewall ipv4 name ip-br2-fwd rule 10 outbound-interface name eth0 +set firewall ipv4 name ip-br2-fwd rule 20 +set firewall ipv4 name ip-br2-fwd rule 20 action 'accept' +set firewall ipv4 name ip-br2-fwd rule 20 description 'br2 - allow access to br1' +set firewall ipv4 name ip-br2-fwd rule 20 outbound-interface group br1-ifaces +set interfaces bridge br0 description 'Isolated L2 bridge' +set interfaces bridge br0 member interface eth1 +set interfaces bridge br0 member interface eth2 +set interfaces bridge br1 address '10.1.1.1/24' +set interfaces bridge br1 description 'L3 bridge br1' +set interfaces bridge br1 member interface eth3 +set interfaces bridge br1 member interface eth4 +set interfaces bridge br2 address '10.2.2.1/24' +set interfaces bridge br2 description 'L3 bridge br2' +set interfaces bridge br2 member interface eth5 +set interfaces bridge br2 member interface eth6 +set interfaces bridge br2 member interface eth7 +set interfaces ethernet eth1 description 'br0' +set interfaces ethernet eth2 description 'br0' +set interfaces ethernet eth3 description 'br1' +set interfaces ethernet eth4 description 'br1' +set interfaces ethernet eth5 description 'br2 - Host' +set interfaces ethernet eth6 description 'br2 - Trusted DHCP Server' +set interfaces ethernet eth7 description 'br2' diff --git a/tests/render/cases/bridge_firewall/docs_three_bridges/vars.yml b/tests/render/cases/bridge_firewall/docs_three_bridges/vars.yml new file mode 100644 index 0000000..211891f --- /dev/null +++ b/tests/render/cases/bridge_firewall/docs_three_bridges/vars.yml @@ -0,0 +1,81 @@ +--- +# docs.vyos.io/en/1.5/configexamples/fwall-and-bridge.html +bridge_firewall_bridges: + - name: br0 + description: Isolated L2 bridge + members: + - {interface: eth1, description: br0} + - {interface: eth2, description: br0} + prerouting: + default_action: drop + rules: + - {number: 10, description: Accept IPv6 traffic, ethernet_type: ipv6} + forward: + default_action: accept + + - name: br1 + description: L3 bridge br1 + addresses: [10.1.1.1/24] + members: + - {interface: eth3, description: br1} + - {interface: eth4, description: br1} + prerouting: + default_action: accept + rules: + - number: 10 + description: Drop DHCP discover + action: drop + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + log: true + - {number: 20, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - {number: 10, description: Accept ARP, ethernet_type: arp} + - {number: 20, description: Accept ipv4 from host, source: {address: 10.1.1.102}, state: [new]} + ip_forward: + rules: + - {number: 10, description: br1 - allow internet access, outbound_interface: eth0} + router_access: accept + + - name: br2 + description: L3 bridge br2 + addresses: [10.2.2.1/24] + members: + - {interface: eth5, description: br2 - Host} + - {interface: eth6, description: br2 - Trusted DHCP Server} + - {interface: eth7, description: br2} + prerouting: + default_action: accept + rules: + - {number: 10, description: Drop IPv6 traffic, action: drop, ethernet_type: ipv6} + forward: + default_action: drop + rules: + - number: 10 + description: Accept DHCP discover + protocol: udp + source: {port: "68"} + destination: {port: "67", mac_address: "ff:ff:ff:ff:ff:ff"} + - number: 20 + description: Accept DHCP offers from trusted interface + protocol: udp + source: {port: "67"} + destination: {port: "68"} + inbound_interface: eth6 + - number: 22 + description: Drop all other DHCP offers + action: drop + protocol: udp + source: {port: "67"} + destination: {port: "68"} + log: true + - {number: 30, description: Accept ARP, ethernet_type: arp} + - {number: 40, description: Accept ipv4, ethernet_type: ipv4} + ip_forward: + rules: + - {number: 10, description: br2 - allow internet access, outbound_interface: eth0} + - {number: 20, description: br2 - allow access to br1, outbound_bridge: br1} + router_access: drop diff --git a/tests/render/cases/ipsec_route_based/docs_azure/expected/r1.txt b/tests/render/cases/ipsec_route_based/docs_azure/expected/r1.txt new file mode 100644 index 0000000..6c4fee4 --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_azure/expected/r1.txt @@ -0,0 +1,46 @@ +set interfaces vti vti1 address '10.10.1.5/32' +set interfaces vti vti1 description 'Azure Tunnel' +set interfaces vti vti1 ip adjust-mss '1350' +set protocols bgp neighbor 10.0.0.4 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.0.0.4 disable-connected-check +set protocols bgp neighbor 10.0.0.4 remote-as 65540 +set protocols bgp neighbor 10.0.0.4 timers holdtime 30 +set protocols bgp neighbor 10.0.0.4 timers keepalive 10 +set protocols bgp system-as 64499 +set protocols static route 10.0.0.4/32 +set protocols static route 10.0.0.4/32 interface 'vti1' +set vpn ipsec authentication psk azure +set vpn ipsec authentication psk azure id 198.51.100.3 +set vpn ipsec authentication psk azure id 203.0.113.2 +set vpn ipsec authentication psk azure secret '********' +set vpn ipsec esp-group AZURE +set vpn ipsec esp-group AZURE lifetime 3600 +set vpn ipsec esp-group AZURE mode tunnel +set vpn ipsec esp-group AZURE pfs dh-group2 +set vpn ipsec esp-group AZURE proposal 1 +set vpn ipsec esp-group AZURE proposal 1 encryption aes256 +set vpn ipsec esp-group AZURE proposal 1 hash sha1 +set vpn ipsec ike-group AZURE +set vpn ipsec ike-group AZURE dead-peer-detection action restart +set vpn ipsec ike-group AZURE dead-peer-detection interval 15 +set vpn ipsec ike-group AZURE dead-peer-detection timeout 30 +set vpn ipsec ike-group AZURE ikev2-reauth +set vpn ipsec ike-group AZURE key-exchange ikev2 +set vpn ipsec ike-group AZURE lifetime 28800 +set vpn ipsec ike-group AZURE proposal 1 +set vpn ipsec ike-group AZURE proposal 1 dh-group 2 +set vpn ipsec ike-group AZURE proposal 1 encryption aes256 +set vpn ipsec ike-group AZURE proposal 1 hash sha1 +set vpn ipsec interface eth0 +set vpn ipsec site-to-site peer 203.0.113.2 +set vpn ipsec site-to-site peer 203.0.113.2 authentication local-id '198.51.100.3' +set vpn ipsec site-to-site peer 203.0.113.2 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer 203.0.113.2 authentication remote-id '203.0.113.2' +set vpn ipsec site-to-site peer 203.0.113.2 connection-type 'initiate' +set vpn ipsec site-to-site peer 203.0.113.2 description 'AZURE PRIMARY TUNNEL' +set vpn ipsec site-to-site peer 203.0.113.2 ike-group 'AZURE' +set vpn ipsec site-to-site peer 203.0.113.2 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer 203.0.113.2 local-address '10.10.0.5' +set vpn ipsec site-to-site peer 203.0.113.2 remote-address '203.0.113.2' +set vpn ipsec site-to-site peer 203.0.113.2 vti bind 'vti1' +set vpn ipsec site-to-site peer 203.0.113.2 vti esp-group 'AZURE' diff --git a/tests/render/cases/ipsec_route_based/docs_azure/vars.yml b/tests/render/cases/ipsec_route_based/docs_azure/vars.yml new file mode 100644 index 0000000..0b52694 --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_azure/vars.yml @@ -0,0 +1,45 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-bgp.html +ipsec_route_based_peers: + - name: 203.0.113.2 + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk + local_address: 10.10.0.5 # private IP; the device is behind NAT + local_id: 198.51.100.3 # public IP + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, description: Azure Tunnel, adjust_mss: "1350"} +ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} +ipsec_route_based_esp_group: + name: AZURE + lifetime: 3600 + mode: tunnel + pfs: dh-group2 + proposal_id: 1 + encryption: aes256 + hash: sha1 +ipsec_route_based_interfaces: [eth0] +ipsec_route_based_disable_route_autoinstall: false # the page does not set it +ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} +ipsec_route_based_bgp: + asn: 64499 + neighbors: + - address: 10.0.0.4 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true diff --git a/tests/render/cases/ipsec_route_based/docs_azure_dual/expected/r1.txt b/tests/render/cases/ipsec_route_based/docs_azure_dual/expected/r1.txt new file mode 100644 index 0000000..d360ddf --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_azure_dual/expected/r1.txt @@ -0,0 +1,69 @@ +set interfaces vti vti1 address '10.10.1.5/32' +set interfaces vti vti1 description 'Azure Primary Tunnel' +set interfaces vti vti1 ip adjust-mss '1350' +set interfaces vti vti2 address '10.10.1.6/32' +set interfaces vti vti2 description 'Azure Secondary Tunnel' +set interfaces vti vti2 ip adjust-mss '1350' +set protocols bgp neighbor 10.0.0.4 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.0.0.4 disable-connected-check +set protocols bgp neighbor 10.0.0.4 remote-as 65540 +set protocols bgp neighbor 10.0.0.4 timers holdtime 30 +set protocols bgp neighbor 10.0.0.4 timers keepalive 10 +set protocols bgp neighbor 10.0.0.5 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.0.0.5 disable-connected-check +set protocols bgp neighbor 10.0.0.5 remote-as 65540 +set protocols bgp neighbor 10.0.0.5 timers holdtime 30 +set protocols bgp neighbor 10.0.0.5 timers keepalive 10 +set protocols bgp system-as 64499 +set protocols static route 10.0.0.4/32 +set protocols static route 10.0.0.4/32 interface 'vti1' +set protocols static route 10.0.0.5/32 +set protocols static route 10.0.0.5/32 interface 'vti2' +set vpn ipsec authentication psk azure +set vpn ipsec authentication psk azure id 198.51.100.3 +set vpn ipsec authentication psk azure id 203.0.113.2 +set vpn ipsec authentication psk azure id 203.0.113.3 +set vpn ipsec authentication psk azure secret '********' +set vpn ipsec esp-group AZURE +set vpn ipsec esp-group AZURE lifetime 3600 +set vpn ipsec esp-group AZURE mode tunnel +set vpn ipsec esp-group AZURE pfs dh-group2 +set vpn ipsec esp-group AZURE proposal 1 +set vpn ipsec esp-group AZURE proposal 1 encryption aes256 +set vpn ipsec esp-group AZURE proposal 1 hash sha1 +set vpn ipsec ike-group AZURE +set vpn ipsec ike-group AZURE dead-peer-detection action restart +set vpn ipsec ike-group AZURE dead-peer-detection interval 15 +set vpn ipsec ike-group AZURE dead-peer-detection timeout 30 +set vpn ipsec ike-group AZURE ikev2-reauth +set vpn ipsec ike-group AZURE key-exchange ikev2 +set vpn ipsec ike-group AZURE lifetime 28800 +set vpn ipsec ike-group AZURE proposal 1 +set vpn ipsec ike-group AZURE proposal 1 dh-group 2 +set vpn ipsec ike-group AZURE proposal 1 encryption aes256 +set vpn ipsec ike-group AZURE proposal 1 hash sha1 +set vpn ipsec interface eth0 +set vpn ipsec site-to-site peer azure-primary +set vpn ipsec site-to-site peer azure-primary authentication local-id '198.51.100.3' +set vpn ipsec site-to-site peer azure-primary authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer azure-primary authentication remote-id '203.0.113.2' +set vpn ipsec site-to-site peer azure-primary connection-type 'initiate' +set vpn ipsec site-to-site peer azure-primary description 'AZURE PRIMARY TUNNEL' +set vpn ipsec site-to-site peer azure-primary ike-group 'AZURE' +set vpn ipsec site-to-site peer azure-primary ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer azure-primary local-address '10.10.0.5' +set vpn ipsec site-to-site peer azure-primary remote-address '203.0.113.2' +set vpn ipsec site-to-site peer azure-primary vti bind 'vti1' +set vpn ipsec site-to-site peer azure-primary vti esp-group 'AZURE' +set vpn ipsec site-to-site peer azure-secondary +set vpn ipsec site-to-site peer azure-secondary authentication local-id '198.51.100.3' +set vpn ipsec site-to-site peer azure-secondary authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer azure-secondary authentication remote-id '203.0.113.3' +set vpn ipsec site-to-site peer azure-secondary connection-type 'initiate' +set vpn ipsec site-to-site peer azure-secondary description 'AZURE secondary TUNNEL' +set vpn ipsec site-to-site peer azure-secondary ike-group 'AZURE' +set vpn ipsec site-to-site peer azure-secondary ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer azure-secondary local-address '10.10.0.5' +set vpn ipsec site-to-site peer azure-secondary remote-address '203.0.113.3' +set vpn ipsec site-to-site peer azure-secondary vti bind 'vti2' +set vpn ipsec site-to-site peer azure-secondary vti esp-group 'AZURE' diff --git a/tests/render/cases/ipsec_route_based/docs_azure_dual/vars.yml b/tests/render/cases/ipsec_route_based/docs_azure_dual/vars.yml new file mode 100644 index 0000000..4054b68 --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_azure_dual/vars.yml @@ -0,0 +1,64 @@ +--- +# docs.vyos.io/en/1.5/configexamples/azure-vpn-dual-bgp.html +# Both peers share the PSK entry "azure" (one entry, three ids), as on the page. +ipsec_route_based_peers: + - name: azure-primary + description: AZURE PRIMARY TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk + local_address: 10.10.0.5 # private IP; the device is behind NAT + local_id: 198.51.100.3 # public IP + remote_address: 203.0.113.2 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti1, address: 10.10.1.5/32, description: Azure Primary Tunnel, adjust_mss: "1350"} + - name: azure-secondary + description: AZURE secondary TUNNEL + psk_name: azure + psk: ch00s3-4-s3cur3-psk + local_address: 10.10.0.5 + local_id: 198.51.100.3 + remote_address: 203.0.113.3 + connection_type: initiate + ikev2_reauth: inherit + esp_group_on_vti: true + vti: {interface: vti2, address: 10.10.1.6/32, description: Azure Secondary Tunnel, adjust_mss: "1350"} +ipsec_route_based_ike_group: + name: AZURE + key_exchange: ikev2 + ikev2_reauth: true + lifetime: 28800 + proposal_id: 1 + dh_group: 2 + encryption: aes256 + hash: sha1 + dead_peer_detection: {action: restart, interval: 15, timeout: 30} +ipsec_route_based_esp_group: + name: AZURE + lifetime: 3600 + mode: tunnel + pfs: dh-group2 + proposal_id: 1 + encryption: aes256 + hash: sha1 +ipsec_route_based_interfaces: [eth0] +ipsec_route_based_disable_route_autoinstall: false # the page does not set it +ipsec_route_based_interface_routes: + - {dest: 10.0.0.4/32, interface: vti1} + - {dest: 10.0.0.5/32, interface: vti2} +ipsec_route_based_bgp: + asn: 64499 + neighbors: + - address: 10.0.0.4 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true + - address: 10.0.0.5 + remote_as: 65540 + holdtime: 30 + keepalive: 10 + disable_connected_check: true + soft_reconfiguration_inbound: true diff --git a/tests/render/cases/ipsec_route_based/docs_cisco/expected/r1.txt b/tests/render/cases/ipsec_route_based/docs_cisco/expected/r1.txt new file mode 100644 index 0000000..aa551b3 --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_cisco/expected/r1.txt @@ -0,0 +1,45 @@ +set interfaces vti vti1 address '10.100.100.1/30' +set interfaces vti vti1 mtu '1438' +set protocols ospf area '0' +set protocols ospf area 0 network 10.100.100.0/30 +set protocols ospf area 0 network 192.168.0.0/24 +set protocols ospf area 0 network 192.168.1.0/24 +set protocols ospf interface eth1 passive +set protocols ospf interface eth2 passive +set protocols ospf interface vti1 network point-to-point +set protocols ospf parameters router-id '2.2.2.2' +set protocols static route 0.0.0.0/0 +set protocols static route 0.0.0.0/0 next-hop '10.0.1.1' +set vpn ipsec authentication psk AUTH-PSK +set vpn ipsec authentication psk AUTH-PSK id 10.0.1.2 +set vpn ipsec authentication psk AUTH-PSK id 10.0.2.2 +set vpn ipsec authentication psk AUTH-PSK secret '********' +set vpn ipsec authentication psk AUTH-PSK secret-type base64 +set vpn ipsec esp-group ESP-GROUP +set vpn ipsec esp-group ESP-GROUP lifetime 3600 +set vpn ipsec esp-group ESP-GROUP pfs disable +set vpn ipsec esp-group ESP-GROUP proposal 10 +set vpn ipsec esp-group ESP-GROUP proposal 10 encryption aes256 +set vpn ipsec esp-group ESP-GROUP proposal 10 hash sha256 +set vpn ipsec ike-group IKE-GROUP +set vpn ipsec ike-group IKE-GROUP close-action start +set vpn ipsec ike-group IKE-GROUP dead-peer-detection action restart +set vpn ipsec ike-group IKE-GROUP dead-peer-detection interval 10 +set vpn ipsec ike-group IKE-GROUP dead-peer-detection timeout 30 +set vpn ipsec ike-group IKE-GROUP key-exchange ikev1 +set vpn ipsec ike-group IKE-GROUP lifetime 28800 +set vpn ipsec ike-group IKE-GROUP proposal 10 +set vpn ipsec ike-group IKE-GROUP proposal 10 dh-group 14 +set vpn ipsec ike-group IKE-GROUP proposal 10 encryption aes128 +set vpn ipsec ike-group IKE-GROUP proposal 10 hash sha1 +set vpn ipsec options disable-route-autoinstall +set vpn ipsec site-to-site peer CISCO +set vpn ipsec site-to-site peer CISCO authentication local-id '10.0.1.2' +set vpn ipsec site-to-site peer CISCO authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer CISCO authentication remote-id '10.0.2.2' +set vpn ipsec site-to-site peer CISCO connection-type 'initiate' +set vpn ipsec site-to-site peer CISCO default-esp-group 'ESP-GROUP' +set vpn ipsec site-to-site peer CISCO ike-group 'IKE-GROUP' +set vpn ipsec site-to-site peer CISCO local-address '10.0.1.2' +set vpn ipsec site-to-site peer CISCO remote-address '10.0.2.2' +set vpn ipsec site-to-site peer CISCO vti bind 'vti1' diff --git a/tests/render/cases/ipsec_route_based/docs_cisco/vars.yml b/tests/render/cases/ipsec_route_based/docs_cisco/vars.yml new file mode 100644 index 0000000..a7f5c4d --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_cisco/vars.yml @@ -0,0 +1,33 @@ +--- +# docs.vyos.io/en/1.5/configexamples/ipsec-cisco-route-based.html - VyOS side. +# WAN/LAN addresses (eth0-eth2) belong to the base role. +ipsec_route_based_peers: + - name: CISCO + local_address: 10.0.1.2 + remote_address: 10.0.2.2 + psk: dGVzdA== + psk_type: base64 + psk_name: AUTH-PSK + connection_type: initiate + vti: {interface: vti1, address: 10.100.100.1/30, mtu: 1438} +ipsec_route_based_ike_group: + name: IKE-GROUP + key_exchange: ikev1 + lifetime: 28800 + dh_group: 14 + encryption: aes128 + hash: sha1 + close_action: start + dead_peer_detection: {action: restart, interval: 10, timeout: 30} +ipsec_route_based_esp_group: + name: ESP-GROUP + lifetime: 3600 + pfs: disable + encryption: aes256 + hash: sha256 +ipsec_route_based_ospf: + router_id: 2.2.2.2 + area: "0" + networks: [10.100.100.0/30, 192.168.0.0/24, 192.168.1.0/24] + passive_interfaces: [eth1, eth2] +ipsec_route_based_default_gateway: 10.0.1.1 diff --git a/tests/render/cases/ipsec_route_based/docs_palo_alto/expected/r1.txt b/tests/render/cases/ipsec_route_based/docs_palo_alto/expected/r1.txt new file mode 100644 index 0000000..4c50bcd --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_palo_alto/expected/r1.txt @@ -0,0 +1,45 @@ +set interfaces vti vti1 address '10.100.100.1/30' +set interfaces vti vti1 mtu '1438' +set protocols ospf area '0' +set protocols ospf area 0 network 10.100.100.0/30 +set protocols ospf area 0 network 192.168.0.0/24 +set protocols ospf area 0 network 192.168.1.0/24 +set protocols ospf interface eth1 passive +set protocols ospf interface eth2 passive +set protocols ospf interface vti1 network point-to-point +set protocols ospf parameters router-id '2.2.2.2' +set protocols static route 0.0.0.0/0 +set protocols static route 0.0.0.0/0 next-hop '10.0.1.1' +set vpn ipsec authentication psk AUTH-PSK +set vpn ipsec authentication psk AUTH-PSK id 10.0.1.2 +set vpn ipsec authentication psk AUTH-PSK id 10.0.2.2 +set vpn ipsec authentication psk AUTH-PSK secret '********' +set vpn ipsec authentication psk AUTH-PSK secret-type base64 +set vpn ipsec esp-group ESP-GROUP +set vpn ipsec esp-group ESP-GROUP lifetime 3600 +set vpn ipsec esp-group ESP-GROUP pfs disable +set vpn ipsec esp-group ESP-GROUP proposal 10 +set vpn ipsec esp-group ESP-GROUP proposal 10 encryption aes256 +set vpn ipsec esp-group ESP-GROUP proposal 10 hash sha256 +set vpn ipsec ike-group IKE-GROUP +set vpn ipsec ike-group IKE-GROUP close-action start +set vpn ipsec ike-group IKE-GROUP dead-peer-detection action restart +set vpn ipsec ike-group IKE-GROUP dead-peer-detection interval 10 +set vpn ipsec ike-group IKE-GROUP dead-peer-detection timeout 30 +set vpn ipsec ike-group IKE-GROUP key-exchange ikev1 +set vpn ipsec ike-group IKE-GROUP lifetime 28800 +set vpn ipsec ike-group IKE-GROUP proposal 10 +set vpn ipsec ike-group IKE-GROUP proposal 10 dh-group 14 +set vpn ipsec ike-group IKE-GROUP proposal 10 encryption aes128 +set vpn ipsec ike-group IKE-GROUP proposal 10 hash sha1 +set vpn ipsec options disable-route-autoinstall +set vpn ipsec site-to-site peer PA +set vpn ipsec site-to-site peer PA authentication local-id '10.0.1.2' +set vpn ipsec site-to-site peer PA authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer PA authentication remote-id '10.0.2.2' +set vpn ipsec site-to-site peer PA connection-type 'initiate' +set vpn ipsec site-to-site peer PA default-esp-group 'ESP-GROUP' +set vpn ipsec site-to-site peer PA ike-group 'IKE-GROUP' +set vpn ipsec site-to-site peer PA local-address '10.0.1.2' +set vpn ipsec site-to-site peer PA remote-address '10.0.2.2' +set vpn ipsec site-to-site peer PA vti bind 'vti1' diff --git a/tests/render/cases/ipsec_route_based/docs_palo_alto/vars.yml b/tests/render/cases/ipsec_route_based/docs_palo_alto/vars.yml new file mode 100644 index 0000000..9e8e814 --- /dev/null +++ b/tests/render/cases/ipsec_route_based/docs_palo_alto/vars.yml @@ -0,0 +1,33 @@ +--- +# docs.vyos.io/en/1.5/configexamples/ipsec-pa-route-based.html - VyOS side. +# WAN/LAN addresses (eth0-eth2) belong to the base role. +ipsec_route_based_peers: + - name: PA + local_address: 10.0.1.2 + remote_address: 10.0.2.2 + psk: dGVzdA== + psk_type: base64 + psk_name: AUTH-PSK + connection_type: initiate + vti: {interface: vti1, address: 10.100.100.1/30, mtu: 1438} +ipsec_route_based_ike_group: + name: IKE-GROUP + key_exchange: ikev1 + lifetime: 28800 + dh_group: 14 + encryption: aes128 + hash: sha1 + close_action: start + dead_peer_detection: {action: restart, interval: 10, timeout: 30} +ipsec_route_based_esp_group: + name: ESP-GROUP + lifetime: 3600 + pfs: disable + encryption: aes256 + hash: sha256 +ipsec_route_based_ospf: + router_id: 2.2.2.2 + area: "0" + networks: [10.100.100.0/30, 192.168.0.0/24, 192.168.1.0/24] + passive_interfaces: [eth1, eth2] +ipsec_route_based_default_gateway: 10.0.1.1 diff --git a/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerA.txt b/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerA.txt new file mode 100644 index 0000000..0bb9bb2 --- /dev/null +++ b/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerA.txt @@ -0,0 +1,12 @@ +set interfaces ethernet eth1 address '192.168.0.1/32' +set interfaces ethernet eth2 address '192.168.0.1/32' +set interfaces loopback lo address '192.168.0.1/32' +set protocols ospf area '0.0.0.0' +set protocols ospf area 0.0.0.0 authentication md5 +set protocols ospf area 0.0.0.0 network 192.168.0.1/32 +set protocols ospf interface eth1 authentication md5 key-id 1 md5-key '********' +set protocols ospf interface eth1 network point-to-point +set protocols ospf interface eth2 authentication md5 key-id 1 md5-key '********' +set protocols ospf interface eth2 network point-to-point +set protocols ospf parameters router-id '192.168.0.1' +set protocols ospf redistribute connected diff --git a/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerB.txt b/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerB.txt new file mode 100644 index 0000000..6569ef8 --- /dev/null +++ b/tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerB.txt @@ -0,0 +1,12 @@ +set interfaces ethernet eth1 address '192.168.0.2/32' +set interfaces ethernet eth2 address '192.168.0.2/32' +set interfaces loopback lo address '192.168.0.2/32' +set protocols ospf area '0.0.0.0' +set protocols ospf area 0.0.0.0 authentication md5 +set protocols ospf area 0.0.0.0 network 192.168.0.2/32 +set protocols ospf interface eth1 authentication md5 key-id 1 md5-key '********' +set protocols ospf interface eth1 network point-to-point +set protocols ospf interface eth2 authentication md5 key-id 1 md5-key '********' +set protocols ospf interface eth2 network point-to-point +set protocols ospf parameters router-id '192.168.0.2' +set protocols ospf redistribute connected diff --git a/tests/render/cases/ospf_unnumbered/docs_two_routers/inventory.yml b/tests/render/cases/ospf_unnumbered/docs_two_routers/inventory.yml new file mode 100644 index 0000000..e1cab44 --- /dev/null +++ b/tests/render/cases/ospf_unnumbered/docs_two_routers/inventory.yml @@ -0,0 +1,17 @@ +--- +all: + children: + fabric: + hosts: + routerA: + ansible_host: 192.0.2.1 + ospf_unnumbered_router_id: 192.168.0.1 + routerB: + ansible_host: 192.0.2.2 + ospf_unnumbered_router_id: 192.168.0.2 + vars: + ansible_network_os: vyos.vyos.vyos + ansible_connection: ansible.netcommon.network_cli + ansible_user: vyos + ansible_connect_timeout: 5 + ansible_command_timeout: 5 diff --git a/tests/render/cases/ospf_unnumbered/docs_two_routers/vars.yml b/tests/render/cases/ospf_unnumbered/docs_two_routers/vars.yml new file mode 100644 index 0000000..c1303ba --- /dev/null +++ b/tests/render/cases/ospf_unnumbered/docs_two_routers/vars.yml @@ -0,0 +1,4 @@ +--- +# docs.vyos.io/en/1.5/configexamples/ospf-unnumbered.html - shared settings +ospf_unnumbered_interfaces: [eth1, eth2] +ospf_unnumbered_md5_key: yourpassword diff --git a/tests/render/cases/vrf_firewall/docs_four_vrfs/expected/r1.txt b/tests/render/cases/vrf_firewall/docs_four_vrfs/expected/r1.txt new file mode 100644 index 0000000..62951c9 --- /dev/null +++ b/tests/render/cases/vrf_firewall/docs_four_vrfs/expected/r1.txt @@ -0,0 +1,53 @@ +set firewall global-options state-policy established action 'accept' +set firewall global-options state-policy invalid action 'drop' +set firewall global-options state-policy related action 'accept' +set firewall ipv4 forward filter default-action 'drop' +set firewall ipv4 forward filter default-log +set firewall ipv4 forward filter rule 10 +set firewall ipv4 forward filter rule 10 action 'accept' +set firewall ipv4 forward filter rule 10 description 'MGMT - Allow to LAN and PROD' +set firewall ipv4 forward filter rule 10 inbound-interface name MGMT +set firewall ipv4 forward filter rule 10 outbound-interface name eth2* +set firewall ipv4 forward filter rule 120 +set firewall ipv4 forward filter rule 120 action 'accept' +set firewall ipv4 forward filter rule 120 description 'LAN - Allow to PROD' +set firewall ipv4 forward filter rule 120 inbound-interface name LAN +set firewall ipv4 forward filter rule 120 outbound-interface name eth2.3500 +set firewall ipv4 forward filter rule 130 +set firewall ipv4 forward filter rule 130 action 'accept' +set firewall ipv4 forward filter rule 130 description 'LAN - Allow internet' +set firewall ipv4 forward filter rule 130 inbound-interface name LAN +set firewall ipv4 forward filter rule 130 outbound-interface name pppoe0 +set firewall ipv4 forward filter rule 99 +set firewall ipv4 forward filter rule 99 action 'drop' +set firewall ipv4 forward filter rule 99 description 'MGMT - Drop all going to mgmt' +set firewall ipv4 forward filter rule 99 outbound-interface name eth1 +set firewall ipv4 input filter default-action 'drop' +set firewall ipv4 input filter default-log +set firewall ipv4 input filter rule 10 +set firewall ipv4 input filter rule 10 action 'accept' +set firewall ipv4 input filter rule 10 description 'MGMT - Allow input' +set firewall ipv4 input filter rule 10 inbound-interface name MGMT +set interfaces ethernet eth1 vrf 'MGMT' +set interfaces ethernet eth2 vif 150 vrf 'LAN' +set interfaces ethernet eth2 vif 160 vrf 'LAN' +set interfaces ethernet eth2 vif 3500 vrf 'PROD' +set interfaces pppoe pppoe0 vrf 'WAN' +set vrf bind-to-all +set vrf name LAN protocols static route 0.0.0.0/0 interface pppoe0 vrf 'WAN' +set vrf name LAN protocols static route 10.100.100.0/24 interface eth1 vrf 'MGMT' +set vrf name LAN protocols static route 172.16.20.0/24 interface eth2.3500 vrf 'PROD' +set vrf name LAN table 103 +set vrf name MGMT protocols static route 10.150.150.0/24 interface eth2.150 vrf 'LAN' +set vrf name MGMT protocols static route 10.160.160.0/24 interface eth2.160 vrf 'LAN' +set vrf name MGMT protocols static route 172.16.20.0/24 interface eth2.3500 vrf 'PROD' +set vrf name MGMT table 102 +set vrf name PROD protocols static route 0.0.0.0/0 interface pppoe0 vrf 'WAN' +set vrf name PROD protocols static route 10.100.100.0/24 interface eth1 vrf 'MGMT' +set vrf name PROD protocols static route 10.150.150.0/24 interface eth2.150 vrf 'LAN' +set vrf name PROD protocols static route 10.160.160.0/24 interface eth2.160 vrf 'LAN' +set vrf name PROD table 104 +set vrf name WAN protocols static route 10.150.150.0/24 interface eth2.150 vrf 'LAN' +set vrf name WAN protocols static route 10.160.160.0/24 interface eth2.160 vrf 'LAN' +set vrf name WAN protocols static route 172.16.20.0/24 interface eth2.3500 vrf 'PROD' +set vrf name WAN table 101 diff --git a/tests/render/cases/vrf_firewall/docs_four_vrfs/vars.yml b/tests/render/cases/vrf_firewall/docs_four_vrfs/vars.yml new file mode 100644 index 0000000..38626df --- /dev/null +++ b/tests/render/cases/vrf_firewall/docs_four_vrfs/vars.yml @@ -0,0 +1,43 @@ +--- +# docs.vyos.io/en/1.5/configexamples/fwall-and-vrf.html +# Interface addresses and the PPPoE interface itself are outside this role +# (base role / a later PPPoE role); everything else is from the page. +vrf_firewall_vrfs: + - name: MGMT + table: 102 + interfaces: [eth1] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: WAN + table: 101 + interfaces: [pppoe0] + routes: + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: LAN + table: 103 + interfaces: [eth2.150, eth2.160] + routes: + - {dest: 0.0.0.0/0, interface: pppoe0, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 172.16.20.0/24, interface: eth2.3500, vrf: PROD} + - name: PROD + table: 104 + interfaces: [eth2.3500] + routes: + - {dest: 0.0.0.0/0, interface: pppoe0, vrf: WAN} + - {dest: 10.100.100.0/24, interface: eth1, vrf: MGMT} + - {dest: 10.150.150.0/24, interface: eth2.150, vrf: LAN} + - {dest: 10.160.160.0/24, interface: eth2.160, vrf: LAN} + +vrf_firewall_forward_rules: + - {number: 10, description: MGMT - Allow to LAN and PROD, inbound_interface: MGMT, outbound_interface: eth2*} + - {number: 99, action: drop, description: MGMT - Drop all going to mgmt, outbound_interface: eth1} + - {number: 120, description: LAN - Allow to PROD, inbound_interface: LAN, outbound_interface: eth2.3500} + - {number: 130, description: LAN - Allow internet, inbound_interface: LAN, outbound_interface: pppoe0} + +vrf_firewall_input_rules: + - {number: 10, description: MGMT - Allow input, inbound_interface: MGMT} diff --git a/tests/render/cases/zone_firewall/docs_zone_policy/expected/r1.txt b/tests/render/cases/zone_firewall/docs_zone_policy/expected/r1.txt new file mode 100644 index 0000000..c7c2232 --- /dev/null +++ b/tests/render/cases/zone_firewall/docs_zone_policy/expected/r1.txt @@ -0,0 +1,548 @@ +set firewall ipv4 name dmz-lan default-action 'drop' +set firewall ipv4 name dmz-lan default-log +set firewall ipv4 name dmz-lan rule 1 +set firewall ipv4 name dmz-lan rule 1 action 'accept' +set firewall ipv4 name dmz-lan rule 1 state established +set firewall ipv4 name dmz-lan rule 1 state related +set firewall ipv4 name dmz-lan rule 2 +set firewall ipv4 name dmz-lan rule 2 action 'drop' +set firewall ipv4 name dmz-lan rule 2 log +set firewall ipv4 name dmz-lan rule 2 state invalid +set firewall ipv4 name dmz-local default-action 'drop' +set firewall ipv4 name dmz-local default-log +set firewall ipv4 name dmz-local rule 1 +set firewall ipv4 name dmz-local rule 1 action 'accept' +set firewall ipv4 name dmz-local rule 1 state established +set firewall ipv4 name dmz-local rule 1 state related +set firewall ipv4 name dmz-local rule 2 +set firewall ipv4 name dmz-local rule 2 action 'drop' +set firewall ipv4 name dmz-local rule 2 log +set firewall ipv4 name dmz-local rule 2 state invalid +set firewall ipv4 name dmz-local rule 400 +set firewall ipv4 name dmz-local rule 400 action 'accept' +set firewall ipv4 name dmz-local rule 400 destination port 123 +set firewall ipv4 name dmz-local rule 400 log +set firewall ipv4 name dmz-local rule 400 protocol 'tcp' +set firewall ipv4 name dmz-local rule 600 +set firewall ipv4 name dmz-local rule 600 action 'accept' +set firewall ipv4 name dmz-local rule 600 destination port 53 +set firewall ipv4 name dmz-local rule 600 log +set firewall ipv4 name dmz-local rule 600 protocol 'tcp' +set firewall ipv4 name dmz-local rule 700 +set firewall ipv4 name dmz-local rule 700 action 'accept' +set firewall ipv4 name dmz-local rule 700 destination port 67,68 +set firewall ipv4 name dmz-local rule 700 log +set firewall ipv4 name dmz-local rule 700 protocol 'tcp' +set firewall ipv4 name dmz-wan default-action 'drop' +set firewall ipv4 name dmz-wan default-log +set firewall ipv4 name dmz-wan rule 1 +set firewall ipv4 name dmz-wan rule 1 action 'accept' +set firewall ipv4 name dmz-wan rule 1 state established +set firewall ipv4 name dmz-wan rule 1 state related +set firewall ipv4 name dmz-wan rule 2 +set firewall ipv4 name dmz-wan rule 2 action 'drop' +set firewall ipv4 name dmz-wan rule 2 log +set firewall ipv4 name dmz-wan rule 2 state invalid +set firewall ipv4 name dmz-wan rule 200 +set firewall ipv4 name dmz-wan rule 200 action 'accept' +set firewall ipv4 name dmz-wan rule 200 destination port 80,443 +set firewall ipv4 name dmz-wan rule 200 log +set firewall ipv4 name dmz-wan rule 200 protocol 'tcp' +set firewall ipv4 name dmz-wan rule 300 +set firewall ipv4 name dmz-wan rule 300 action 'accept' +set firewall ipv4 name dmz-wan rule 300 destination port 20,21 +set firewall ipv4 name dmz-wan rule 300 log +set firewall ipv4 name dmz-wan rule 300 protocol 'tcp' +set firewall ipv4 name dmz-wan rule 600 +set firewall ipv4 name dmz-wan rule 600 action 'accept' +set firewall ipv4 name dmz-wan rule 600 destination port 53 +set firewall ipv4 name dmz-wan rule 600 log +set firewall ipv4 name dmz-wan rule 600 protocol 'tcp_udp' +set firewall ipv4 name dmz-wan rule 800 +set firewall ipv4 name dmz-wan rule 800 action 'accept' +set firewall ipv4 name dmz-wan rule 800 destination port 22 +set firewall ipv4 name dmz-wan rule 800 log +set firewall ipv4 name dmz-wan rule 800 protocol 'tcp' +set firewall ipv4 name lan-dmz default-action 'drop' +set firewall ipv4 name lan-dmz default-log +set firewall ipv4 name lan-dmz rule 1 +set firewall ipv4 name lan-dmz rule 1 action 'accept' +set firewall ipv4 name lan-dmz rule 1 state established +set firewall ipv4 name lan-dmz rule 1 state related +set firewall ipv4 name lan-dmz rule 2 +set firewall ipv4 name lan-dmz rule 2 action 'drop' +set firewall ipv4 name lan-dmz rule 2 log +set firewall ipv4 name lan-dmz rule 2 state invalid +set firewall ipv4 name lan-dmz rule 200 +set firewall ipv4 name lan-dmz rule 200 action 'accept' +set firewall ipv4 name lan-dmz rule 200 destination port 80,443 +set firewall ipv4 name lan-dmz rule 200 log +set firewall ipv4 name lan-dmz rule 200 protocol 'tcp' +set firewall ipv4 name lan-dmz rule 800 +set firewall ipv4 name lan-dmz rule 800 action 'accept' +set firewall ipv4 name lan-dmz rule 800 destination address 192.168.200.200 +set firewall ipv4 name lan-dmz rule 800 destination port 22 +set firewall ipv4 name lan-dmz rule 800 log +set firewall ipv4 name lan-dmz rule 800 protocol 'tcp' +set firewall ipv4 name lan-dmz rule 800 source address 192.168.100.10 +set firewall ipv4 name lan-dmz rule 900 +set firewall ipv4 name lan-dmz rule 900 action 'accept' +set firewall ipv4 name lan-dmz rule 900 destination port 993 +set firewall ipv4 name lan-dmz rule 900 log +set firewall ipv4 name lan-dmz rule 900 protocol 'tcp' +set firewall ipv4 name lan-local default-action 'drop' +set firewall ipv4 name lan-local default-log +set firewall ipv4 name lan-local rule 1 +set firewall ipv4 name lan-local rule 1 action 'accept' +set firewall ipv4 name lan-local rule 1 state established +set firewall ipv4 name lan-local rule 1 state related +set firewall ipv4 name lan-local rule 2 +set firewall ipv4 name lan-local rule 2 action 'drop' +set firewall ipv4 name lan-local rule 2 log +set firewall ipv4 name lan-local rule 2 state invalid +set firewall ipv4 name lan-local rule 400 +set firewall ipv4 name lan-local rule 400 action 'accept' +set firewall ipv4 name lan-local rule 400 destination port 123 +set firewall ipv4 name lan-local rule 400 log +set firewall ipv4 name lan-local rule 400 protocol 'tcp' +set firewall ipv4 name lan-local rule 600 +set firewall ipv4 name lan-local rule 600 action 'accept' +set firewall ipv4 name lan-local rule 600 destination port 53 +set firewall ipv4 name lan-local rule 600 log +set firewall ipv4 name lan-local rule 600 protocol 'tcp' +set firewall ipv4 name lan-local rule 700 +set firewall ipv4 name lan-local rule 700 action 'accept' +set firewall ipv4 name lan-local rule 700 destination port 67,68 +set firewall ipv4 name lan-local rule 700 log +set firewall ipv4 name lan-local rule 700 protocol 'tcp' +set firewall ipv4 name lan-local rule 800 +set firewall ipv4 name lan-local rule 800 action 'accept' +set firewall ipv4 name lan-local rule 800 destination port 22 +set firewall ipv4 name lan-local rule 800 log +set firewall ipv4 name lan-local rule 800 protocol 'tcp' +set firewall ipv4 name lan-local rule 800 source address 192.168.100.10 +set firewall ipv4 name lan-wan default-action 'drop' +set firewall ipv4 name lan-wan default-log +set firewall ipv4 name lan-wan rule 1 +set firewall ipv4 name lan-wan rule 1 action 'accept' +set firewall ipv4 name lan-wan rule 1 state established +set firewall ipv4 name lan-wan rule 1 state related +set firewall ipv4 name lan-wan rule 2 +set firewall ipv4 name lan-wan rule 2 action 'drop' +set firewall ipv4 name lan-wan rule 2 log +set firewall ipv4 name lan-wan rule 2 state invalid +set firewall ipv4 name lan-wan rule 200 +set firewall ipv4 name lan-wan rule 200 action 'accept' +set firewall ipv4 name lan-wan rule 200 destination port 80,443 +set firewall ipv4 name lan-wan rule 200 log +set firewall ipv4 name lan-wan rule 200 protocol 'tcp' +set firewall ipv4 name lan-wan rule 300 +set firewall ipv4 name lan-wan rule 300 action 'accept' +set firewall ipv4 name lan-wan rule 300 destination port 20,21 +set firewall ipv4 name lan-wan rule 300 log +set firewall ipv4 name lan-wan rule 300 protocol 'tcp' +set firewall ipv4 name lan-wan rule 800 +set firewall ipv4 name lan-wan rule 800 action 'accept' +set firewall ipv4 name lan-wan rule 800 destination port 22 +set firewall ipv4 name lan-wan rule 800 log +set firewall ipv4 name lan-wan rule 800 protocol 'tcp' +set firewall ipv4 name local-dmz default-action 'drop' +set firewall ipv4 name local-dmz default-log +set firewall ipv4 name local-dmz rule 1 +set firewall ipv4 name local-dmz rule 1 action 'accept' +set firewall ipv4 name local-dmz rule 1 state established +set firewall ipv4 name local-dmz rule 1 state related +set firewall ipv4 name local-dmz rule 2 +set firewall ipv4 name local-dmz rule 2 action 'drop' +set firewall ipv4 name local-dmz rule 2 log +set firewall ipv4 name local-dmz rule 2 state invalid +set firewall ipv4 name local-dmz rule 500 +set firewall ipv4 name local-dmz rule 500 action 'accept' +set firewall ipv4 name local-dmz rule 500 destination port 25 +set firewall ipv4 name local-dmz rule 500 log +set firewall ipv4 name local-dmz rule 500 protocol 'tcp' +set firewall ipv4 name local-dmz rule 600 +set firewall ipv4 name local-dmz rule 600 action 'accept' +set firewall ipv4 name local-dmz rule 600 destination port 53 +set firewall ipv4 name local-dmz rule 600 log +set firewall ipv4 name local-dmz rule 600 protocol 'tcp_udp' +set firewall ipv4 name local-dmz rule 700 +set firewall ipv4 name local-dmz rule 700 action 'accept' +set firewall ipv4 name local-dmz rule 700 destination port 67,68 +set firewall ipv4 name local-dmz rule 700 log +set firewall ipv4 name local-dmz rule 700 protocol 'tcp' +set firewall ipv4 name local-lan default-action 'drop' +set firewall ipv4 name local-lan default-log +set firewall ipv4 name local-lan rule 1 +set firewall ipv4 name local-lan rule 1 action 'accept' +set firewall ipv4 name local-lan rule 1 state established +set firewall ipv4 name local-lan rule 1 state related +set firewall ipv4 name local-lan rule 2 +set firewall ipv4 name local-lan rule 2 action 'drop' +set firewall ipv4 name local-lan rule 2 log +set firewall ipv4 name local-lan rule 2 state invalid +set firewall ipv4 name local-lan rule 700 +set firewall ipv4 name local-lan rule 700 action 'accept' +set firewall ipv4 name local-lan rule 700 destination port 67,68 +set firewall ipv4 name local-lan rule 700 log +set firewall ipv4 name local-lan rule 700 protocol 'tcp' +set firewall ipv4 name local-wan default-action 'drop' +set firewall ipv4 name local-wan default-log +set firewall ipv4 name local-wan rule 1 +set firewall ipv4 name local-wan rule 1 action 'accept' +set firewall ipv4 name local-wan rule 1 state established +set firewall ipv4 name local-wan rule 1 state related +set firewall ipv4 name local-wan rule 2 +set firewall ipv4 name local-wan rule 2 action 'drop' +set firewall ipv4 name local-wan rule 2 log +set firewall ipv4 name local-wan rule 2 state invalid +set firewall ipv4 name local-wan rule 200 +set firewall ipv4 name local-wan rule 200 action 'accept' +set firewall ipv4 name local-wan rule 200 destination port 80,443 +set firewall ipv4 name local-wan rule 200 log +set firewall ipv4 name local-wan rule 200 protocol 'tcp' +set firewall ipv4 name local-wan rule 300 +set firewall ipv4 name local-wan rule 300 action 'accept' +set firewall ipv4 name local-wan rule 300 destination port 20,21 +set firewall ipv4 name local-wan rule 300 log +set firewall ipv4 name local-wan rule 300 protocol 'tcp' +set firewall ipv4 name wan-dmz default-action 'drop' +set firewall ipv4 name wan-dmz default-log +set firewall ipv4 name wan-dmz rule 1 +set firewall ipv4 name wan-dmz rule 1 action 'accept' +set firewall ipv4 name wan-dmz rule 1 state established +set firewall ipv4 name wan-dmz rule 1 state related +set firewall ipv4 name wan-dmz rule 2 +set firewall ipv4 name wan-dmz rule 2 action 'drop' +set firewall ipv4 name wan-dmz rule 2 log +set firewall ipv4 name wan-dmz rule 2 state invalid +set firewall ipv4 name wan-dmz rule 200 +set firewall ipv4 name wan-dmz rule 200 action 'accept' +set firewall ipv4 name wan-dmz rule 200 destination address 192.168.200.200 +set firewall ipv4 name wan-dmz rule 200 destination port 80,443 +set firewall ipv4 name wan-dmz rule 200 log +set firewall ipv4 name wan-dmz rule 200 protocol 'tcp' +set firewall ipv4 name wan-dmz rule 500 +set firewall ipv4 name wan-dmz rule 500 action 'accept' +set firewall ipv4 name wan-dmz rule 500 destination address 192.168.200.200 +set firewall ipv4 name wan-dmz rule 500 destination port 25 +set firewall ipv4 name wan-dmz rule 500 log +set firewall ipv4 name wan-dmz rule 500 protocol 'tcp' +set firewall ipv4 name wan-dmz rule 600 +set firewall ipv4 name wan-dmz rule 600 action 'accept' +set firewall ipv4 name wan-dmz rule 600 destination address 192.168.200.200 +set firewall ipv4 name wan-dmz rule 600 destination port 53 +set firewall ipv4 name wan-dmz rule 600 log +set firewall ipv4 name wan-dmz rule 600 protocol 'tcp' +set firewall ipv4 name wan-lan default-action 'drop' +set firewall ipv4 name wan-lan default-log +set firewall ipv4 name wan-lan rule 1 +set firewall ipv4 name wan-lan rule 1 action 'accept' +set firewall ipv4 name wan-lan rule 1 state established +set firewall ipv4 name wan-lan rule 1 state related +set firewall ipv4 name wan-lan rule 2 +set firewall ipv4 name wan-lan rule 2 action 'drop' +set firewall ipv4 name wan-lan rule 2 log +set firewall ipv4 name wan-lan rule 2 state invalid +set firewall ipv4 name wan-local default-action 'drop' +set firewall ipv4 name wan-local default-log +set firewall ipv4 name wan-local rule 1 +set firewall ipv4 name wan-local rule 1 action 'accept' +set firewall ipv4 name wan-local rule 1 state established +set firewall ipv4 name wan-local rule 1 state related +set firewall ipv4 name wan-local rule 2 +set firewall ipv4 name wan-local rule 2 action 'drop' +set firewall ipv4 name wan-local rule 2 log +set firewall ipv4 name wan-local rule 2 state invalid +set firewall ipv6 name dmz-lan-6 default-action 'drop' +set firewall ipv6 name dmz-lan-6 default-log +set firewall ipv6 name dmz-lan-6 rule 1 +set firewall ipv6 name dmz-lan-6 rule 1 action 'accept' +set firewall ipv6 name dmz-lan-6 rule 1 state established +set firewall ipv6 name dmz-lan-6 rule 1 state related +set firewall ipv6 name dmz-lan-6 rule 2 +set firewall ipv6 name dmz-lan-6 rule 2 action 'drop' +set firewall ipv6 name dmz-lan-6 rule 2 log +set firewall ipv6 name dmz-lan-6 rule 2 state invalid +set firewall ipv6 name dmz-local-6 default-action 'drop' +set firewall ipv6 name dmz-local-6 default-log +set firewall ipv6 name dmz-local-6 rule 1 +set firewall ipv6 name dmz-local-6 rule 1 action 'accept' +set firewall ipv6 name dmz-local-6 rule 1 state established +set firewall ipv6 name dmz-local-6 rule 1 state related +set firewall ipv6 name dmz-local-6 rule 2 +set firewall ipv6 name dmz-local-6 rule 2 action 'drop' +set firewall ipv6 name dmz-local-6 rule 2 log +set firewall ipv6 name dmz-local-6 rule 2 state invalid +set firewall ipv6 name dmz-local-6 rule 400 +set firewall ipv6 name dmz-local-6 rule 400 action 'accept' +set firewall ipv6 name dmz-local-6 rule 400 destination port 123 +set firewall ipv6 name dmz-local-6 rule 400 log +set firewall ipv6 name dmz-local-6 rule 400 protocol 'tcp' +set firewall ipv6 name dmz-local-6 rule 600 +set firewall ipv6 name dmz-local-6 rule 600 action 'accept' +set firewall ipv6 name dmz-local-6 rule 600 destination port 53 +set firewall ipv6 name dmz-local-6 rule 600 log +set firewall ipv6 name dmz-local-6 rule 600 protocol 'tcp' +set firewall ipv6 name dmz-local-6 rule 700 +set firewall ipv6 name dmz-local-6 rule 700 action 'accept' +set firewall ipv6 name dmz-local-6 rule 700 destination port 67,68 +set firewall ipv6 name dmz-local-6 rule 700 log +set firewall ipv6 name dmz-local-6 rule 700 protocol 'tcp' +set firewall ipv6 name dmz-wan-6 default-action 'drop' +set firewall ipv6 name dmz-wan-6 default-log +set firewall ipv6 name dmz-wan-6 rule 1 +set firewall ipv6 name dmz-wan-6 rule 1 action 'accept' +set firewall ipv6 name dmz-wan-6 rule 1 state established +set firewall ipv6 name dmz-wan-6 rule 1 state related +set firewall ipv6 name dmz-wan-6 rule 2 +set firewall ipv6 name dmz-wan-6 rule 2 action 'drop' +set firewall ipv6 name dmz-wan-6 rule 2 log +set firewall ipv6 name dmz-wan-6 rule 2 state invalid +set firewall ipv6 name dmz-wan-6 rule 200 +set firewall ipv6 name dmz-wan-6 rule 200 action 'accept' +set firewall ipv6 name dmz-wan-6 rule 200 destination port 80,443 +set firewall ipv6 name dmz-wan-6 rule 200 log +set firewall ipv6 name dmz-wan-6 rule 200 protocol 'tcp' +set firewall ipv6 name dmz-wan-6 rule 300 +set firewall ipv6 name dmz-wan-6 rule 300 action 'accept' +set firewall ipv6 name dmz-wan-6 rule 300 destination port 20,21 +set firewall ipv6 name dmz-wan-6 rule 300 log +set firewall ipv6 name dmz-wan-6 rule 300 protocol 'tcp' +set firewall ipv6 name dmz-wan-6 rule 600 +set firewall ipv6 name dmz-wan-6 rule 600 action 'accept' +set firewall ipv6 name dmz-wan-6 rule 600 destination port 53 +set firewall ipv6 name dmz-wan-6 rule 600 log +set firewall ipv6 name dmz-wan-6 rule 600 protocol 'tcp_udp' +set firewall ipv6 name dmz-wan-6 rule 800 +set firewall ipv6 name dmz-wan-6 rule 800 action 'accept' +set firewall ipv6 name dmz-wan-6 rule 800 destination port 22 +set firewall ipv6 name dmz-wan-6 rule 800 log +set firewall ipv6 name dmz-wan-6 rule 800 protocol 'tcp' +set firewall ipv6 name lan-dmz-6 default-action 'drop' +set firewall ipv6 name lan-dmz-6 default-log +set firewall ipv6 name lan-dmz-6 rule 1 +set firewall ipv6 name lan-dmz-6 rule 1 action 'accept' +set firewall ipv6 name lan-dmz-6 rule 1 state established +set firewall ipv6 name lan-dmz-6 rule 1 state related +set firewall ipv6 name lan-dmz-6 rule 2 +set firewall ipv6 name lan-dmz-6 rule 2 action 'drop' +set firewall ipv6 name lan-dmz-6 rule 2 log +set firewall ipv6 name lan-dmz-6 rule 2 state invalid +set firewall ipv6 name lan-dmz-6 rule 200 +set firewall ipv6 name lan-dmz-6 rule 200 action 'accept' +set firewall ipv6 name lan-dmz-6 rule 200 destination port 80,443 +set firewall ipv6 name lan-dmz-6 rule 200 log +set firewall ipv6 name lan-dmz-6 rule 200 protocol 'tcp' +set firewall ipv6 name lan-dmz-6 rule 800 +set firewall ipv6 name lan-dmz-6 rule 800 action 'accept' +set firewall ipv6 name lan-dmz-6 rule 800 destination address 2001:db8:0:bbbb::200 +set firewall ipv6 name lan-dmz-6 rule 800 destination port 22 +set firewall ipv6 name lan-dmz-6 rule 800 log +set firewall ipv6 name lan-dmz-6 rule 800 protocol 'tcp' +set firewall ipv6 name lan-dmz-6 rule 800 source address 2001:db8:0:aaaa::10 +set firewall ipv6 name lan-dmz-6 rule 900 +set firewall ipv6 name lan-dmz-6 rule 900 action 'accept' +set firewall ipv6 name lan-dmz-6 rule 900 destination port 993 +set firewall ipv6 name lan-dmz-6 rule 900 log +set firewall ipv6 name lan-dmz-6 rule 900 protocol 'tcp' +set firewall ipv6 name lan-local-6 default-action 'drop' +set firewall ipv6 name lan-local-6 default-log +set firewall ipv6 name lan-local-6 rule 1 +set firewall ipv6 name lan-local-6 rule 1 action 'accept' +set firewall ipv6 name lan-local-6 rule 1 state established +set firewall ipv6 name lan-local-6 rule 1 state related +set firewall ipv6 name lan-local-6 rule 2 +set firewall ipv6 name lan-local-6 rule 2 action 'drop' +set firewall ipv6 name lan-local-6 rule 2 log +set firewall ipv6 name lan-local-6 rule 2 state invalid +set firewall ipv6 name lan-local-6 rule 400 +set firewall ipv6 name lan-local-6 rule 400 action 'accept' +set firewall ipv6 name lan-local-6 rule 400 destination port 123 +set firewall ipv6 name lan-local-6 rule 400 log +set firewall ipv6 name lan-local-6 rule 400 protocol 'tcp' +set firewall ipv6 name lan-local-6 rule 600 +set firewall ipv6 name lan-local-6 rule 600 action 'accept' +set firewall ipv6 name lan-local-6 rule 600 destination port 53 +set firewall ipv6 name lan-local-6 rule 600 log +set firewall ipv6 name lan-local-6 rule 600 protocol 'tcp' +set firewall ipv6 name lan-local-6 rule 700 +set firewall ipv6 name lan-local-6 rule 700 action 'accept' +set firewall ipv6 name lan-local-6 rule 700 destination port 67,68 +set firewall ipv6 name lan-local-6 rule 700 log +set firewall ipv6 name lan-local-6 rule 700 protocol 'tcp' +set firewall ipv6 name lan-local-6 rule 800 +set firewall ipv6 name lan-local-6 rule 800 action 'accept' +set firewall ipv6 name lan-local-6 rule 800 destination port 22 +set firewall ipv6 name lan-local-6 rule 800 log +set firewall ipv6 name lan-local-6 rule 800 protocol 'tcp' +set firewall ipv6 name lan-local-6 rule 800 source address 2001:db8:0:aaaa::10 +set firewall ipv6 name lan-wan-6 default-action 'drop' +set firewall ipv6 name lan-wan-6 default-log +set firewall ipv6 name lan-wan-6 rule 1 +set firewall ipv6 name lan-wan-6 rule 1 action 'accept' +set firewall ipv6 name lan-wan-6 rule 1 state established +set firewall ipv6 name lan-wan-6 rule 1 state related +set firewall ipv6 name lan-wan-6 rule 2 +set firewall ipv6 name lan-wan-6 rule 2 action 'drop' +set firewall ipv6 name lan-wan-6 rule 2 log +set firewall ipv6 name lan-wan-6 rule 2 state invalid +set firewall ipv6 name lan-wan-6 rule 200 +set firewall ipv6 name lan-wan-6 rule 200 action 'accept' +set firewall ipv6 name lan-wan-6 rule 200 destination port 80,443 +set firewall ipv6 name lan-wan-6 rule 200 log +set firewall ipv6 name lan-wan-6 rule 200 protocol 'tcp' +set firewall ipv6 name lan-wan-6 rule 300 +set firewall ipv6 name lan-wan-6 rule 300 action 'accept' +set firewall ipv6 name lan-wan-6 rule 300 destination port 20,21 +set firewall ipv6 name lan-wan-6 rule 300 log +set firewall ipv6 name lan-wan-6 rule 300 protocol 'tcp' +set firewall ipv6 name lan-wan-6 rule 800 +set firewall ipv6 name lan-wan-6 rule 800 action 'accept' +set firewall ipv6 name lan-wan-6 rule 800 destination port 22 +set firewall ipv6 name lan-wan-6 rule 800 log +set firewall ipv6 name lan-wan-6 rule 800 protocol 'tcp' +set firewall ipv6 name local-dmz-6 default-action 'drop' +set firewall ipv6 name local-dmz-6 default-log +set firewall ipv6 name local-dmz-6 rule 1 +set firewall ipv6 name local-dmz-6 rule 1 action 'accept' +set firewall ipv6 name local-dmz-6 rule 1 state established +set firewall ipv6 name local-dmz-6 rule 1 state related +set firewall ipv6 name local-dmz-6 rule 2 +set firewall ipv6 name local-dmz-6 rule 2 action 'drop' +set firewall ipv6 name local-dmz-6 rule 2 log +set firewall ipv6 name local-dmz-6 rule 2 state invalid +set firewall ipv6 name local-dmz-6 rule 500 +set firewall ipv6 name local-dmz-6 rule 500 action 'accept' +set firewall ipv6 name local-dmz-6 rule 500 destination port 25 +set firewall ipv6 name local-dmz-6 rule 500 log +set firewall ipv6 name local-dmz-6 rule 500 protocol 'tcp' +set firewall ipv6 name local-dmz-6 rule 600 +set firewall ipv6 name local-dmz-6 rule 600 action 'accept' +set firewall ipv6 name local-dmz-6 rule 600 destination port 53 +set firewall ipv6 name local-dmz-6 rule 600 log +set firewall ipv6 name local-dmz-6 rule 600 protocol 'tcp_udp' +set firewall ipv6 name local-dmz-6 rule 700 +set firewall ipv6 name local-dmz-6 rule 700 action 'accept' +set firewall ipv6 name local-dmz-6 rule 700 destination port 67,68 +set firewall ipv6 name local-dmz-6 rule 700 log +set firewall ipv6 name local-dmz-6 rule 700 protocol 'tcp' +set firewall ipv6 name local-lan-6 default-action 'drop' +set firewall ipv6 name local-lan-6 default-log +set firewall ipv6 name local-lan-6 rule 1 +set firewall ipv6 name local-lan-6 rule 1 action 'accept' +set firewall ipv6 name local-lan-6 rule 1 state established +set firewall ipv6 name local-lan-6 rule 1 state related +set firewall ipv6 name local-lan-6 rule 2 +set firewall ipv6 name local-lan-6 rule 2 action 'drop' +set firewall ipv6 name local-lan-6 rule 2 log +set firewall ipv6 name local-lan-6 rule 2 state invalid +set firewall ipv6 name local-lan-6 rule 700 +set firewall ipv6 name local-lan-6 rule 700 action 'accept' +set firewall ipv6 name local-lan-6 rule 700 destination port 67,68 +set firewall ipv6 name local-lan-6 rule 700 log +set firewall ipv6 name local-lan-6 rule 700 protocol 'tcp' +set firewall ipv6 name local-wan-6 default-action 'drop' +set firewall ipv6 name local-wan-6 default-log +set firewall ipv6 name local-wan-6 rule 1 +set firewall ipv6 name local-wan-6 rule 1 action 'accept' +set firewall ipv6 name local-wan-6 rule 1 state established +set firewall ipv6 name local-wan-6 rule 1 state related +set firewall ipv6 name local-wan-6 rule 2 +set firewall ipv6 name local-wan-6 rule 2 action 'drop' +set firewall ipv6 name local-wan-6 rule 2 log +set firewall ipv6 name local-wan-6 rule 2 state invalid +set firewall ipv6 name local-wan-6 rule 200 +set firewall ipv6 name local-wan-6 rule 200 action 'accept' +set firewall ipv6 name local-wan-6 rule 200 destination port 80,443 +set firewall ipv6 name local-wan-6 rule 200 log +set firewall ipv6 name local-wan-6 rule 200 protocol 'tcp' +set firewall ipv6 name local-wan-6 rule 300 +set firewall ipv6 name local-wan-6 rule 300 action 'accept' +set firewall ipv6 name local-wan-6 rule 300 destination port 20,21 +set firewall ipv6 name local-wan-6 rule 300 log +set firewall ipv6 name local-wan-6 rule 300 protocol 'tcp' +set firewall ipv6 name wan-dmz-6 default-action 'drop' +set firewall ipv6 name wan-dmz-6 default-log +set firewall ipv6 name wan-dmz-6 rule 1 +set firewall ipv6 name wan-dmz-6 rule 1 action 'accept' +set firewall ipv6 name wan-dmz-6 rule 1 state established +set firewall ipv6 name wan-dmz-6 rule 1 state related +set firewall ipv6 name wan-dmz-6 rule 2 +set firewall ipv6 name wan-dmz-6 rule 2 action 'drop' +set firewall ipv6 name wan-dmz-6 rule 2 log +set firewall ipv6 name wan-dmz-6 rule 2 state invalid +set firewall ipv6 name wan-dmz-6 rule 200 +set firewall ipv6 name wan-dmz-6 rule 200 action 'accept' +set firewall ipv6 name wan-dmz-6 rule 200 destination address 2001:db8:0:bbbb::200 +set firewall ipv6 name wan-dmz-6 rule 200 destination port 80,443 +set firewall ipv6 name wan-dmz-6 rule 200 log +set firewall ipv6 name wan-dmz-6 rule 200 protocol 'tcp' +set firewall ipv6 name wan-dmz-6 rule 500 +set firewall ipv6 name wan-dmz-6 rule 500 action 'accept' +set firewall ipv6 name wan-dmz-6 rule 500 destination address 2001:db8:0:bbbb::200 +set firewall ipv6 name wan-dmz-6 rule 500 destination port 25 +set firewall ipv6 name wan-dmz-6 rule 500 log +set firewall ipv6 name wan-dmz-6 rule 500 protocol 'tcp' +set firewall ipv6 name wan-dmz-6 rule 600 +set firewall ipv6 name wan-dmz-6 rule 600 action 'accept' +set firewall ipv6 name wan-dmz-6 rule 600 destination address 2001:db8:0:bbbb::200 +set firewall ipv6 name wan-dmz-6 rule 600 destination port 53 +set firewall ipv6 name wan-dmz-6 rule 600 log +set firewall ipv6 name wan-dmz-6 rule 600 protocol 'tcp' +set firewall ipv6 name wan-lan-6 default-action 'drop' +set firewall ipv6 name wan-lan-6 default-log +set firewall ipv6 name wan-lan-6 rule 1 +set firewall ipv6 name wan-lan-6 rule 1 action 'accept' +set firewall ipv6 name wan-lan-6 rule 1 state established +set firewall ipv6 name wan-lan-6 rule 1 state related +set firewall ipv6 name wan-lan-6 rule 2 +set firewall ipv6 name wan-lan-6 rule 2 action 'drop' +set firewall ipv6 name wan-lan-6 rule 2 log +set firewall ipv6 name wan-lan-6 rule 2 state invalid +set firewall ipv6 name wan-local-6 default-action 'drop' +set firewall ipv6 name wan-local-6 default-log +set firewall ipv6 name wan-local-6 rule 1 +set firewall ipv6 name wan-local-6 rule 1 action 'accept' +set firewall ipv6 name wan-local-6 rule 1 state established +set firewall ipv6 name wan-local-6 rule 1 state related +set firewall ipv6 name wan-local-6 rule 2 +set firewall ipv6 name wan-local-6 rule 2 action 'drop' +set firewall ipv6 name wan-local-6 rule 2 log +set firewall ipv6 name wan-local-6 rule 2 state invalid +set firewall zone dmz default-action 'drop' +set firewall zone dmz default-log +set firewall zone dmz from lan firewall ipv6-name lan-dmz-6 +set firewall zone dmz from lan firewall name lan-dmz +set firewall zone dmz from local firewall ipv6-name local-dmz-6 +set firewall zone dmz from local firewall name local-dmz +set firewall zone dmz from wan firewall ipv6-name wan-dmz-6 +set firewall zone dmz from wan firewall name wan-dmz +set firewall zone dmz member interface eth0.30 +set firewall zone lan default-action 'drop' +set firewall zone lan default-log +set firewall zone lan from dmz firewall ipv6-name dmz-lan-6 +set firewall zone lan from dmz firewall name dmz-lan +set firewall zone lan from local firewall ipv6-name local-lan-6 +set firewall zone lan from local firewall name local-lan +set firewall zone lan from wan firewall ipv6-name wan-lan-6 +set firewall zone lan from wan firewall name wan-lan +set firewall zone lan member interface eth0.20 +set firewall zone local default-action 'drop' +set firewall zone local default-log +set firewall zone local from dmz firewall ipv6-name dmz-local-6 +set firewall zone local from dmz firewall name dmz-local +set firewall zone local from lan firewall ipv6-name lan-local-6 +set firewall zone local from lan firewall name lan-local +set firewall zone local from wan firewall ipv6-name wan-local-6 +set firewall zone local from wan firewall name wan-local +set firewall zone local local-zone +set firewall zone wan default-action 'drop' +set firewall zone wan default-log +set firewall zone wan from dmz firewall ipv6-name dmz-wan-6 +set firewall zone wan from dmz firewall name dmz-wan +set firewall zone wan from lan firewall ipv6-name lan-wan-6 +set firewall zone wan from lan firewall name lan-wan +set firewall zone wan from local firewall ipv6-name local-wan-6 +set firewall zone wan from local firewall name local-wan +set firewall zone wan member interface eth0.10 diff --git a/tests/render/cases/zone_firewall/docs_zone_policy/vars.yml b/tests/render/cases/zone_firewall/docs_zone_policy/vars.yml new file mode 100644 index 0000000..8450b97 --- /dev/null +++ b/tests/render/cases/zone_firewall/docs_zone_policy/vars.yml @@ -0,0 +1,71 @@ +--- +# docs.vyos.io/en/1.5/configexamples/zone-policy.html +# Zones and VLANs from "Native IPv4 and IPv6"; rules from the page's rule list, +# numbered with the page's scheme (100 ICMP, 200 Web, 300 FTP, 400 NTP, +# 500 SMTP, 600 DNS, 700 DHCP, 800 SSH, 900 IMAPS). Protocols are as listed +# on the page (it lists NTP and DHCP as tcp). +zone_firewall_zones: + - {name: wan, interfaces: [eth0.10]} + - {name: lan, interfaces: [eth0.20]} + - {name: dmz, interfaces: [eth0.30]} + - {name: local, local: true} + +zone_firewall_policies: + - from: wan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {address: 192.168.200.200, port: "80,443"}} + - {number: 200, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "80,443"}} + - {number: 500, protocol: tcp, destination: {address: 192.168.200.200, port: "25"}} + - {number: 500, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "25"}} + - {number: 600, protocol: tcp, destination: {address: 192.168.200.200, port: "53"}} + - {number: 600, protocol: tcp, destination: {address: "2001:db8:0:bbbb::200", port: "53"}} + - from: dmz + to: local + rules: + - {number: 400, protocol: tcp, destination: {port: "123"}} + - {number: 600, protocol: tcp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: lan + to: local + rules: + - {number: 400, protocol: tcp, destination: {port: "123"}} + - {number: 600, protocol: tcp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - {number: 800, protocol: tcp, source: {address: 192.168.100.10}, destination: {port: "22"}} + - {number: 800, protocol: tcp, source: {address: "2001:db8:0:aaaa::10"}, destination: {port: "22"}} + - from: lan + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - {number: 800, protocol: tcp, destination: {port: "22"}} + - from: dmz + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - {number: 600, protocol: tcp_udp, destination: {port: "53"}} + - {number: 800, protocol: tcp, destination: {port: "22"}} + - from: local + to: wan + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 300, protocol: tcp, destination: {port: "20,21"}} + - from: local + to: dmz + rules: + - {number: 500, protocol: tcp, destination: {port: "25"}} + - {number: 600, protocol: tcp_udp, destination: {port: "53"}} + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: local + to: lan + rules: + - {number: 700, protocol: tcp, destination: {port: "67,68"}} + - from: lan + to: dmz + rules: + - {number: 200, protocol: tcp, destination: {port: "80,443"}} + - {number: 800, protocol: tcp, source: {address: 192.168.100.10}, destination: {address: 192.168.200.200, port: "22"}} + - {number: 800, protocol: tcp, source: {address: "2001:db8:0:aaaa::10"}, destination: {address: "2001:db8:0:bbbb::200", port: "22"}} + - {number: 900, protocol: tcp, destination: {port: "993"}} diff --git a/tests/render/render.yml b/tests/render/render.yml index 7c0f433..3c86b41 100644 --- a/tests/render/render.yml +++ b/tests/render/render.yml @@ -5,11 +5,12 @@ vars: _expected_file: "{{ case_dir }}/expected/{{ inventory_hostname }}.txt" tasks: - - name: Render the role + - name: Render the role(s) ansible.builtin.include_role: - name: "vyos.blueprints.{{ role }}" + name: "vyos.blueprints.{{ item }}" vars: vyos_blueprints_render_only: true + loop: "{{ roles.split(',') if roles | default('') | length > 0 else [role] }}" - name: Normalise rendered commands ansible.builtin.set_fact: diff --git a/tests/render/run.sh b/tests/render/run.sh index 6b6fe1a..eaad5f6 100755 --- a/tests/render/run.sh +++ b/tests/render/run.sh @@ -6,8 +6,10 @@ # tests/render/run.sh ha_vrrp # cases whose "<role>/<case>" starts with this # UPDATE_GOLDEN=true tests/render/run.sh edge_nat # regenerate golden files # -# A case directory may contain vars.yml (shared inputs, passed as extra vars) -# and inventory.yml (multi-node cases; per-host inputs live there). +# A case directory may contain vars.yml (shared inputs, passed as extra vars), +# inventory.yml (multi-node cases; per-host inputs live there) and roles (a +# comma-separated role list, for blueprints that combine several roles; such +# cases live under cases/blueprints/). set -euo pipefail here="$(cd "$(dirname "$0")" && pwd)" update="${UPDATE_GOLDEN:-false}" @@ -23,6 +25,8 @@ for case_dir in "$here"/cases/*/*/; do args=(-i "$inventory" "$here/render.yml" -e "role=$role" -e "case_dir=$case_dir" -e "update_golden=$update") [[ -f "$case_dir/vars.yml" ]] && args+=(-e "@$case_dir/vars.yml") + # Blueprints that combine roles list them, comma-separated, in a "roles" file. + [[ -f "$case_dir/roles" ]] && args+=(-e "roles=$(tr -d '[:space:]' < "$case_dir/roles")") mkdir -p "$case_dir/expected" echo "=== $role/$name" if ! ansible-playbook "${args[@]}"; then |
