summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-10-05 20:56:05 +1000
committeromnom62 <omnom62@outlook.com>2026-10-05 20:56:05 +1000
commit8ee802e41374942965b6b93cfb4534725ef45145 (patch)
treeef3e5579bd767422caa6fe77cea6a7b5d2d8562c
parent748df2bc1d35fa285dd3fe46916e1230408778c7 (diff)
downloadvyos.blueprints-main.tar.gz
vyos.blueprints-main.zip
T9393: new rolesmain
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--CHANGELOG.md11
-rw-r--r--README.md10
-rw-r--r--examples/README.md13
-rw-r--r--examples/azure-vpn-bgp/group_vars/onprem.yml45
-rw-r--r--examples/azure-vpn-bgp/inventory.yml15
-rw-r--r--examples/azure-vpn-bgp/site.yml11
-rw-r--r--examples/azure-vpn-bgp/verify.yml9
-rw-r--r--examples/azure-vpn-dual-bgp/group_vars/onprem.yml64
-rw-r--r--examples/azure-vpn-dual-bgp/inventory.yml15
-rw-r--r--examples/azure-vpn-dual-bgp/site.yml11
-rw-r--r--examples/azure-vpn-dual-bgp/verify.yml9
-rw-r--r--examples/bgp-unnumbered/group_vars/fabric.yml3
-rw-r--r--examples/bgp-unnumbered/host_vars/routerA.yml3
-rw-r--r--examples/bgp-unnumbered/host_vars/routerB.yml3
-rw-r--r--examples/bgp-unnumbered/inventory.yml15
-rw-r--r--examples/bgp-unnumbered/site.yml11
-rw-r--r--examples/bgp-unnumbered/topology.clab.yml13
-rw-r--r--examples/bgp-unnumbered/verify.yml9
-rw-r--r--examples/bridge-firewall/group_vars/bridge.yml85
-rw-r--r--examples/bridge-firewall/inventory.yml13
-rw-r--r--examples/bridge-firewall/site.yml12
-rw-r--r--examples/bridge-firewall/topology.clab.yml63
-rw-r--r--examples/bridge-firewall/verify.yml9
-rw-r--r--examples/flexvpn-cisco/group_vars/spoke.yml44
-rw-r--r--examples/flexvpn-cisco/inventory.yml15
-rw-r--r--examples/flexvpn-cisco/site.yml12
-rw-r--r--examples/flexvpn-cisco/verify.yml13
-rw-r--r--examples/ipsec-route-based/group_vars/vpn.yml17
-rw-r--r--examples/ipsec-route-based/host_vars/cisco.yml20
-rw-r--r--examples/ipsec-route-based/host_vars/vyos.yml20
-rw-r--r--examples/ipsec-route-based/inventory.yml18
-rw-r--r--examples/ipsec-route-based/site.yml12
-rw-r--r--examples/ipsec-route-based/topology.clab.yml25
-rw-r--r--examples/ipsec-route-based/verify.yml9
-rw-r--r--examples/ospf-unnumbered/group_vars/fabric.yml4
-rw-r--r--examples/ospf-unnumbered/host_vars/routerA.yml5
-rw-r--r--examples/ospf-unnumbered/host_vars/routerB.yml4
-rw-r--r--examples/ospf-unnumbered/inventory.yml15
-rw-r--r--examples/ospf-unnumbered/site.yml12
-rw-r--r--examples/ospf-unnumbered/topology.clab.yml25
-rw-r--r--examples/ospf-unnumbered/verify.yml9
-rw-r--r--examples/policy-ipsec-firewall/group_vars/sites.yml5
-rw-r--r--examples/policy-ipsec-firewall/host_vars/left.yml98
-rw-r--r--examples/policy-ipsec-firewall/host_vars/right.yml23
-rw-r--r--examples/policy-ipsec-firewall/inventory.yml15
-rw-r--r--examples/policy-ipsec-firewall/site.yml15
-rw-r--r--examples/policy-ipsec-firewall/topology.clab.yml51
-rw-r--r--examples/policy-ipsec-firewall/verify.yml22
-rw-r--r--examples/vrf-firewall/group_vars/router.yml51
-rw-r--r--examples/vrf-firewall/inventory.yml13
-rw-r--r--examples/vrf-firewall/site.yml12
-rw-r--r--examples/vrf-firewall/topology.clab.yml43
-rw-r--r--examples/vrf-firewall/verify.yml9
-rw-r--r--examples/zone-policy/group_vars/firewall.yml80
-rw-r--r--examples/zone-policy/inventory.yml13
-rw-r--r--examples/zone-policy/site.yml12
-rw-r--r--examples/zone-policy/topology.clab.yml40
-rw-r--r--examples/zone-policy/verify.yml9
-rw-r--r--extensions/molecule/bgp_unnumbered/converge.yml6
-rw-r--r--extensions/molecule/bgp_unnumbered/inventory.yml23
-rw-r--r--extensions/molecule/bgp_unnumbered/molecule.yml28
-rw-r--r--extensions/molecule/bgp_unnumbered/topology.clab.yml14
-rw-r--r--extensions/molecule/bgp_unnumbered/verify.yml20
-rw-r--r--extensions/molecule/bridge_firewall/converge.yml7
-rw-r--r--extensions/molecule/bridge_firewall/inventory.yml94
-rw-r--r--extensions/molecule/bridge_firewall/molecule.yml27
-rw-r--r--extensions/molecule/bridge_firewall/topology.clab.yml63
-rw-r--r--extensions/molecule/bridge_firewall/verify.yml37
-rw-r--r--extensions/molecule/gre_tunnel/converge.yml7
-rw-r--r--extensions/molecule/gre_tunnel/inventory.yml24
-rw-r--r--extensions/molecule/gre_tunnel/molecule.yml28
-rw-r--r--extensions/molecule/gre_tunnel/topology.clab.yml12
-rw-r--r--extensions/molecule/gre_tunnel/verify.yml14
-rw-r--r--extensions/molecule/ipsec_route_based/converge.yml7
-rw-r--r--extensions/molecule/ipsec_route_based/inventory.yml61
-rw-r--r--extensions/molecule/ipsec_route_based/molecule.yml28
-rw-r--r--extensions/molecule/ipsec_route_based/topology.clab.yml32
-rw-r--r--extensions/molecule/ipsec_route_based/verify.yml29
-rw-r--r--extensions/molecule/ipsec_route_based_bgp/converge.yml7
-rw-r--r--extensions/molecule/ipsec_route_based_bgp/inventory.yml86
-rw-r--r--extensions/molecule/ipsec_route_based_bgp/molecule.yml28
-rw-r--r--extensions/molecule/ipsec_route_based_bgp/topology.clab.yml28
-rw-r--r--extensions/molecule/ipsec_route_based_bgp/verify.yml29
-rw-r--r--extensions/molecule/ipsec_route_based_bgp_dual/converge.yml7
-rw-r--r--extensions/molecule/ipsec_route_based_bgp_dual/inventory.yml115
-rw-r--r--extensions/molecule/ipsec_route_based_bgp_dual/molecule.yml29
-rw-r--r--extensions/molecule/ipsec_route_based_bgp_dual/topology.clab.yml39
-rw-r--r--extensions/molecule/ipsec_route_based_bgp_dual/verify.yml47
-rw-r--r--extensions/molecule/ospf_unnumbered/converge.yml7
-rw-r--r--extensions/molecule/ospf_unnumbered/inventory.yml26
-rw-r--r--extensions/molecule/ospf_unnumbered/molecule.yml28
-rw-r--r--extensions/molecule/ospf_unnumbered/topology.clab.yml20
-rw-r--r--extensions/molecule/ospf_unnumbered/verify.yml27
-rw-r--r--extensions/molecule/policy_ipsec_firewall/converge.yml9
-rw-r--r--extensions/molecule/policy_ipsec_firewall/inventory.yml131
-rw-r--r--extensions/molecule/policy_ipsec_firewall/molecule.yml28
-rw-r--r--extensions/molecule/policy_ipsec_firewall/topology.clab.yml51
-rw-r--r--extensions/molecule/policy_ipsec_firewall/verify.yml35
-rw-r--r--extensions/molecule/vrf_firewall/converge.yml7
-rw-r--r--extensions/molecule/vrf_firewall/inventory.yml54
-rw-r--r--extensions/molecule/vrf_firewall/molecule.yml27
-rw-r--r--extensions/molecule/vrf_firewall/topology.clab.yml43
-rw-r--r--extensions/molecule/vrf_firewall/verify.yml36
-rw-r--r--extensions/molecule/zone_firewall/converge.yml7
-rw-r--r--extensions/molecule/zone_firewall/inventory.yml33
-rw-r--r--extensions/molecule/zone_firewall/molecule.yml27
-rw-r--r--extensions/molecule/zone_firewall/topology.clab.yml34
-rw-r--r--extensions/molecule/zone_firewall/verify.yml30
-rw-r--r--roles/bgp_unnumbered/defaults/main.yml7
-rw-r--r--roles/bgp_unnumbered/meta/argument_specs.yml81
-rw-r--r--roles/bgp_unnumbered/meta/main.yml11
-rw-r--r--roles/bgp_unnumbered/tasks/main.yml40
-rw-r--r--roles/bgp_unnumbered/tasks/verify.yml44
-rw-r--r--roles/bgp_unnumbered/templates/bgp_address_family.yml.j211
-rw-r--r--roles/bgp_unnumbered/templates/bgp_global.yml.j27
-rw-r--r--roles/bgp_unnumbered/templates/bgp_neighbors.j214
-rw-r--r--roles/bgp_unnumbered/vars/main.yml3
-rw-r--r--roles/bridge_firewall/defaults/main.yml4
-rw-r--r--roles/bridge_firewall/meta/argument_specs.yml163
-rw-r--r--roles/bridge_firewall/meta/main.yml11
-rw-r--r--roles/bridge_firewall/tasks/main.yml35
-rw-r--r--roles/bridge_firewall/tasks/verify.yml27
-rw-r--r--roles/bridge_firewall/templates/bridge_cli.j272
-rw-r--r--roles/bridge_firewall/templates/ipv4_rules.yml.j254
-rw-r--r--roles/bridge_firewall/vars/main.yml11
-rw-r--r--roles/firewall/defaults/main.yml4
-rw-r--r--roles/firewall/meta/argument_specs.yml77
-rw-r--r--roles/firewall/meta/main.yml11
-rw-r--r--roles/firewall/tasks/main.yml22
-rw-r--r--roles/firewall/tasks/verify.yml23
-rw-r--r--roles/firewall/templates/groups.yml.j242
-rw-r--r--roles/firewall/templates/rules.yml.j217
-rw-r--r--roles/firewall/vars/main.yml3
-rw-r--r--roles/gre_tunnel/defaults/main.yml2
-rw-r--r--roles/gre_tunnel/meta/argument_specs.yml37
-rw-r--r--roles/gre_tunnel/meta/main.yml11
-rw-r--r--roles/gre_tunnel/tasks/main.yml20
-rw-r--r--roles/gre_tunnel/tasks/verify.yml15
-rw-r--r--roles/gre_tunnel/templates/tunnels.j222
-rw-r--r--roles/gre_tunnel/vars/main.yml11
-rw-r--r--roles/ha_vrrp/tasks/main.yml4
-rw-r--r--roles/ipsec_policy_based/defaults/main.yml19
-rw-r--r--roles/ipsec_policy_based/meta/argument_specs.yml145
-rw-r--r--roles/ipsec_policy_based/meta/main.yml11
-rw-r--r--roles/ipsec_policy_based/tasks/main.yml34
-rw-r--r--roles/ipsec_policy_based/tasks/verify.yml23
-rw-r--r--roles/ipsec_policy_based/templates/vpn_ipsec.yml.j258
-rw-r--r--roles/ipsec_policy_based/templates/vpn_ipsec_s2s.yml.j229
-rw-r--r--roles/ipsec_policy_based/vars/main.yml2
-rw-r--r--roles/ipsec_route_based/defaults/main.yml22
-rw-r--r--roles/ipsec_route_based/meta/argument_specs.yml209
-rw-r--r--roles/ipsec_route_based/meta/main.yml11
-rw-r--r--roles/ipsec_route_based/tasks/main.yml113
-rw-r--r--roles/ipsec_route_based/tasks/verify.yml52
-rw-r--r--roles/ipsec_route_based/templates/bgp_address_family.yml.j220
-rw-r--r--roles/ipsec_route_based/templates/bgp_global.yml.j229
-rw-r--r--roles/ipsec_route_based/templates/interface_routes.yml.j25
-rw-r--r--roles/ipsec_route_based/templates/ospf_interfaces.yml.j212
-rw-r--r--roles/ipsec_route_based/templates/ospfv2.yml.j29
-rw-r--r--roles/ipsec_route_based/templates/vpn_ipsec.yml.j251
-rw-r--r--roles/ipsec_route_based/templates/vpn_ipsec_s2s.yml.j227
-rw-r--r--roles/ipsec_route_based/templates/vti.yml.j29
-rw-r--r--roles/ipsec_route_based/templates/vti_cli.j24
-rw-r--r--roles/ipsec_route_based/templates/vti_l3.yml.j25
-rw-r--r--roles/ipsec_route_based/vars/main.yml23
-rw-r--r--roles/nat/defaults/main.yml3
-rw-r--r--roles/nat/meta/argument_specs.yml33
-rw-r--r--roles/nat/meta/main.yml11
-rw-r--r--roles/nat/tasks/main.yml15
-rw-r--r--roles/nat/tasks/verify.yml25
-rw-r--r--roles/nat/vars/main.yml2
-rw-r--r--roles/ospf_unnumbered/defaults/main.yml6
-rw-r--r--roles/ospf_unnumbered/meta/argument_specs.yml68
-rw-r--r--roles/ospf_unnumbered/meta/main.yml11
-rw-r--r--roles/ospf_unnumbered/tasks/main.yml28
-rw-r--r--roles/ospf_unnumbered/tasks/verify.yml42
-rw-r--r--roles/ospf_unnumbered/templates/l3_interfaces.yml.j28
-rw-r--r--roles/ospf_unnumbered/templates/ospf_interfaces.yml.j212
-rw-r--r--roles/ospf_unnumbered/templates/ospfv2.yml.j215
-rw-r--r--roles/ospf_unnumbered/vars/main.yml3
-rw-r--r--roles/vrf_firewall/defaults/main.yml11
-rw-r--r--roles/vrf_firewall/meta/argument_specs.yml139
-rw-r--r--roles/vrf_firewall/meta/main.yml11
-rw-r--r--roles/vrf_firewall/tasks/main.yml60
-rw-r--r--roles/vrf_firewall/tasks/verify.yml26
-rw-r--r--roles/vrf_firewall/templates/firewall_rules.yml.j245
-rw-r--r--roles/vrf_firewall/templates/vrf.yml.j223
-rw-r--r--roles/vrf_firewall/templates/vrf_cli.j215
-rw-r--r--roles/vrf_firewall/vars/main.yml24
-rw-r--r--roles/zone_firewall/defaults/main.yml6
-rw-r--r--roles/zone_firewall/meta/argument_specs.yml151
-rw-r--r--roles/zone_firewall/meta/main.yml11
-rw-r--r--roles/zone_firewall/tasks/main.yml34
-rw-r--r--roles/zone_firewall/tasks/verify.yml24
-rw-r--r--roles/zone_firewall/templates/firewall_rules.yml.j247
-rw-r--r--roles/zone_firewall/templates/zones.yml.j228
-rw-r--r--roles/zone_firewall/vars/main.yml17
-rw-r--r--tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerA.txt16
-rw-r--r--tests/render/cases/bgp_unnumbered/docs_two_routers/expected/routerB.txt16
-rw-r--r--tests/render/cases/bgp_unnumbered/docs_two_routers/inventory.yml19
-rw-r--r--tests/render/cases/bgp_unnumbered/docs_two_routers/vars.yml3
-rw-r--r--tests/render/cases/blueprints/flexvpn_cisco/expected/r1.txt42
-rw-r--r--tests/render/cases/blueprints/flexvpn_cisco/roles1
-rw-r--r--tests/render/cases/blueprints/flexvpn_cisco/vars.yml46
-rw-r--r--tests/render/cases/blueprints/policy_ipsec_firewall/expected/LEFT.txt100
-rw-r--r--tests/render/cases/blueprints/policy_ipsec_firewall/expected/RIGHT.txt35
-rw-r--r--tests/render/cases/blueprints/policy_ipsec_firewall/inventory.yml67
-rw-r--r--tests/render/cases/blueprints/policy_ipsec_firewall/roles1
-rw-r--r--tests/render/cases/blueprints/policy_ipsec_firewall/vars.yml17
-rw-r--r--tests/render/cases/bridge_firewall/docs_three_bridges/expected/r1.txt159
-rw-r--r--tests/render/cases/bridge_firewall/docs_three_bridges/vars.yml81
-rw-r--r--tests/render/cases/ipsec_route_based/docs_azure/expected/r1.txt46
-rw-r--r--tests/render/cases/ipsec_route_based/docs_azure/vars.yml45
-rw-r--r--tests/render/cases/ipsec_route_based/docs_azure_dual/expected/r1.txt69
-rw-r--r--tests/render/cases/ipsec_route_based/docs_azure_dual/vars.yml64
-rw-r--r--tests/render/cases/ipsec_route_based/docs_cisco/expected/r1.txt45
-rw-r--r--tests/render/cases/ipsec_route_based/docs_cisco/vars.yml33
-rw-r--r--tests/render/cases/ipsec_route_based/docs_palo_alto/expected/r1.txt45
-rw-r--r--tests/render/cases/ipsec_route_based/docs_palo_alto/vars.yml33
-rw-r--r--tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerA.txt12
-rw-r--r--tests/render/cases/ospf_unnumbered/docs_two_routers/expected/routerB.txt12
-rw-r--r--tests/render/cases/ospf_unnumbered/docs_two_routers/inventory.yml17
-rw-r--r--tests/render/cases/ospf_unnumbered/docs_two_routers/vars.yml4
-rw-r--r--tests/render/cases/vrf_firewall/docs_four_vrfs/expected/r1.txt53
-rw-r--r--tests/render/cases/vrf_firewall/docs_four_vrfs/vars.yml43
-rw-r--r--tests/render/cases/zone_firewall/docs_zone_policy/expected/r1.txt548
-rw-r--r--tests/render/cases/zone_firewall/docs_zone_policy/vars.yml71
-rw-r--r--tests/render/render.yml5
-rwxr-xr-xtests/render/run.sh8
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").
diff --git a/README.md b/README.md
index 5d3ef4a..05afd56 100644
--- a/README.md
+++ b/README.md
@@ -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