stack-orchestrator/playbooks/ashburn-relay-mia-sw01.yml

300 lines
11 KiB
YAML

---
# 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