Мёртвый переключатель для редактирования брандмауэра

Самая страшная строка в автоматизации домашней лаборатории — та, которая редактирует правило брандмауэра на маршрутизаторе, к которому вы подключены по SSH.

Вот как Claude Code и я всё равно их редактируем.


Страх

Мне нужно было изменить правило UDM Pro под названием “Block inter-VLAN traffic” с accept на drop. Правило было неправильно настроено долгое время — установлено в accept, что замыкало все разрешения выше по потоку — и закрыть его обратно было основной целью. Оркестратор, выполнявший изменение, был VM NixOS в моей домашней сети. Если переключение прервало бы путь, который использовала та VM (или мой собственный SSH), я оказался бы заблокирован на собственном маршрутизаторе без консольного резерва.

Решение не в том, чтобы “быть осторожным”. Решение в том, чтобы сделать опасное изменение с автоматическим откатом, если я не подтверждаю, что всё в порядке.

Этот шаблон вам уже знаком. Измените разрешение монитора в macOS или Windows, и система применит его на пятнадцать секунд с обратным отсчётом в диалоге “Сохранить эти параметры экрана?”. Если экран почернел, вы не можете ничего нажать — и в этом весь смысл. Результат по умолчанию — откат. Подтверждение — по желанию.

Именно это нам и нужно при переключении брандмауэра на маршрутизаторе, правила которого мы собираемся изменить.


Трюк

udm_set_firewall_rules.py --flip-with-revert 300 --apply

Читать как: “переключить правило, но автоматически откатить через 300 секунд, если я не скажу иначе.”

Что происходит по порядку:

  1. Снимок. GET текущего правила и сохранение полного JSON-payload на UDM в /root/.udm-flip-revert/payload-<id>.json.
  2. Подготовка отката. Рядом с payload записывается небольшой shell-скрипт, который снова PUT-ит снимок в UDM API.
  3. Планирование отката. systemd-run --on-active=300s --unit=udm-flip-revert-<id> ставит скрипт в очередь как временный таймер.
  4. Переключение. PUT нового правила (action: drop).

Теперь у вас есть 5-минутное окно для проверки:

✓ flipped 'Block inter-VLAN traffic' to action='drop'
⏱  auto-revert scheduled (~300s)

TO KEEP THE FLIP:    ssh udm 'systemctl stop udm-flip-revert-66c1d…timer'
TO ROLL BACK NOW:    ssh udm 'systemctl stop …timer; bash /root/.udm-flip-revert/revert-….sh'
WAIT IT OUT:         do nothing — timer will revert in ~300s

Три исхода:

Третий случай — вот ради чего всё это существует.


Почему systemd-run, а не at(1)

UDM Pro (Debian 11) не поставляется с atd. Зато там есть systemd. systemd-run --on-active=Ns создаёт временный таймер, который срабатывает один раз и самоудаляется — это именно форма “сделай это через N секунд и исчезни”.

ssh_udm(
    f"systemd-run --on-active={seconds} --unit={unit} --collect "
    f"--quiet /bin/bash {script_file}"
)

--collect — ключевой флаг: без него юнит зависает в состоянии failed/inactive и засоряет systemctl.


Оговорка

Временные юниты systemd живут в /run, который является tmpfs. Если UDM перезагрузится в пределах окна отката, таймер исчезнет — и переключение останется применённым без присмотра мёртвого переключателя.

Контринтуитивно, но реально: держите окно коротким. 5-минутное окно с небольшим риском перезагрузки безопаснее, чем 1-часовое окно, при котором сбой ИБП на 12-й минуте оставит вас с постоянной блокировкой. Достаточно долгое для проверки, достаточно короткое, чтобы “забыл ли я про ожидающий откат?” никогда не становилось вопросом.


Вывод

Паттерн мёртвого переключателя обобщается за пределы правил брандмауэра UDM. В любом месте, где вы собираетесь применить изменение, которое может оборвать путь, через который вы его применяли — таблицы маршрутизации, sshd_config, nftables, BGP — работает та же форма:

  1. Зафиксировать текущее состояние
  2. Подготовить скрипт, который его восстанавливает
  3. Запланировать запуск скрипта через N секунд
  4. Применить изменение

Если вы всё ещё здесь через N+ε секунд и всё выглядит нормально, отмените таймер. Если нет, таймер отменит вас.

Мне лучше спится с этим в наборе инструментов.