--- # Configure laconic-mia-sw01 for validator traffic relay via dedicated GRE tunnel # # Creates a NEW GRE tunnel (Tunnel100) separate from the DoubleZero-managed # Tunnel500. The DZ agent controls Tunnel500's ACL (SEC-USER-500-IN) and # overwrites any custom entries, so we cannot use it for validator traffic # with src 137.239.194.65. # # Tunnel100 uses mia-sw01's free LAN IP (209.42.167.137) as the tunnel # source, and biscayne's public IP (186.233.184.235) as the destination. # This tunnel carries traffic over the ISP uplink, completely independent # of the DoubleZero overlay. # # Outbound routing uses VRF isolation instead of PBR. Tunnel100 lives in # VRF "relay" whose only default route points to was-sw01 via the backbone. # Traffic decapsulated from Tunnel100 (src 137.239.194.65) routes via VRF # relay's table, which sends it to was-sw01 where the source IP is # legitimate. No PBR or traffic-policy needed — the TCAM profile # (tunnel-interface-acl) doesn't support either on tunnel interfaces. # # Inbound: was-sw01 → backbone Et4/1 → mia-sw01 → egress-vrf relay → # Tunnel100 → biscayne # Outbound: biscayne → Tunnel100 (VRF relay) → egress-vrf default → # backbone Et4/1 → was-sw01 # # Usage: # # Pre-flight checks only (safe, read-only) # ansible-playbook -i inventory-switches/switches.yml playbooks/ashburn-relay-mia-sw01.yml # # # Apply config (after reviewing pre-flight output) # ansible-playbook -i inventory-switches/switches.yml playbooks/ashburn-relay-mia-sw01.yml \ # -e apply=true # # # Persist to startup-config (write memory) # ansible-playbook -i inventory-switches/switches.yml playbooks/ashburn-relay-mia-sw01.yml \ # -e commit=true # # # Rollback # ansible-playbook -i inventory-switches/switches.yml playbooks/ashburn-relay-mia-sw01.yml \ # -e rollback=true - name: Configure mia-sw01 validator relay tunnel hosts: all gather_facts: false vars: ashburn_ip: 137.239.194.65 biscayne_ip: 186.233.184.235 apply: false commit: false rollback: false # New tunnel — not managed by DZ agent tunnel_interface: Tunnel100 tunnel_source_ip: 209.42.167.137 # mia-sw01 free LAN IP tunnel_local: 169.254.100.0 # /31 link, mia-sw01 side tunnel_remote: 169.254.100.1 # /31 link, biscayne side tunnel_acl: SEC-VALIDATOR-100-IN # Loopback for tunnel source (so it's always up) tunnel_source_lo: Loopback101 # VRF for outbound routing — isolates tunnel traffic from default table tunnel_vrf: relay backbone_interface: Ethernet4/1 backbone_peer: 172.16.1.188 # was-sw01 backbone IP session_name: validator-tunnel checkpoint_name: pre-validator-tunnel tasks: # ------------------------------------------------------------------ # Rollback path # ------------------------------------------------------------------ - name: Rollback to checkpoint when: rollback | bool block: - name: Execute rollback arista.eos.eos_command: commands: - "rollback running-config checkpoint {{ checkpoint_name }}" - write memory register: rollback_result - name: Show rollback result ansible.builtin.debug: var: rollback_result.stdout_lines - name: End play after rollback ansible.builtin.meta: end_play # ------------------------------------------------------------------ # Write memory (persist to startup-config) # ------------------------------------------------------------------ - name: Write memory to persist config when: commit | bool block: - name: Write memory arista.eos.eos_command: commands: - write memory register: write_result - name: Show write result ansible.builtin.debug: var: write_result.stdout_lines - name: End play after write ansible.builtin.meta: end_play # ------------------------------------------------------------------ # Pre-flight checks (always run unless commit/rollback) # ------------------------------------------------------------------ - name: Check existing tunnel interfaces arista.eos.eos_command: commands: - show ip interface brief | include Tunnel register: existing_tunnels tags: [preflight] - name: Display existing tunnels ansible.builtin.debug: var: existing_tunnels.stdout_lines tags: [preflight] - name: Check if Tunnel100 already exists arista.eos.eos_command: commands: - "show running-config interfaces {{ tunnel_interface }}" register: tunnel_config tags: [preflight] - name: Display Tunnel100 config ansible.builtin.debug: var: tunnel_config.stdout_lines tags: [preflight] - name: Check if Loopback101 already exists arista.eos.eos_command: commands: - "show running-config interfaces {{ tunnel_source_lo }}" register: lo_config tags: [preflight] - name: Display Loopback101 config ansible.builtin.debug: var: lo_config.stdout_lines tags: [preflight] - name: Check VRF state arista.eos.eos_command: commands: - "show vrf {{ tunnel_vrf }}" register: vrf_check tags: [preflight] ignore_errors: true - name: Display VRF state ansible.builtin.debug: var: vrf_check.stdout_lines tags: [preflight] - name: Check route for ashburn IP arista.eos.eos_command: commands: - "show ip route {{ ashburn_ip }}" register: route_check tags: [preflight] - name: Display route check ansible.builtin.debug: var: route_check.stdout_lines tags: [preflight] - name: Pre-flight summary when: not (apply | bool) ansible.builtin.debug: msg: | === Pre-flight complete === Review the output above: 1. Does {{ tunnel_interface }} already exist? 2. Does {{ tunnel_source_lo }} already exist? 3. Does VRF {{ tunnel_vrf }} already exist? 4. Current route for {{ ashburn_ip }} Planned config: - VRF {{ tunnel_vrf }}: isolates tunnel outbound traffic - {{ tunnel_source_lo }}: {{ tunnel_source_ip }}/32 - {{ tunnel_interface }}: GRE src {{ tunnel_source_ip }} dst {{ biscayne_ip }} VRF {{ tunnel_vrf }}, link address {{ tunnel_local }}/31 ACL {{ tunnel_acl }}: permit src {{ ashburn_ip }}, permit src {{ tunnel_remote }} - Inbound: {{ ashburn_ip }}/32 egress-vrf {{ tunnel_vrf }} via {{ tunnel_remote }} - Outbound: 0.0.0.0/0 in VRF {{ tunnel_vrf }} egress-vrf default via {{ backbone_peer }} To apply config: ansible-playbook -i inventory-switches/switches.yml \ playbooks/ashburn-relay-mia-sw01.yml -e apply=true tags: [preflight] - name: End play if not applying when: not (apply | bool) ansible.builtin.meta: end_play # ------------------------------------------------------------------ # Apply config via session (checkpoint saved for rollback) # ------------------------------------------------------------------ - name: Save checkpoint arista.eos.eos_command: commands: - "configure checkpoint save {{ checkpoint_name }}" - name: Apply config session arista.eos.eos_command: commands: - command: "configure session {{ session_name }}" # Loopback for tunnel source (always-up interface) - command: "interface {{ tunnel_source_lo }}" - command: "ip address {{ tunnel_source_ip }}/32" - command: exit # VRF for tunnel outbound isolation - command: "vrf instance {{ tunnel_vrf }}" - command: exit - command: "ip routing vrf {{ tunnel_vrf }}" # ACL for the new tunnel — we control this, DZ agent won't touch it - command: "ip access-list {{ tunnel_acl }}" - command: "counters per-entry" - command: "10 permit icmp host {{ tunnel_remote }} any" - command: "20 permit ip host {{ ashburn_ip }} any" - command: "30 permit ip host {{ tunnel_remote }} any" - command: "100 deny ip any any" - command: exit # GRE tunnel in VRF relay - command: "interface {{ tunnel_interface }}" - command: "mtu 9216" - command: "vrf {{ tunnel_vrf }}" - command: "ip address {{ tunnel_local }}/31" - command: "ip access-group {{ tunnel_acl }} in" - command: "tunnel mode gre" - command: "tunnel source {{ tunnel_source_ip }}" - command: "tunnel destination {{ biscayne_ip }}" - command: exit # Outbound: default route in VRF relay → backbone → was-sw01 - command: "ip route vrf {{ tunnel_vrf }} 0.0.0.0/0 egress-vrf default {{ backbone_peer }}" # Inbound: route ashburn IP from default VRF into tunnel via VRF relay - command: "ip route {{ ashburn_ip }}/32 egress-vrf {{ tunnel_vrf }} {{ tunnel_remote }}" - name: Show session diff arista.eos.eos_command: commands: - "configure session {{ session_name }}" - show session-config diffs - exit register: session_diff - name: Display session diff ansible.builtin.debug: var: session_diff.stdout_lines - name: Commit session (checkpoint saved for rollback) arista.eos.eos_command: commands: - "configure session {{ session_name }} commit" # ------------------------------------------------------------------ # Verify # ------------------------------------------------------------------ - name: Verify config arista.eos.eos_command: commands: - "show running-config interfaces {{ tunnel_source_lo }}" - "show running-config interfaces {{ tunnel_interface }}" - "show ip access-lists {{ tunnel_acl }}" - "show vrf {{ tunnel_vrf }}" - "show ip route {{ ashburn_ip }}" - "show ip route vrf {{ tunnel_vrf }} 0.0.0.0/0" register: verify - name: Display verification ansible.builtin.debug: var: verify.stdout_lines - name: Reminder ansible.builtin.debug: msg: | === Config applied (running-config only) === Checkpoint: {{ checkpoint_name }} Changes applied: 1. {{ tunnel_source_lo }}: {{ tunnel_source_ip }}/32 2. VRF {{ tunnel_vrf }}: outbound isolation for tunnel traffic 3. {{ tunnel_interface }}: GRE tunnel to {{ biscayne_ip }} in VRF {{ tunnel_vrf }} link {{ tunnel_local }}/31, ACL {{ tunnel_acl }} 4. Inbound: {{ ashburn_ip }}/32 egress-vrf {{ tunnel_vrf }} via {{ tunnel_remote }} 5. Outbound: 0.0.0.0/0 in VRF {{ tunnel_vrf }} egress-vrf default via {{ backbone_peer }} Config is in running-config but NOT saved to startup-config. A reboot will revert to the previous state. To persist (write memory): ansible-playbook ... -e commit=true To rollback immediately: ansible-playbook ... -e rollback=true