Files
poc-frr-anycast/ansible/tests/render-check-cloud.sh
T
ayurishchevandClaude Sonnet 5 804fee4ffa Fix BGP route leak: mandatory outbound prefix-filter on every neighbor
Prod-инцидент: FRR реэкспортировал входящий 10.0.0.0/8 обратно
eBGP-соседям. Причина — ни у одного соседа не было outbound-фильтра
(no bgp ebgp-requires-policy отключал защиту FRR, единственный
route-map RM-PREPEND-OUT permit-all фильтрации не делал).

frr.conf.j2: добавлен ip prefix-list PL-ANYCAST-OUT (permit только
anycast_prefix) и route-map RM-OUT / RM-OUT-PREPEND (фильтр, либо
фильтр+prepend), оба с явным deny в конце. Назначены outbound на
ВСЕХ соседей без исключения — наружу теперь уходит исключительно
анонсируемый anycast-префикс, что бы ни попало в локальную
BGP-таблицу от других соседей.

Уязвимость структурная (в общей роли), поэтому фикс задел и
площадку A — golden-файлы frr/a{1,2,3}/frr.conf синхронизированы
с новым рендером (намеренно, не регресс). no bgp ebgp-requires-policy
сознательно не тронут — его включение требует ещё inbound-policy на
каждого соседа, отдельная задача.

render-check.sh 6/6, render-check-cloud.sh 84/84 (включая nft -c).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bc3HkMiDwooSyLBLoMKNyF
2026-09-10 17:57:51 +03:00

75 lines
3.5 KiB
Bash
Executable File

#!/bin/sh
# Офлайн-проверка шаблонов роли frr_anycast для окружения site_cloud (4 узла,
# 2 канала, as-path prepend только на резервном, sNAT). Golden-файлов для
# этого окружения нет (оно новое, живых ВМ нет) — проверяем grep'ом ожидаемые
# инварианты вместо byte-diff.
set -e
cd "$(dirname "$0")/.." # -> ansible/
ENV_FILE="${SITE_ENV_FILE:-site-cloud.env}"
if [ ! -f "$ENV_FILE" ]; then
echo "Не найден файл переменных окружения: $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
. "./$ENV_FILE"
set +a
OUT_DIR="$(mktemp -d)"
trap 'rm -rf "$OUT_DIR"' EXIT
ansible-playbook -i inventory/site-cloud.yml tests/render-test-cloud.yml -e "render_out_dir=$OUT_DIR" 2>&1 | cat
STATUS=0
check() {
# check <label> <file> <pattern> <expected_count>
label="$1"; file="$2"; pattern="$3"; expected="$4"
actual="$(grep -Fc -- "$pattern" "$file" || true)"
if [ "$actual" = "$expected" ]; then
echo "OK $label ($file): '$pattern' x$expected"
else
echo "FAIL $label ($file): ожидалось '$pattern' x$expected, найдено x$actual" >&2
STATUS=1
fi
}
for n in vr01 vr02 vr03 vr04; do
f="$OUT_DIR/$n/frr.conf"
check "$n router bgp" "$f" "router bgp 65200" 1
check "$n network anycast" "$f" " network 172.31.255.10/32" 1
check "$n neighbor main1" "$f" " neighbor $MAIN1_NEIGHBOR_IP remote-as 65104" 1
check "$n neighbor main2" "$f" " neighbor $MAIN2_NEIGHBOR_IP remote-as 65104" 1
check "$n neighbor backup1" "$f" " neighbor $BACKUP1_NEIGHBOR_IP remote-as 65103" 1
check "$n neighbor backup2" "$f" " neighbor $BACKUP2_NEIGHBOR_IP remote-as 65103" 1
check "$n prefix-list anycast only" "$f" "ip prefix-list PL-ANYCAST-OUT seq 5 permit 172.31.255.10/32" 1
check "$n RM-OUT filters to anycast" "$f" "match ip address prefix-list PL-ANYCAST-OUT" 2 # RM-OUT + RM-OUT-PREPEND
check "$n RM-OUT implicit deny" "$f" "route-map RM-OUT deny 20" 1
check "$n RM-OUT-PREPEND defined" "$f" "route-map RM-OUT-PREPEND permit 10" 1
check "$n prepend value" "$f" "set as-path prepend 65200 65200 65200" 1
check "$n filter+prepend on backup1" "$f" "neighbor $BACKUP1_NEIGHBOR_IP route-map RM-OUT-PREPEND out" 1
check "$n filter+prepend on backup2" "$f" "neighbor $BACKUP2_NEIGHBOR_IP route-map RM-OUT-PREPEND out" 1
check "$n filter-only on main1" "$f" "neighbor $MAIN1_NEIGHBOR_IP route-map RM-OUT out" 1
check "$n filter-only on main2" "$f" "neighbor $MAIN2_NEIGHBOR_IP route-map RM-OUT out" 1
check "$n NO prepend route-map on main1" "$f" "neighbor $MAIN1_NEIGHBOR_IP route-map RM-OUT-PREPEND out" 0
check "$n NO prepend route-map on main2" "$f" "neighbor $MAIN2_NEIGHBOR_IP route-map RM-OUT-PREPEND out" 0
nft="$OUT_DIR/$n/nftables.conf"
check "$n nftables masquerade" "$nft" "masquerade" 1
check "$n nftables oifname" "$nft" "oifname \"$EXT_IFACE\"" 1
check "$n nftables dnat" "$nft" "ip daddr 172.31.255.10 dnat to $DNAT_TARGET_IP" 1
if command -v nft >/dev/null 2>&1; then
if nft -c -f "$nft" >/tmp/render-check-cloud-nft.err 2>&1; then
echo "OK $n nftables.conf: синтаксис валиден (nft -c)"
else
echo "FAIL $n nftables.conf: nft -c провалился:" >&2
cat /tmp/render-check-cloud-nft.err >&2
STATUS=1
fi
fi
done
exit "$STATUS"