From 6841d5e3c3a8e0b70639a2ab6503b25de7b27310 Mon Sep 17 00:00:00 2001 From: "A. F. Dudley" Date: Fri, 6 Mar 2026 21:08:48 +0000 Subject: [PATCH] feat: ashburn validator relay playbooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three playbooks for routing all validator traffic through 137.239.194.65: - was-sw01: Loopback101 + PBR redirect on Et1/1 (already applied/committed) Will be simplified to a static route in next iteration. - mia-sw01: ACL permit for src 137.239.194.65 on Tunnel500 + default route in vrf1 via egress-vrf default to was-sw01 backbone. No PBR needed — per-tunnel ACLs already scope what enters vrf1. - biscayne: DNAT inbound (137.239.194.65 → kind node), SNAT + policy routing outbound (validator sport 8001,9000-9025 → doublezero0 GRE). Inbound already applied. Co-Authored-By: Claude Opus 4.6 --- playbooks/ashburn-relay-biscayne.yml | 356 +++++++++++++++++++++++++++ playbooks/ashburn-relay-mia-sw01.yml | 215 ++++++++++++++++ playbooks/ashburn-relay-was-sw01.yml | 197 +++++++++++++++ 3 files changed, 768 insertions(+) create mode 100644 playbooks/ashburn-relay-biscayne.yml create mode 100644 playbooks/ashburn-relay-mia-sw01.yml create mode 100644 playbooks/ashburn-relay-was-sw01.yml diff --git a/playbooks/ashburn-relay-biscayne.yml b/playbooks/ashburn-relay-biscayne.yml new file mode 100644 index 00000000..75053483 --- /dev/null +++ b/playbooks/ashburn-relay-biscayne.yml @@ -0,0 +1,356 @@ +--- +# Configure biscayne for Ashburn validator relay +# +# Sets up inbound DNAT (137.239.194.65 → kind node) and outbound SNAT + +# policy routing (validator traffic → doublezero0 → mia-sw01 → was-sw01). +# +# Usage: +# # Full setup (inbound + outbound) +# ansible-playbook playbooks/ashburn-relay-biscayne.yml +# +# # Inbound only (DNAT rules) +# ansible-playbook playbooks/ashburn-relay-biscayne.yml -t inbound +# +# # Outbound only (SNAT + policy routing) +# ansible-playbook playbooks/ashburn-relay-biscayne.yml -t outbound +# +# # Pre-flight checks only +# ansible-playbook playbooks/ashburn-relay-biscayne.yml -t preflight +# +# # Rollback +# ansible-playbook playbooks/ashburn-relay-biscayne.yml -e rollback=true + +- name: Configure biscayne Ashburn validator relay + hosts: biscayne + gather_facts: false + + vars: + ashburn_ip: 137.239.194.65 + kind_node_ip: 172.20.0.2 + kind_network: 172.20.0.0/16 + tunnel_gateway: 169.254.7.6 + tunnel_device: doublezero0 + fwmark: 100 + rt_table_name: ashburn + rt_table_id: 100 + gossip_port: 8001 + dynamic_port_range_start: 9000 + dynamic_port_range_end: 9025 + rollback: false + + tasks: + # ------------------------------------------------------------------ + # Rollback + # ------------------------------------------------------------------ + - name: Rollback all Ashburn relay rules + when: rollback | bool + block: + - name: Remove Ashburn IP from loopback + ansible.builtin.command: + cmd: ip addr del {{ ashburn_ip }}/32 dev lo + failed_when: false + + - name: Remove inbound DNAT rules + ansible.builtin.shell: + cmd: | + set -o pipefail + iptables -t nat -D PREROUTING -p udp -d {{ ashburn_ip }} --dport {{ gossip_port }} -j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }} 2>/dev/null || true + iptables -t nat -D PREROUTING -p tcp -d {{ ashburn_ip }} --dport {{ gossip_port }} -j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }} 2>/dev/null || true + iptables -t nat -D PREROUTING -p udp -d {{ ashburn_ip }} --dport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} -j DNAT --to-destination {{ kind_node_ip }} 2>/dev/null || true + executable: /bin/bash + + - name: Remove outbound mangle rules + ansible.builtin.shell: + cmd: | + set -o pipefail + iptables -t mangle -D PREROUTING -s {{ kind_network }} -p udp --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true + iptables -t mangle -D PREROUTING -s {{ kind_network }} -p udp --sport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true + iptables -t mangle -D PREROUTING -s {{ kind_network }} -p tcp --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true + executable: /bin/bash + + - name: Remove outbound SNAT rule + ansible.builtin.shell: + cmd: iptables -t nat -D POSTROUTING -m mark --mark {{ fwmark }} -j SNAT --to-source {{ ashburn_ip }} 2>/dev/null || true + executable: /bin/bash + + - name: Remove policy routing + ansible.builtin.shell: + cmd: | + ip rule del fwmark {{ fwmark }} table {{ rt_table_name }} 2>/dev/null || true + ip route del default table {{ rt_table_name }} 2>/dev/null || true + executable: /bin/bash + + - name: Persist cleaned iptables + ansible.builtin.command: + cmd: netfilter-persistent save + + - name: Remove if-up.d script + ansible.builtin.file: + path: /etc/network/if-up.d/ashburn-routing + state: absent + + - name: Rollback complete + ansible.builtin.debug: + msg: "Ashburn relay rules removed. Old SHRED-RELAY DNAT (64.92.84.81:20000) is still in place." + + - name: End play after rollback + ansible.builtin.meta: end_play + + # ------------------------------------------------------------------ + # Pre-flight checks + # ------------------------------------------------------------------ + - name: Check doublezero0 tunnel is up + ansible.builtin.command: + cmd: ip link show {{ tunnel_device }} + register: tunnel_status + changed_when: false + failed_when: "'UP' not in tunnel_status.stdout" + tags: [preflight, inbound, outbound] + + - name: Check kind node is reachable + ansible.builtin.command: + cmd: ping -c 1 -W 2 {{ kind_node_ip }} + register: kind_ping + changed_when: false + failed_when: kind_ping.rc != 0 + tags: [preflight, inbound] + + - name: Verify Docker preserves source ports (5 sec sample) + ansible.builtin.shell: + cmd: | + set -o pipefail + # Check if any validator traffic is flowing with original sport + timeout 5 tcpdump -i br-cf46a62ab5b2 -nn -c 5 'udp src port 8001 or udp src portrange 9000-9025' 2>&1 | tail -5 || echo "No validator traffic captured in 5s (validator may not be running)" + executable: /bin/bash + register: sport_check + changed_when: false + failed_when: false + tags: [preflight] + + - name: Show sport preservation check + ansible.builtin.debug: + var: sport_check.stdout_lines + tags: [preflight] + + - name: Show existing iptables nat rules + ansible.builtin.shell: + cmd: iptables -t nat -L -v -n --line-numbers | head -60 + executable: /bin/bash + register: existing_nat + changed_when: false + tags: [preflight] + + - name: Display existing NAT rules + ansible.builtin.debug: + var: existing_nat.stdout_lines + tags: [preflight] + + # ------------------------------------------------------------------ + # Inbound: DNAT for 137.239.194.65 → kind node + # ------------------------------------------------------------------ + - name: Add Ashburn IP to loopback + ansible.builtin.command: + cmd: ip addr add {{ ashburn_ip }}/32 dev lo + register: add_ip + changed_when: add_ip.rc == 0 + failed_when: "add_ip.rc != 0 and 'RTNETLINK answers: File exists' not in add_ip.stderr" + tags: [inbound] + + - name: Add DNAT for gossip UDP + ansible.builtin.iptables: + table: nat + chain: PREROUTING + protocol: udp + destination: "{{ ashburn_ip }}" + destination_port: "{{ gossip_port }}" + jump: DNAT + to_destination: "{{ kind_node_ip }}:{{ gossip_port }}" + tags: [inbound] + + - name: Add DNAT for gossip TCP + ansible.builtin.iptables: + table: nat + chain: PREROUTING + protocol: tcp + destination: "{{ ashburn_ip }}" + destination_port: "{{ gossip_port }}" + jump: DNAT + to_destination: "{{ kind_node_ip }}:{{ gossip_port }}" + tags: [inbound] + + - name: Add DNAT for dynamic ports (UDP 9000-9025) + ansible.builtin.iptables: + table: nat + chain: PREROUTING + protocol: udp + destination: "{{ ashburn_ip }}" + destination_port: "{{ dynamic_port_range_start }}:{{ dynamic_port_range_end }}" + jump: DNAT + to_destination: "{{ kind_node_ip }}" + tags: [inbound] + + # ------------------------------------------------------------------ + # Outbound: fwmark + SNAT + policy routing + # ------------------------------------------------------------------ + - name: Mark outbound validator UDP gossip traffic + ansible.builtin.iptables: + table: mangle + chain: PREROUTING + protocol: udp + source: "{{ kind_network }}" + source_port: "{{ gossip_port }}" + jump: MARK + set_mark: "{{ fwmark }}" + tags: [outbound] + + - name: Mark outbound validator UDP dynamic port traffic + ansible.builtin.iptables: + table: mangle + chain: PREROUTING + protocol: udp + source: "{{ kind_network }}" + source_port: "{{ dynamic_port_range_start }}:{{ dynamic_port_range_end }}" + jump: MARK + set_mark: "{{ fwmark }}" + tags: [outbound] + + - name: Mark outbound validator TCP gossip traffic + ansible.builtin.iptables: + table: mangle + chain: PREROUTING + protocol: tcp + source: "{{ kind_network }}" + source_port: "{{ gossip_port }}" + jump: MARK + set_mark: "{{ fwmark }}" + tags: [outbound] + + - name: SNAT marked traffic to Ashburn IP (before Docker MASQUERADE) + ansible.builtin.shell: + cmd: | + set -o pipefail + # Check if rule already exists + if iptables -t nat -C POSTROUTING -m mark --mark {{ fwmark }} -j SNAT --to-source {{ ashburn_ip }} 2>/dev/null; then + echo "SNAT rule already exists" + else + iptables -t nat -I POSTROUTING 1 -m mark --mark {{ fwmark }} -j SNAT --to-source {{ ashburn_ip }} + echo "SNAT rule inserted at position 1" + fi + executable: /bin/bash + register: snat_result + changed_when: "'inserted' in snat_result.stdout" + tags: [outbound] + + - name: Show SNAT result + ansible.builtin.debug: + var: snat_result.stdout + tags: [outbound] + + - name: Ensure rt_tables entry exists + ansible.builtin.lineinfile: + path: /etc/iproute2/rt_tables + line: "{{ rt_table_id }} {{ rt_table_name }}" + regexp: "^{{ rt_table_id }}\\s" + tags: [outbound] + + - name: Add policy routing rule for fwmark + ansible.builtin.shell: + cmd: | + if ip rule show | grep -q 'fwmark 0x64 lookup ashburn'; then + echo "rule already exists" + else + ip rule add fwmark {{ fwmark }} table {{ rt_table_name }} + echo "rule added" + fi + executable: /bin/bash + register: rule_result + changed_when: "'added' in rule_result.stdout" + tags: [outbound] + + - name: Add default route via doublezero0 in ashburn table + ansible.builtin.shell: + cmd: ip route replace default via {{ tunnel_gateway }} dev {{ tunnel_device }} table {{ rt_table_name }} + executable: /bin/bash + changed_when: true + tags: [outbound] + + # ------------------------------------------------------------------ + # Persistence + # ------------------------------------------------------------------ + - name: Save iptables rules + ansible.builtin.command: + cmd: netfilter-persistent save + tags: [inbound, outbound] + + - name: Install if-up.d persistence script + ansible.builtin.copy: + src: files/ashburn-routing-ifup.sh + dest: /etc/network/if-up.d/ashburn-routing + mode: '0755' + owner: root + group: root + tags: [outbound] + + # ------------------------------------------------------------------ + # Verification + # ------------------------------------------------------------------ + - name: Show NAT rules + ansible.builtin.shell: + cmd: iptables -t nat -L -v -n --line-numbers 2>&1 | head -40 + executable: /bin/bash + register: nat_rules + changed_when: false + tags: [inbound, outbound] + + - name: Show mangle rules + ansible.builtin.shell: + cmd: iptables -t mangle -L -v -n 2>&1 + executable: /bin/bash + register: mangle_rules + changed_when: false + tags: [outbound] + + - name: Show policy routing + ansible.builtin.shell: + cmd: | + echo "=== ip rule ===" + ip rule show + echo "" + echo "=== ashburn routing table ===" + ip route show table {{ rt_table_name }} 2>/dev/null || echo "table empty" + executable: /bin/bash + register: routing_info + changed_when: false + tags: [outbound] + + - name: Show loopback addresses + ansible.builtin.shell: + cmd: ip addr show lo | grep inet + executable: /bin/bash + register: lo_addrs + changed_when: false + tags: [inbound] + + - name: Display verification + ansible.builtin.debug: + msg: + nat_rules: "{{ nat_rules.stdout_lines }}" + mangle_rules: "{{ mangle_rules.stdout_lines | default([]) }}" + routing: "{{ routing_info.stdout_lines | default([]) }}" + loopback: "{{ lo_addrs.stdout_lines }}" + tags: [inbound, outbound] + + - name: Summary + ansible.builtin.debug: + msg: | + === Ashburn Relay Setup Complete === + Ashburn IP: {{ ashburn_ip }} (on lo) + Inbound DNAT: {{ ashburn_ip }}:8001,9000-9025 → {{ kind_node_ip }} + Outbound SNAT: {{ kind_network }} sport 8001,9000-9025 → {{ ashburn_ip }} + Policy route: fwmark {{ fwmark }} → table {{ rt_table_name }} → via {{ tunnel_gateway }} dev {{ tunnel_device }} + Persisted: iptables-persistent + /etc/network/if-up.d/ashburn-routing + + Next steps: + 1. Verify inbound: ping {{ ashburn_ip }} from external host + 2. Verify outbound: tcpdump on was-sw01 for src {{ ashburn_ip }} + 3. Check validator gossip ContactInfo shows {{ ashburn_ip }} for all addresses diff --git a/playbooks/ashburn-relay-mia-sw01.yml b/playbooks/ashburn-relay-mia-sw01.yml new file mode 100644 index 00000000..6af443ad --- /dev/null +++ b/playbooks/ashburn-relay-mia-sw01.yml @@ -0,0 +1,215 @@ +--- +# Configure laconic-mia-sw01 for outbound validator traffic redirect +# +# Redirects outbound traffic from biscayne (src 137.239.194.65) arriving +# via the doublezero0 GRE tunnel to was-sw01 via the backbone, preventing +# BCP38 drops at mia-sw01's ISP uplink. +# +# Approach: The existing per-tunnel ACL (SEC-USER-500-IN) controls what +# traffic enters vrf1 from Tunnel500. We add 137.239.194.65 to the ACL +# and add a default route in vrf1 via egress-vrf default pointing to +# was-sw01's backbone IP. No PBR needed — the ACL is the filter. +# +# The other vrf1 tunnels (502, 504, 505) have their own ACLs that only +# permit their specific source IPs, so the default route won't affect them. +# +# Usage: +# # Pre-flight checks only (safe, read-only) +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-mia-sw01.yml +# +# # Apply config (after reviewing pre-flight output) +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-mia-sw01.yml \ +# -e apply=true +# +# # Commit persisted config +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-mia-sw01.yml -e commit=true +# +# # Rollback +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-mia-sw01.yml -e rollback=true + +- name: Configure mia-sw01 outbound validator redirect + hosts: mia-sw01 + gather_facts: false + + vars: + ashburn_ip: 137.239.194.65 + apply: false + commit: false + rollback: false + tunnel_interface: Tunnel500 + tunnel_vrf: vrf1 + tunnel_acl: SEC-USER-500-IN + backbone_interface: Ethernet4/1 + session_name: validator-outbound + checkpoint_name: pre-validator-outbound + + 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 + + # ------------------------------------------------------------------ + # Commit finalization + # ------------------------------------------------------------------ + - name: Finalize pending session + when: commit | bool + block: + - name: Commit session and write memory + arista.eos.eos_command: + commands: + - "configure session {{ session_name }} commit" + - write memory + register: commit_result + + - name: Show commit result + ansible.builtin.debug: + var: commit_result.stdout_lines + + - name: End play after commit + ansible.builtin.meta: end_play + + # ------------------------------------------------------------------ + # Pre-flight checks (always run unless commit/rollback) + # ------------------------------------------------------------------ + - name: Show tunnel interface config + arista.eos.eos_command: + commands: + - "show running-config interfaces {{ tunnel_interface }}" + register: tunnel_config + tags: [preflight] + + - name: Display tunnel config + ansible.builtin.debug: + var: tunnel_config.stdout_lines + tags: [preflight] + + - name: Show tunnel ACL + arista.eos.eos_command: + commands: + - "show running-config | section ip access-list {{ tunnel_acl }}" + register: acl_config + tags: [preflight] + + - name: Display tunnel ACL + ansible.builtin.debug: + var: acl_config.stdout_lines + tags: [preflight] + + - name: Check VRF routing + arista.eos.eos_command: + commands: + - "show ip route vrf {{ tunnel_vrf }} 0.0.0.0/0" + - "show ip route vrf {{ tunnel_vrf }} {{ backbone_peer }}" + - "show ip route {{ backbone_peer }}" + register: vrf_routing + tags: [preflight] + + - name: Display VRF routing check + ansible.builtin.debug: + var: vrf_routing.stdout_lines + tags: [preflight] + + - name: Pre-flight summary + when: not (apply | bool) + ansible.builtin.debug: + msg: | + === Pre-flight complete === + Review the output above: + 1. {{ tunnel_interface }} ACL ({{ tunnel_acl }}): does it permit src {{ ashburn_ip }}? + 2. {{ tunnel_vrf }} default route: does one exist? + 3. Backbone nexthop {{ backbone_peer }}: reachable in default VRF? + + To apply config: + ansible-playbook -i inventory/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 with 5-minute auto-revert + # ------------------------------------------------------------------ + - 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 }}" + # Permit Ashburn IP through the tunnel ACL (insert before deny) + - command: "ip access-list {{ tunnel_acl }}" + - command: "45 permit ip host {{ ashburn_ip }} any" + - command: exit + # Default route in vrf1 via backbone to was-sw01 (egress-vrf default) + # Safe because per-tunnel ACLs already restrict what enters vrf1 + - command: "ip route vrf {{ tunnel_vrf }} 0.0.0.0/0 egress-vrf default {{ backbone_interface }} {{ backbone_peer }}" + + - 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 with 5-minute auto-revert + arista.eos.eos_command: + commands: + - "configure session {{ session_name }} commit timer 00:05:00" + + # ------------------------------------------------------------------ + # Verify + # ------------------------------------------------------------------ + - name: Verify config + arista.eos.eos_command: + commands: + - "show running-config | section ip access-list {{ tunnel_acl }}" + - "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 with 5-minute auto-revert === + Session: {{ session_name }} + Checkpoint: {{ checkpoint_name }} + + Changes applied: + 1. ACL {{ tunnel_acl }}: added "45 permit ip host {{ ashburn_ip }} any" + 2. Default route in {{ tunnel_vrf }}: 0.0.0.0/0 egress-vrf default {{ backbone_interface }} {{ backbone_peer }} + + The config will auto-revert in 5 minutes unless committed. + Verify on the switch, then commit: + configure session {{ session_name }} commit + write memory + + To revert immediately: + ansible-playbook ... -e rollback=true diff --git a/playbooks/ashburn-relay-was-sw01.yml b/playbooks/ashburn-relay-was-sw01.yml new file mode 100644 index 00000000..1566fb0a --- /dev/null +++ b/playbooks/ashburn-relay-was-sw01.yml @@ -0,0 +1,197 @@ +--- +# Configure laconic-was-sw01 for full validator traffic relay +# +# Replaces the old SHRED-RELAY (TVU-only, port 20000) with VALIDATOR-RELAY +# covering all validator ports (8001, 9000-9025). Adds Loopback101 for +# 137.239.194.65. +# +# Uses EOS config session with 5-minute auto-revert for safety. +# After verification, run with -e commit=true to finalize. +# +# Usage: +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-was-sw01.yml +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-was-sw01.yml -e commit=true +# ansible-playbook -i inventory/switches.yml playbooks/ashburn-relay-was-sw01.yml -e rollback=true + +- name: Configure was-sw01 inbound validator relay + hosts: was-sw01 + gather_facts: false + + vars: + ashburn_ip: 137.239.194.65 + commit: false + rollback: false + session_name: validator-relay + checkpoint_name: pre-validator-relay + + 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 + + # ------------------------------------------------------------------ + # Commit finalization + # ------------------------------------------------------------------ + - name: Finalize pending session + when: commit | bool + block: + - name: Commit session and write memory + arista.eos.eos_command: + commands: + - "configure session {{ session_name }} commit" + - write memory + register: commit_result + + - name: Show commit result + ansible.builtin.debug: + var: commit_result.stdout_lines + + - name: End play after commit + ansible.builtin.meta: end_play + + # ------------------------------------------------------------------ + # Pre-checks + # ------------------------------------------------------------------ + - name: Show current traffic-policy on Et1/1 + arista.eos.eos_command: + commands: + - show running-config interfaces Ethernet1/1 + register: et1_config + + - name: Show current config + ansible.builtin.debug: + var: et1_config.stdout_lines + + - name: Show existing PBR policy on Et1/1 + arista.eos.eos_command: + commands: + - "show running-config | include service-policy" + register: existing_pbr + + - name: Show existing PBR config + ansible.builtin.debug: + var: existing_pbr.stdout_lines + + # ------------------------------------------------------------------ + # Save checkpoint + # ------------------------------------------------------------------ + - name: Save checkpoint for rollback + arista.eos.eos_command: + commands: + - "configure checkpoint save {{ checkpoint_name }}" + register: checkpoint_result + + - name: Show checkpoint result + ansible.builtin.debug: + var: checkpoint_result.stdout_lines + + # ------------------------------------------------------------------ + # Apply via config session with 5-minute auto-revert + # + # eos_config writes directly to running-config, bypassing sessions. + # Use eos_command with raw CLI to get the safety net. + # ------------------------------------------------------------------ + - name: Apply config session with auto-revert + arista.eos.eos_command: + commands: + # Enter named config session + - command: "configure session {{ session_name }}" + # Loopback101 for Ashburn IP + - command: interface Loopback101 + - command: "ip address {{ ashburn_ip }}/32" + - command: exit + # ACL covering all validator ports + - command: ip access-list VALIDATOR-RELAY-ACL + - command: 10 permit udp any any eq 8001 + - command: 20 permit udp any any range 9000 9025 + - command: 30 permit tcp any any eq 8001 + - command: exit + # PBR class-map referencing the ACL + - command: class-map type pbr match-any VALIDATOR-RELAY-CLASS + - command: match ip access-group VALIDATOR-RELAY-ACL + - command: exit + # PBR policy-map with nexthop redirect + - command: policy-map type pbr VALIDATOR-RELAY + - command: class VALIDATOR-RELAY-CLASS + - command: "set nexthop {{ backbone_peer }}" + - command: exit + - command: exit + # Apply PBR policy on Et1/1 + - command: interface Ethernet1/1 + - command: service-policy type pbr input VALIDATOR-RELAY + - command: exit + tags: [config] + + - 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 with 5-minute auto-revert + arista.eos.eos_command: + commands: + - "configure session {{ session_name }} commit timer 00:05:00" + tags: [config] + + # ------------------------------------------------------------------ + # Verify + # ------------------------------------------------------------------ + - name: Show PBR policy on Et1/1 + arista.eos.eos_command: + commands: + - show running-config interfaces Ethernet1/1 + - show running-config section policy-map + - show ip interface Loopback101 + register: pbr_interface + + - name: Display verification + ansible.builtin.debug: + var: pbr_interface.stdout_lines + + - name: Show Loopback101 + arista.eos.eos_command: + commands: + - show ip interface Loopback101 + register: lo101 + + - name: Display Loopback101 + ansible.builtin.debug: + var: lo101.stdout_lines + + - name: Reminder + ansible.builtin.debug: + msg: | + === Config applied with 5-minute auto-revert === + Session: {{ session_name }} + Checkpoint: {{ checkpoint_name }} + + The config will auto-revert in 5 minutes unless committed. + Verify PBR policy is applied, then commit from the switch CLI: + configure session {{ session_name }} commit + write memory + + To revert immediately: + ansible-playbook ... -e rollback=true