2026-03-06 21:08:48 +00:00
|
|
|
---
|
|
|
|
|
# Configure biscayne for Ashburn validator relay
|
|
|
|
|
#
|
2026-03-10 05:54:08 +00:00
|
|
|
# WHY THIS USES FIREWALLD ZONES (not just iptables):
|
2026-03-07 01:47:58 +00:00
|
|
|
#
|
2026-03-10 05:54:08 +00:00
|
|
|
# Biscayne runs firewalld with the nftables backend. Firewalld's nftables
|
|
|
|
|
# filter_FORWARD chain (priority filter+10) rejects forwarded traffic
|
|
|
|
|
# between interfaces that aren't in known zones. The original playbook
|
|
|
|
|
# used only iptables rules, which run at priority filter (0) and were
|
|
|
|
|
# accepted by Docker's chains — but then firewalld's nftables chain
|
|
|
|
|
# rejected the same packets because it didn't know about the Docker
|
|
|
|
|
# bridges or gre-ashburn.
|
|
|
|
|
#
|
|
|
|
|
# The fix: Docker bridges go in the 'docker' zone, gre-ashburn goes in
|
|
|
|
|
# 'trusted', and a 'docker-to-relay' policy allows forwarding between
|
|
|
|
|
# them. These are firewalld --permanent rules that survive reboots.
|
|
|
|
|
#
|
|
|
|
|
# WHY IPTABLES IS STILL NEEDED:
|
|
|
|
|
#
|
|
|
|
|
# Docker's iptables FORWARD chain (priority filter) runs BEFORE
|
|
|
|
|
# firewalld's nftables chain (priority filter+10). Docker's FORWARD
|
|
|
|
|
# policy is DROP, and its DOCKER-FORWARD subchain only accepts
|
|
|
|
|
# RELATED,ESTABLISHED inbound. So NEW inbound DNAT'd traffic is dropped
|
|
|
|
|
# by Docker before firewalld can accept it. DOCKER-USER ACCEPT rules
|
|
|
|
|
# and DNAT-before-Docker ordering must remain as iptables.
|
|
|
|
|
#
|
|
|
|
|
# Layers:
|
|
|
|
|
# 1. Firewalld zones + policies (permanent, survives reboots/reloads)
|
|
|
|
|
# 2. GRE tunnel + loopback IP (iproute2, restored by systemd service)
|
|
|
|
|
# 3. iptables DNAT/SNAT/mangle (restored by systemd service)
|
|
|
|
|
# 4. Policy routing (iproute2, restored by systemd service)
|
2026-03-06 21:08:48 +00:00
|
|
|
#
|
|
|
|
|
# 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
|
|
|
|
|
#
|
|
|
|
|
# # Rollback
|
|
|
|
|
# ansible-playbook playbooks/ashburn-relay-biscayne.yml -e rollback=true
|
|
|
|
|
|
|
|
|
|
- name: Configure biscayne Ashburn validator relay
|
2026-03-07 22:28:21 +00:00
|
|
|
hosts: all
|
2026-03-06 21:08:48 +00:00
|
|
|
gather_facts: false
|
|
|
|
|
|
|
|
|
|
vars:
|
|
|
|
|
ashburn_ip: 137.239.194.65
|
|
|
|
|
kind_node_ip: 172.20.0.2
|
|
|
|
|
kind_network: 172.20.0.0/16
|
2026-03-07 01:47:58 +00:00
|
|
|
tunnel_device: gre-ashburn
|
|
|
|
|
tunnel_local_ip: 169.254.100.1 # biscayne end of /31
|
|
|
|
|
tunnel_remote_ip: 169.254.100.0 # mia-sw01 end of /31
|
|
|
|
|
tunnel_src: 186.233.184.235 # biscayne public IP
|
|
|
|
|
tunnel_dst: 209.42.167.137 # mia-sw01 free LAN IP
|
2026-03-06 21:08:48 +00:00
|
|
|
fwmark: 100
|
|
|
|
|
rt_table_name: ashburn
|
|
|
|
|
rt_table_id: 100
|
|
|
|
|
gossip_port: 8001
|
|
|
|
|
dynamic_port_range_start: 9000
|
|
|
|
|
dynamic_port_range_end: 9025
|
2026-03-10 05:54:08 +00:00
|
|
|
# Docker bridge for the kind network — find with:
|
|
|
|
|
# ip route get 172.20.0.2 | grep -oP 'dev \K\S+'
|
|
|
|
|
docker_bridges:
|
|
|
|
|
- br-cf46a62ab5b2
|
|
|
|
|
- docker0
|
|
|
|
|
- br-4fb6f6795448
|
2026-03-06 21:08:48 +00:00
|
|
|
rollback: false
|
|
|
|
|
|
|
|
|
|
tasks:
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Rollback
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
- name: Rollback all Ashburn relay rules
|
|
|
|
|
when: rollback | bool
|
|
|
|
|
block:
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Remove firewalld zone assignments
|
|
|
|
|
ansible.posix.firewalld:
|
|
|
|
|
zone: "{{ item.zone }}"
|
|
|
|
|
interface: "{{ item.iface }}"
|
|
|
|
|
permanent: true
|
|
|
|
|
state: disabled
|
|
|
|
|
loop:
|
|
|
|
|
- {zone: docker, iface: br-cf46a62ab5b2}
|
|
|
|
|
- {zone: docker, iface: docker0}
|
|
|
|
|
- {zone: docker, iface: br-4fb6f6795448}
|
|
|
|
|
- {zone: trusted, iface: gre-ashburn}
|
|
|
|
|
failed_when: false
|
|
|
|
|
|
|
|
|
|
- name: Remove docker-to-relay policy
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: firewall-cmd --permanent --delete-policy=docker-to-relay
|
|
|
|
|
failed_when: false
|
|
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: Reload firewalld
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: firewall-cmd --reload
|
|
|
|
|
changed_when: false
|
|
|
|
|
|
2026-03-06 21:08:48 +00:00
|
|
|
- name: Remove Ashburn IP from loopback
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: ip addr del {{ ashburn_ip }}/32 dev lo
|
|
|
|
|
failed_when: false
|
2026-03-07 01:47:58 +00:00
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: Remove GRE tunnel
|
2026-03-10 05:54:08 +00:00
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: ip tunnel del {{ tunnel_device }}
|
|
|
|
|
failed_when: false
|
2026-03-07 01:47:58 +00:00
|
|
|
changed_when: false
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Flush iptables relay rules
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
2026-03-10 05:54:08 +00:00
|
|
|
set -euo pipefail
|
|
|
|
|
# DNAT
|
2026-03-07 22:28:21 +00:00
|
|
|
iptables -t nat -D PREROUTING -p udp -d {{ ashburn_ip }} \
|
|
|
|
|
--dport {{ gossip_port }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }} 2>/dev/null || true
|
2026-03-07 22:28:21 +00:00
|
|
|
iptables -t nat -D PREROUTING -p tcp -d {{ ashburn_ip }} \
|
|
|
|
|
--dport {{ gossip_port }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }} 2>/dev/null || true
|
2026-03-07 22:28:21 +00:00
|
|
|
iptables -t nat -D PREROUTING -p udp -d {{ ashburn_ip }} \
|
|
|
|
|
--dport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-j DNAT --to-destination {{ kind_node_ip }} 2>/dev/null || true
|
|
|
|
|
# DOCKER-USER
|
2026-03-08 02:43:31 +00:00
|
|
|
iptables -D DOCKER-USER -p udp -d {{ kind_node_ip }} \
|
|
|
|
|
--dport {{ gossip_port }} -j ACCEPT 2>/dev/null || true
|
|
|
|
|
iptables -D DOCKER-USER -p tcp -d {{ kind_node_ip }} \
|
|
|
|
|
--dport {{ gossip_port }} -j ACCEPT 2>/dev/null || true
|
|
|
|
|
iptables -D DOCKER-USER -p udp -d {{ kind_node_ip }} \
|
|
|
|
|
--dport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} \
|
|
|
|
|
-j ACCEPT 2>/dev/null || true
|
2026-03-10 05:54:08 +00:00
|
|
|
# Mangle
|
2026-03-07 22:28:21 +00:00
|
|
|
iptables -t mangle -D PREROUTING -s {{ kind_network }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-p udp --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true
|
2026-03-07 22:28:21 +00:00
|
|
|
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 }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-p tcp --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true
|
2026-03-07 23:31:18 +00:00
|
|
|
iptables -t mangle -D PREROUTING -s {{ kind_network }} \
|
2026-03-10 05:54:08 +00:00
|
|
|
-p tcp --dport {{ gossip_port }} -j MARK --set-mark {{ fwmark }} 2>/dev/null || true
|
|
|
|
|
# SNAT
|
|
|
|
|
iptables -t nat -D POSTROUTING -m mark --mark {{ fwmark }} \
|
|
|
|
|
-j SNAT --to-source {{ ashburn_ip }} 2>/dev/null || true
|
2026-03-06 21:08:48 +00:00
|
|
|
executable: /bin/bash
|
2026-03-07 01:47:58 +00:00
|
|
|
changed_when: false
|
2026-03-06 21:08:48 +00:00
|
|
|
|
|
|
|
|
- name: Remove policy routing
|
|
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
|
|
|
|
ip rule del fwmark {{ fwmark }} table {{ rt_table_name }} 2>/dev/null || true
|
2026-03-10 05:54:08 +00:00
|
|
|
ip rule del from {{ kind_network }} fwmark {{ fwmark }} table {{ rt_table_name }} 2>/dev/null || true
|
2026-03-06 21:08:48 +00:00
|
|
|
ip route del default table {{ rt_table_name }} 2>/dev/null || true
|
|
|
|
|
executable: /bin/bash
|
2026-03-07 01:47:58 +00:00
|
|
|
changed_when: false
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-07 22:28:21 +00:00
|
|
|
- name: Disable and remove ashburn-relay service
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: ashburn-relay.service
|
|
|
|
|
enabled: false
|
|
|
|
|
state: stopped
|
|
|
|
|
failed_when: false
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-07 22:28:21 +00:00
|
|
|
- name: Remove ashburn-relay files
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.file:
|
2026-03-07 22:28:21 +00:00
|
|
|
path: "{{ item }}"
|
2026-03-06 21:08:48 +00:00
|
|
|
state: absent
|
2026-03-07 22:28:21 +00:00
|
|
|
loop:
|
|
|
|
|
- /etc/systemd/system/ashburn-relay.service
|
|
|
|
|
- /usr/local/sbin/ashburn-relay-setup.sh
|
|
|
|
|
- /etc/network/if-up.d/ashburn-routing
|
2026-03-06 21:08:48 +00:00
|
|
|
|
|
|
|
|
- name: Rollback complete
|
|
|
|
|
ansible.builtin.debug:
|
2026-03-07 01:47:58 +00:00
|
|
|
msg: "Ashburn relay rules removed."
|
2026-03-06 21:08:48 +00:00
|
|
|
|
|
|
|
|
- name: End play after rollback
|
|
|
|
|
ansible.builtin.meta: end_play
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Pre-flight checks
|
|
|
|
|
# ------------------------------------------------------------------
|
2026-03-07 01:47:58 +00:00
|
|
|
- name: Check tunnel destination is reachable
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.command:
|
2026-03-07 01:47:58 +00:00
|
|
|
cmd: ping -c 1 -W 2 {{ tunnel_dst }}
|
|
|
|
|
register: tunnel_dst_ping
|
2026-03-06 21:08:48 +00:00
|
|
|
changed_when: false
|
2026-03-07 01:47:58 +00:00
|
|
|
failed_when: tunnel_dst_ping.rc != 0
|
|
|
|
|
tags: [preflight, outbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
|
|
|
|
- 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]
|
|
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Firewalld zones and policies (permanent, survives reboots)
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Docker's iptables FORWARD chain (priority filter) drops packets that
|
|
|
|
|
# don't match DOCKER-USER or DOCKER-FORWARD. Firewalld's nftables
|
|
|
|
|
# filter_FORWARD (priority filter+10) then checks zone-based policies.
|
|
|
|
|
# Without the docker zone + docker-to-relay policy, firewalld rejects
|
|
|
|
|
# outbound traffic from Docker bridges to gre-ashburn because neither
|
|
|
|
|
# interface is in a known zone.
|
|
|
|
|
- name: Add Docker bridges to docker zone
|
|
|
|
|
ansible.posix.firewalld:
|
|
|
|
|
zone: docker
|
|
|
|
|
interface: "{{ item }}"
|
|
|
|
|
permanent: true
|
|
|
|
|
state: enabled
|
|
|
|
|
loop: "{{ docker_bridges }}"
|
|
|
|
|
register: docker_zone_result
|
|
|
|
|
tags: [outbound, inbound]
|
|
|
|
|
|
|
|
|
|
- name: Add GRE tunnel to trusted zone
|
|
|
|
|
ansible.posix.firewalld:
|
|
|
|
|
zone: trusted
|
|
|
|
|
interface: "{{ tunnel_device }}"
|
|
|
|
|
permanent: true
|
|
|
|
|
state: enabled
|
|
|
|
|
register: trusted_zone_result
|
|
|
|
|
tags: [outbound, inbound]
|
|
|
|
|
|
|
|
|
|
- name: Check if docker-to-relay policy exists
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: firewall-cmd --info-policy=docker-to-relay
|
|
|
|
|
register: policy_check
|
2026-03-06 21:08:48 +00:00
|
|
|
changed_when: false
|
2026-03-10 05:54:08 +00:00
|
|
|
failed_when: false
|
|
|
|
|
tags: [outbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Create docker-to-relay forwarding policy
|
|
|
|
|
when: policy_check.rc != 0
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.shell:
|
2026-03-10 05:54:08 +00:00
|
|
|
cmd: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
firewall-cmd --permanent --new-policy=docker-to-relay
|
|
|
|
|
firewall-cmd --permanent --policy=docker-to-relay --set-target=ACCEPT
|
|
|
|
|
firewall-cmd --permanent --policy=docker-to-relay --add-ingress-zone=docker
|
|
|
|
|
firewall-cmd --permanent --policy=docker-to-relay --add-egress-zone=trusted
|
|
|
|
|
echo "policy created"
|
2026-03-06 21:08:48 +00:00
|
|
|
executable: /bin/bash
|
2026-03-10 05:54:08 +00:00
|
|
|
register: policy_result
|
|
|
|
|
changed_when: "'created' in policy_result.stdout"
|
|
|
|
|
tags: [outbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Reload firewalld
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: firewall-cmd --reload
|
|
|
|
|
when: >-
|
|
|
|
|
docker_zone_result.changed or
|
|
|
|
|
trusted_zone_result.changed or
|
|
|
|
|
(policy_result is defined and policy_result.changed)
|
|
|
|
|
changed_when: true
|
|
|
|
|
tags: [outbound, inbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-07 01:47:58 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# GRE tunnel setup (iproute2)
|
2026-03-07 01:47:58 +00:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
- name: Create GRE tunnel
|
|
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
2026-03-10 05:54:08 +00:00
|
|
|
set -euo pipefail
|
2026-03-07 01:47:58 +00:00
|
|
|
if ip tunnel show {{ tunnel_device }} 2>/dev/null; then
|
|
|
|
|
echo "tunnel already exists"
|
|
|
|
|
else
|
2026-03-10 05:54:08 +00:00
|
|
|
ip tunnel add {{ tunnel_device }} mode gre \
|
|
|
|
|
local {{ tunnel_src }} remote {{ tunnel_dst }} ttl 64
|
2026-03-07 01:47:58 +00:00
|
|
|
ip addr add {{ tunnel_local_ip }}/31 dev {{ tunnel_device }}
|
|
|
|
|
ip link set {{ tunnel_device }} up mtu 8972
|
|
|
|
|
echo "tunnel created"
|
|
|
|
|
fi
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: tunnel_result
|
|
|
|
|
changed_when: "'created' in tunnel_result.stdout"
|
|
|
|
|
tags: [outbound]
|
|
|
|
|
|
2026-03-06 21:08:48 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# Inbound: DNAT for 137.239.194.65 → kind node (iptables)
|
2026-03-06 21:08:48 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# These must be iptables rules inserted before Docker's ADDRTYPE LOCAL
|
|
|
|
|
# rule in nat PREROUTING. Firewalld forward-ports can't guarantee
|
|
|
|
|
# ordering relative to Docker's chains.
|
2026-03-06 21:08:48 +00:00
|
|
|
- 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
|
2026-03-10 05:54:08 +00:00
|
|
|
failed_when: >-
|
|
|
|
|
add_ip.rc != 0 and
|
|
|
|
|
'already assigned' not in add_ip.stderr and
|
|
|
|
|
'File exists' not in add_ip.stderr
|
2026-03-06 21:08:48 +00:00
|
|
|
tags: [inbound]
|
|
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Add DNAT rules (before Docker's chain)
|
2026-03-07 01:44:25 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
|
|
|
|
set -o pipefail
|
|
|
|
|
for rule in \
|
|
|
|
|
"-p udp -d {{ ashburn_ip }} --dport {{ gossip_port }} -j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }}" \
|
|
|
|
|
"-p tcp -d {{ ashburn_ip }} --dport {{ gossip_port }} -j DNAT --to-destination {{ kind_node_ip }}:{{ gossip_port }}" \
|
|
|
|
|
"-p udp -d {{ ashburn_ip }} --dport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} -j DNAT --to-destination {{ kind_node_ip }}" \
|
|
|
|
|
; do
|
|
|
|
|
if ! iptables -t nat -C PREROUTING $rule 2>/dev/null; then
|
|
|
|
|
iptables -t nat -I PREROUTING 1 $rule
|
|
|
|
|
echo "added: $rule"
|
|
|
|
|
else
|
|
|
|
|
echo "exists: $rule"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: dnat_result
|
|
|
|
|
changed_when: "'added' in dnat_result.stdout"
|
2026-03-06 21:08:48 +00:00
|
|
|
tags: [inbound]
|
|
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Allow DNAT'd traffic through Docker's FORWARD chain
|
2026-03-08 02:43:31 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
|
|
|
|
set -o pipefail
|
2026-03-10 05:54:08 +00:00
|
|
|
# Docker's iptables FORWARD (priority filter) drops NEW inbound
|
|
|
|
|
# traffic to bridge networks. DOCKER-USER is the only place to
|
|
|
|
|
# add ACCEPT rules that survive Docker daemon restarts.
|
2026-03-08 02:43:31 +00:00
|
|
|
for rule in \
|
|
|
|
|
"-p udp -d {{ kind_node_ip }} --dport {{ gossip_port }} -j ACCEPT" \
|
|
|
|
|
"-p tcp -d {{ kind_node_ip }} --dport {{ gossip_port }} -j ACCEPT" \
|
|
|
|
|
"-p udp -d {{ kind_node_ip }} --dport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} -j ACCEPT" \
|
|
|
|
|
; do
|
|
|
|
|
if ! iptables -C DOCKER-USER $rule 2>/dev/null; then
|
|
|
|
|
iptables -I DOCKER-USER 1 $rule
|
|
|
|
|
echo "added: $rule"
|
|
|
|
|
else
|
|
|
|
|
echo "exists: $rule"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: forward_result
|
|
|
|
|
changed_when: "'added' in forward_result.stdout"
|
|
|
|
|
tags: [inbound]
|
|
|
|
|
|
2026-03-06 21:08:48 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# Outbound: fwmark + SNAT + policy routing (iptables + iproute2)
|
2026-03-06 21:08:48 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# Mangle marks select which traffic gets policy-routed through the
|
|
|
|
|
# relay. Source-based routing (ip rule from 172.20.0.0/16) would be
|
|
|
|
|
# simpler but routes ALL Docker traffic through the tunnel, including
|
|
|
|
|
# DNS and health checks. Port-specific marks keep non-validator
|
|
|
|
|
# traffic on the default route.
|
2026-03-07 01:44:25 +00:00
|
|
|
- name: Mark outbound validator traffic (mangle PREROUTING)
|
|
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
|
|
|
|
set -o pipefail
|
|
|
|
|
for rule in \
|
|
|
|
|
"-p udp -s {{ kind_network }} --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }}" \
|
|
|
|
|
"-p udp -s {{ kind_network }} --sport {{ dynamic_port_range_start }}:{{ dynamic_port_range_end }} -j MARK --set-mark {{ fwmark }}" \
|
|
|
|
|
"-p tcp -s {{ kind_network }} --sport {{ gossip_port }} -j MARK --set-mark {{ fwmark }}" \
|
2026-03-07 23:31:18 +00:00
|
|
|
"-p tcp -s {{ kind_network }} --dport {{ gossip_port }} -j MARK --set-mark {{ fwmark }}" \
|
2026-03-07 01:44:25 +00:00
|
|
|
; do
|
|
|
|
|
if ! iptables -t mangle -C PREROUTING $rule 2>/dev/null; then
|
|
|
|
|
iptables -t mangle -A PREROUTING $rule
|
|
|
|
|
echo "added: $rule"
|
|
|
|
|
else
|
|
|
|
|
echo "exists: $rule"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: mangle_result
|
|
|
|
|
changed_when: "'added' in mangle_result.stdout"
|
2026-03-06 21:08:48 +00:00
|
|
|
tags: [outbound]
|
|
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: SNAT marked traffic to Ashburn IP
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
|
|
|
|
set -o pipefail
|
2026-03-10 05:54:08 +00:00
|
|
|
rule="-m mark --mark {{ fwmark }} -j SNAT --to-source {{ ashburn_ip }}"
|
|
|
|
|
if iptables -t nat -C POSTROUTING $rule 2>/dev/null; then
|
|
|
|
|
echo "exists"
|
2026-03-06 21:08:48 +00:00
|
|
|
else
|
2026-03-10 05:54:08 +00:00
|
|
|
iptables -t nat -I POSTROUTING 1 $rule
|
|
|
|
|
echo "added"
|
2026-03-06 21:08:48 +00:00
|
|
|
fi
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: snat_result
|
2026-03-10 05:54:08 +00:00
|
|
|
changed_when: "'added' in snat_result.stdout"
|
2026-03-06 21:08:48 +00:00
|
|
|
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: |
|
2026-03-07 22:28:21 +00:00
|
|
|
set -o pipefail
|
2026-03-06 21:08:48 +00:00
|
|
|
if ip rule show | grep -q 'fwmark 0x64 lookup ashburn'; then
|
2026-03-10 05:54:08 +00:00
|
|
|
echo "exists"
|
2026-03-06 21:08:48 +00:00
|
|
|
else
|
|
|
|
|
ip rule add fwmark {{ fwmark }} table {{ rt_table_name }}
|
2026-03-10 05:54:08 +00:00
|
|
|
echo "added"
|
2026-03-06 21:08:48 +00:00
|
|
|
fi
|
|
|
|
|
executable: /bin/bash
|
|
|
|
|
register: rule_result
|
|
|
|
|
changed_when: "'added' in rule_result.stdout"
|
|
|
|
|
tags: [outbound]
|
|
|
|
|
|
2026-03-07 01:47:58 +00:00
|
|
|
- name: Add default route via GRE tunnel in ashburn table
|
2026-03-10 05:54:08 +00:00
|
|
|
ansible.builtin.command:
|
2026-03-07 01:47:58 +00:00
|
|
|
cmd: ip route replace default via {{ tunnel_remote_ip }} dev {{ tunnel_device }} table {{ rt_table_name }}
|
2026-03-06 21:08:48 +00:00
|
|
|
changed_when: true
|
|
|
|
|
tags: [outbound]
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# Persistence (systemd service for non-firewalld state)
|
2026-03-06 21:08:48 +00:00
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
# Firewalld zones/policies persist natively (--permanent + reload).
|
|
|
|
|
# The systemd service restores: GRE tunnel, loopback IP, iptables
|
|
|
|
|
# rules (DNAT, DOCKER-USER, mangle, SNAT), and policy routing.
|
|
|
|
|
# Runs After=docker.service because Docker recreates its iptables
|
|
|
|
|
# chains on startup.
|
2026-03-07 22:28:21 +00:00
|
|
|
- name: Install ashburn-relay systemd service
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/systemd/system/ashburn-relay.service
|
|
|
|
|
mode: "0644"
|
|
|
|
|
content: |
|
|
|
|
|
[Unit]
|
2026-03-10 05:54:08 +00:00
|
|
|
Description=Ashburn relay (GRE tunnel, iptables, policy routing)
|
|
|
|
|
After=docker.service network-online.target firewalld.service
|
2026-03-07 22:28:21 +00:00
|
|
|
Wants=network-online.target
|
2026-03-10 05:54:08 +00:00
|
|
|
Requires=firewalld.service
|
2026-03-07 22:28:21 +00:00
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
ExecStart=/usr/local/sbin/ashburn-relay-setup.sh
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
register: relay_unit
|
2026-03-06 21:08:48 +00:00
|
|
|
tags: [inbound, outbound]
|
|
|
|
|
|
2026-03-07 22:28:21 +00:00
|
|
|
- name: Install ashburn-relay setup script
|
2026-03-07 01:47:58 +00:00
|
|
|
ansible.builtin.template:
|
2026-03-07 22:28:21 +00:00
|
|
|
src: files/ashburn-relay-setup.sh.j2
|
|
|
|
|
dest: /usr/local/sbin/ashburn-relay-setup.sh
|
|
|
|
|
mode: "0755"
|
|
|
|
|
register: relay_script
|
|
|
|
|
tags: [inbound, outbound]
|
|
|
|
|
|
|
|
|
|
- name: Reload systemd and enable ashburn-relay
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: ashburn-relay.service
|
|
|
|
|
daemon_reload: "{{ relay_unit.changed or relay_script.changed }}"
|
|
|
|
|
enabled: true
|
|
|
|
|
tags: [inbound, outbound]
|
|
|
|
|
|
|
|
|
|
- name: Remove stale if-up.d script
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /etc/network/if-up.d/ashburn-routing
|
|
|
|
|
state: absent
|
|
|
|
|
tags: [inbound, outbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Verification
|
|
|
|
|
# ------------------------------------------------------------------
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Verify firewalld zones
|
2026-03-07 01:47:58 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
2026-03-10 05:54:08 +00:00
|
|
|
echo "=== docker zone ==="
|
|
|
|
|
firewall-cmd --zone=docker --list-interfaces
|
|
|
|
|
echo "=== trusted zone ==="
|
|
|
|
|
firewall-cmd --zone=trusted --list-interfaces
|
|
|
|
|
echo "=== docker-to-relay policy ==="
|
|
|
|
|
firewall-cmd --info-policy=docker-to-relay
|
2026-03-06 21:08:48 +00:00
|
|
|
executable: /bin/bash
|
2026-03-10 05:54:08 +00:00
|
|
|
register: zone_info
|
2026-03-06 21:08:48 +00:00
|
|
|
changed_when: false
|
2026-03-10 05:54:08 +00:00
|
|
|
tags: [outbound, inbound]
|
2026-03-06 21:08:48 +00:00
|
|
|
|
2026-03-10 05:54:08 +00:00
|
|
|
- name: Verify tunnel and routing
|
2026-03-06 21:08:48 +00:00
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: |
|
2026-03-10 05:54:08 +00:00
|
|
|
echo "=== tunnel ==="
|
|
|
|
|
ip tunnel show {{ tunnel_device }}
|
|
|
|
|
ip addr show {{ tunnel_device }}
|
|
|
|
|
echo "=== ping tunnel peer ==="
|
|
|
|
|
ping -c 1 -W 2 {{ tunnel_remote_ip }} 2>&1 || echo "tunnel peer unreachable"
|
2026-03-06 21:08:48 +00:00
|
|
|
echo "=== ip rule ==="
|
|
|
|
|
ip rule show
|
2026-03-10 05:54:08 +00:00
|
|
|
echo "=== ashburn table ==="
|
2026-03-06 21:08:48 +00:00
|
|
|
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: Display verification
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg:
|
2026-03-10 05:54:08 +00:00
|
|
|
firewalld: "{{ zone_info.stdout_lines }}"
|
2026-03-06 21:08:48 +00:00
|
|
|
routing: "{{ routing_info.stdout_lines | default([]) }}"
|
|
|
|
|
tags: [inbound, outbound]
|
|
|
|
|
|
|
|
|
|
- name: Summary
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: |
|
|
|
|
|
=== Ashburn Relay Setup Complete ===
|
|
|
|
|
Ashburn IP: {{ ashburn_ip }} (on lo)
|
2026-03-07 01:47:58 +00:00
|
|
|
GRE tunnel: {{ tunnel_device }} ({{ tunnel_src }} → {{ tunnel_dst }})
|
|
|
|
|
link: {{ tunnel_local_ip }}/31 ↔ {{ tunnel_remote_ip }}/31
|
2026-03-10 05:54:08 +00:00
|
|
|
Firewalld: Docker bridges in 'docker' zone, {{ tunnel_device }} in 'trusted' zone
|
|
|
|
|
Policy: docker-to-relay (docker → trusted, ACCEPT)
|
|
|
|
|
Inbound DNAT: {{ ashburn_ip }}:8001,9000-9025 → {{ kind_node_ip }} (iptables)
|
|
|
|
|
Outbound SNAT: fwmark {{ fwmark }} → {{ ashburn_ip }} (iptables)
|
|
|
|
|
Policy route: fwmark {{ fwmark }} → table {{ rt_table_name }} → via {{ tunnel_remote_ip }}
|