--- # Вывод ZeroTier с ru-vps и чистка оставшихся от него правил UFW. # # ПОЧЕМУ ЭТО БЕЗОПАСНО # Проверено 2026-09-02: ZT-интерфейса на хосте нет, маршрутов через него нет, # на ZT-адресах никто не слушает. Контейнер `zerotier` работает, но подключён # в никуда (потому и unhealthy). `ssh-zt22.service` в состоянии failed: его # sshd настроен на ListenAddress 10.122.62.65, которого больше не существует. # # Из Ansible ZeroTier выведен ещё в июле 2026 — на хосте остался хвост. # # ЧТО НЕ УДАЛЯЕТСЯ # Каталог /opt/services/ru-vps/zerotier, данные /opt/data/zerotier и файл # /etc/ssh/sshd_config_zt22 остаются на месте. `docker compose down` идёт БЕЗ # -v, то есть identity узла ZeroTier сохраняется. Удаление этих файлов — # отдельный осознанный шаг оператора после того, как всё устоялось. # # make zerotier-decommission CONFIRM=1 - name: Decommission ZeroTier on ru-vps hosts: ru-vps gather_facts: false vars: zt_dir: /opt/services/ru-vps/zerotier zt_interface: zt6q3dmi2d zt_legacy_cidr: 10.122.62.0/24 # Правила UFW для сервисов, которые на хосте ничего не слушают (проверено # через ss): squid не запущен, danted слушает 1081 а не 1080, на 993 и 7892 # слушателей нет. Это НЕ наследие ZeroTier, поэтому по умолчанию не трогаем. zt_cleanup_unrelated_stale_rules: false tasks: # --- Предполётные проверки --------------------------------------------- - name: List the network interfaces ansible.builtin.command: ip -br addr show register: zt_ifaces changed_when: false check_mode: false - name: Read the current firewall rules ansible.builtin.command: ufw status register: zt_ufw changed_when: false check_mode: false - name: Refuse to run while a ZeroTier interface is up ansible.builtin.assert: that: - zt_interface not in zt_ifaces.stdout fail_msg: >- Интерфейс {{ zt_interface }} присутствует на хосте — значит ZeroTier снова используется. Плейбук рассчитан на вывод мёртвого стека и отказывается работать: сначала подтверди, что сеть больше не нужна. # Управляющая сессия идёт по 3422/tcp. Если глобального разрешения на этот # порт нет, чистка правил может оборвать доступ — лучше не начинать. - name: Require the management SSH rule to survive the cleanup ansible.builtin.assert: that: - "'3422/tcp' in zt_ufw.stdout" fail_msg: >- В UFW нет правила для 3422/tcp. Прерываюсь, чтобы не остаться без управляющего доступа. # --- Контейнер ---------------------------------------------------------- - name: Check whether the ZeroTier stack is present ansible.builtin.stat: path: "{{ zt_dir }}/docker-compose.yml" register: zt_compose - name: Stop and remove the ZeroTier stack ansible.builtin.command: cmd: /usr/bin/docker compose -f {{ zt_dir }}/docker-compose.yml down chdir: "{{ zt_dir }}" register: zt_down when: zt_compose.stat.exists changed_when: "'Removing' in zt_down.stderr or 'Stopping' in zt_down.stderr" # --- sshd на ZeroTier --------------------------------------------------- - name: Disable the ZeroTier-only sshd unit ansible.builtin.systemd: name: ssh-zt22.service state: stopped enabled: false failed_when: false # `stop` не снимает состояние failed, и юнит навсегда остался бы в секции # FAILED SYSTEMD UNITS отчёта `make status`. homelab-pve-routes.service — # фантом той же эпохи: файла юнита уже нет, а запись о падении осталась. - name: List the units currently in a failed state ansible.builtin.command: systemctl list-units --state=failed --no-legend --plain --no-pager register: zt_failed changed_when: false check_mode: false - name: Clear the leftover failed state of the ZeroTier-era units ansible.builtin.command: "systemctl reset-failed {{ item }}" loop: - ssh-zt22.service - homelab-pve-routes.service # Без этого условия reset-failed рапортует changed на каждом прогоне: # его код возврата нулевой и когда сбрасывать нечего. when: item in zt_failed.stdout register: zt_reset changed_when: zt_reset.rc == 0 failed_when: false # --- UFW ---------------------------------------------------------------- - name: Remove the ZeroTier interface rules community.general.ufw: rule: allow direction: "{{ item }}" interface: "{{ zt_interface }}" delete: true loop: [in, out] - name: Remove the ZeroTier port rules community.general.ufw: rule: "{{ item.rule }}" port: "{{ item.port }}" proto: "{{ item.proto | default('any') }}" interface: "{{ item.interface | default(omit) }}" direction: "{{ 'in' if item.interface is defined else omit }}" delete: true loop: - {rule: allow, port: "9993", proto: udp} # ZeroTier UDP - {rule: deny, port: "9001"} # ZeroTier controller - {rule: allow, port: "9001", interface: "zt+"} # он же, но с ZT-сетей loop_control: label: "{{ item.rule }} {{ item.port }}{{ '/' ~ item.proto if item.proto is defined else '' }}" - name: Remove rules scoped to the retired ZeroTier subnet community.general.ufw: rule: allow port: "{{ item }}" proto: tcp src: "{{ zt_legacy_cidr }}" delete: true loop: ["22", "3422"] - name: Remove stale rules for services that no longer listen community.general.ufw: rule: allow port: "{{ item.port }}" proto: tcp delete: true loop: - {port: "3128"} # squid: стек есть, контейнер не запущен - {port: "1080"} # danted слушает 1081, не 1080 - {port: "993"} # слушателей нет - {port: "7892"} # слушателей нет loop_control: label: "{{ item.port }}/tcp" when: zt_cleanup_unrelated_stale_rules | bool # --- Проверка ----------------------------------------------------------- - name: List the running containers after the cleanup ansible.builtin.command: docker ps --format {{ '{{' }}.Names{{ '}}' }} register: zt_after_ps changed_when: false check_mode: false - name: Read the firewall after the cleanup ansible.builtin.command: ufw status register: zt_after_ufw changed_when: false check_mode: false - name: Confirm ZeroTier is gone and management access survived ansible.builtin.assert: that: - "'zerotier' not in zt_after_ps.stdout_lines" - zt_interface not in zt_after_ufw.stdout - "'3422/tcp' in zt_after_ufw.stdout" success_msg: ZeroTier выведен, правило для 3422/tcp на месте. # В --check контейнер не останавливается и правила не удаляются, поэтому # проверка результата заведомо не прошла бы. Diff'ы задач выше достоверны. when: not ansible_check_mode - name: Show the resulting firewall ansible.builtin.debug: var: zt_after_ufw.stdout_lines