Files
AWG2_harness/docs/ACCEPTANCE_CHECKLIST.md
T
founder 16214f3c66 AmneziaWG 3.0 readiness: centralize wg CLI contract, drop dead host-side fallback
Peer discovery is now the single owner of how wg is invoked and parsed.

wg.rs:
- resolve the binary through ordered amnezia.wg_binaries (default [awg, wg]);
  awg is the real binary in the official image and wg is a symlink to it, so awg is
  tried first and wg stays for plain WireGuard containers. No probe call: resolution
  is a property of the command being run anyway.
- drop the nsenter fallback. AmneziaWG is userspace and its UAPI socket lives under
  RUNSTATEDIR/amneziawg/ in the container mount namespace, while host wireguard-tools
  looks under /var/run/wireguard/. nsenter -n enters only the network namespace, so
  that path could never return peers.
- classify dump lines structurally instead of by column count. The AWG interface line
  is longer than a peer line (14 columns on AWG 2.0, 28 on AWG 3.0) and its
  handshake/rx/tx positions hold junk-packet sizes that parse as integers, so only the
  allowed-ips column discriminates. Column 1 of an interface line is the device private
  key, which the previous heuristic rejected only incidentally.
- fix allowed-ips parsing: `show all allowed-ips` separates a peer's addresses with
  spaces, not commas, so peers with more than one allowed IP were shaped on their first
  address only and the rest ran unlimited.
- collapse the three duplicated /32-in-CIDR filters into one tokenizer shared by the
  allowed-ips, dump and pretty-print parsers.

config.rs:
- add amnezia.wg_binaries with serde default, validated as bare command names
  (non-empty, no whitespace, no path separators) since they execute inside the
  container. Existing deployed configs keep parsing unchanged.
- remove host wg from the required-tool preflight. It gated daemon startup on
  wireguard-tools for a code path that produced nothing.

status.rs:
- discover peers once instead of twice per status call and degrade gracefully like the
  neighbouring sections instead of aborting the whole output.

Tests: real AWG 3.0 / AWG 2.0 / plain WireGuard interface-line fixtures, AWG 3.0 range
keepalive, space-separated allowed-ips, binary resolution order, and a test that parses
and validates the shipped config.example.toml.
2026-08-06 15:32:26 +05:00

16 KiB
Raw Blame History

Acceptance checklist перед production-выкладкой

A. Базовая установка

  • Хост — Debian 13 amd64.
  • Сервер выделенный, без соседних сервисов.
  • AmneziaWG/AWG 2.0 или AmneziaWG/AWG 1.5 установлена официальным способом.
  • Контейнер amnezia-awg2 или amnezia-awg запущен (amnezia-awg3 — резерв на будущее, в AmneziaVPN 5.0 такого типа контейнера ещё нет).
  • docker ps видит контейнер.
  • Можно получить PID контейнера.
  • /proc/<pid>/ns/net существует.
  • На хосте доступны ip, tc, nsenter, modprobe. Хостовый wg не требуется.
  • awg2-harness validate-config проходит без ошибок.

B. Peer discovery

  • awg2-harness status показывает найденный контейнер.
  • awg2-harness status показывает PID/netns контейнера.
  • awg2-harness status показывает найденный интерфейс.
  • Количество peers совпадает с docker exec "$C" awg show all allowed-ips.
  • Peer с несколькими allowed IP даёт отдельный класс на каждый адрес (в этой команде адреса разделены пробелом).
  • Offline peers с allowed IP тоже попадают в список shaping.
  • IP вне amnezia.peer_ipv4_cidr не попадают в traffic shaping.
  • В секции WireGuard clients вывода status нет строки, соответствующей самому интерфейсу (столбец Public key не содержит приватный ключ устройства).
  • docker exec "$C" readlink -f /usr/bin/wg возвращает /usr/bin/awg.

C. Traffic topology contract

  • awg2-harness apply-limits успешно завершается.
  • На AWG/WG-интерфейсе есть root qdisc 1: htb.
  • На AWG/WG-интерфейсе есть root class 1:1 с traffic.root_rate.
  • Для каждого peer есть class 1:<id> с traffic.peer_limit.
  • Для каждого peer есть fq_codel под 1:<id>.
  • Для каждого peer есть egress filter dst peer_ip -> 1:<id>.
  • Есть ingress qdisc ffff: на AWG/WG-интерфейсе.
  • Есть ingress redirect через mirred ... redirect dev ifb0.
  • ifb0 существует и находится в UP state.
  • На ifb0 есть root qdisc 2: htb.
  • На ifb0 есть root class 2:1 с traffic.root_rate.
  • Для каждого peer есть class 2:<id> с traffic.peer_limit.
  • Для каждого peer есть fq_codel под 2:<id>.
  • Для каждого peer есть ingress filter src peer_ip -> 2:<id>.
  • Class ids начинаются с traffic.class_start_id, default 10.
  • Peer IP → class id mapping использует стабильный лексикографический контракт: IP сортируются как строки, не numeric IPv4.

D. Idempotency and drift recovery

  • Повторный apply-limits не создаёт дубли.
  • Повторный apply-limits не меняет healthy state без причины.
  • Если удалить root qdisc на AWG/WG-интерфейсе, daemon восстанавливает topology.
  • Если удалить ingress qdisc, daemon восстанавливает topology.
  • Если удалить ingress redirect filter (parent ffff:), drift детектируется и topology восстанавливается.
  • Если удалить ifb0, daemon восстанавливает topology.
  • Если сделать ifb0 DOWN, drift детектируется и topology восстанавливается.
  • Если удалить один peer filter, daemon восстанавливает topology.
  • Если изменить rate/ceil peer class, drift детектируется и topology восстанавливается.
  • Если добавить stale peer class/filter, drift детектируется и topology восстанавливается.
  • После удаления peer устаревшие class/filter исчезают через rebuild.
  • После добавления peer появляются новые class/filter.

E. Auto-healing lifecycle

  • docker restart amnezia-awg2 → лимиты восстанавливаются автоматически.
  • systemctl restart docker → после возвращения контейнера лимиты восстанавливаются автоматически.
  • systemctl restart awg2-harness → сервис сам находит контейнер и применяет/проверяет лимиты.
  • Reboot хоста → сервис стартует и восстанавливает лимиты.
  • Изменение имени интерфейса на допустимое fallback-имя не ломает detector.
  • Если контейнер временно отсутствует, daemon логирует ожидание и продолжает работу.

F. Status and observability

  • status показывает desired peer limit из конфига, а не hardcoded 70.
  • status показывает egress state.
  • status показывает ingress/ifb state.
  • status показывает WireGuard clients (online/offline, latest handshake, RX/TX, endpoint).
  • status показывает missing/stale peer rules.
  • status явно показывает partial/drift state.
  • status показывает tc filter counters по peer или filter statistics: unavailable.
  • Логи доступны через journalctl -u awg2-harness.
  • Ошибка отсутствия tc/ip/wg логируется явно.
  • Последний reconcile result виден в status или journal.
  • status показывает последние события reconcile/firewall из /run/awg2-harness/events.log.

G. Reboot scheduler

  • Если reboot.enabled = false, сервис не планирует reboot.
  • Если reboot.enabled = true, в journal видна запланированная перезагрузка.
  • Время попадает в окно window_start_utc–window_end_utc.
  • Вызов reboot выполняется через systemctl --no-block reboot.

H. Swap

  • ensure-swap создаёт /swapfile размером 2048 MB.
  • Права /swapfile — 0600.
  • Если до запуска был активен другой swap, после ensure-swap он выключен.
  • После ensure-swap активен ровно один swap: /swapfile.
  • Все старые swap entries удалены из /etc/fstab.
  • Для block-device swap выполняется только swapoff и удаление fstab entry; block device не удаляется.
  • vm.swappiness = 1 применён.
  • Повторный ensure-swap ничего не пересоздаёт и не добавляет дублей.

H2. Sysctl network tuning

  • awg2-harness apply-network-tuning успешно завершается.
  • awg2-harness inspect-network-tuning показывает enabled: true и drift: no.
  • Создаётся только /etc/sysctl.d/90-awg2-harness-vpn.conf.
  • В managed-файле есть marker Managed by awg2-harness sysctl_tuning.
  • В managed-файле отсутствует udp_wmem_min.
  • Runtime apply меняет только drifted keys (sysctl -w), без sysctl --system.
  • При tcp_bbr = auto и недоступном BBR сервис не падает, в статусе есть warning.
  • При tcp_bbr = require и недоступном BBR команда/старт завершается с ошибкой.
  • При enabled=false managed-файл удаляется только если содержит managed marker.

I. Anti-torrent nftables guard

  • Если anti_torrent.enabled = true, в Amnezia netns есть table inet awg2_harness.
  • Есть chain forward_antitorrent с hook forward и policy accept.
  • Правило ограничивает ip saddr <peer_ipv4_cidr>, default 10.8.1.0/24.
  • Лимит считается per source VPN IP через meter/set, а не глобально по всей подсети.
  • Порог совпадает с anti_torrent.max_connections_per_peer; текущий пример config использует 150, более строгий вариант — 100.
  • Drop counter виден в status.
  • На first-run без table awg2-harness apply-firewall создаёт table inet awg2_harness, а не падает.
  • При превышении порога новые flows дропаются, established flows ниже порога не ломаются.
  • После restart контейнера nftables rule пересоздаётся в новом netns.
  • При anti_torrent.enabled = false удаляется/отключается только AWG2-owned nftables state; Docker/Amnezia rules не flush-ятся.
  • status показывает nf_conntrack_count / nf_conntrack_max, если sysctl доступны.

J. Rollback

  • Известно, как остановить и отключить awg2-harness.service и awg2-reboot-scheduler.service.
  • Известно, как удалить только AWG2-owned host firewall state (inet awg2_host_firewall).
  • Известно, как удалить только AWG2-owned anti-torrent state (inet awg2_harness) в выбранном namespace.
  • Известно, как удалить AWG2-owned tc topology (root, ingress, ifb0) в netns Amnezia.
  • При необходимости rollback включает удаление пакета awg2-harness после остановки сервисов.
  • Rollback проверен на тестовом сервере.

K. Packaging / release

  • Package собирается на отдельной Debian 13 amd64 build-machine.
  • Production host не требует Rust/cargo/source tree.
  • ./tools/build.sh --package создаёт dist/awg2-harness_<version>_amd64.deb.
  • dist/SHA256SUMS валидирует checksum пакета.
  • Пакет ставит бинарь в /usr/sbin/awg2-harness.
  • Пакет ставит unit в /lib/systemd/system/awg2-harness.service.
  • Пакет не устанавливает /etc/awg2-harness/config.toml автоматически.
  • Пакет не включает/не стартует сервис автоматически.
  • Пакет не имеет Depends/Recommends на docker.io; установка Amnezia/Docker внешняя.
  • apt install ./awg2-harness_<version>_amd64.deb не устанавливает и не заменяет Docker.
  • apt install ./awg2-harness_<version>_amd64.deb проходит на clean Debian 13 Amnezia host.
  • После ручной установки config awg2-harness validate-config проходит.
  • Сервис стартует только после явного systemctl enable --now awg2-harness.service.

K2. Documentation / install UX

  • README не утверждает, что AmneziaVPN ожидает public key.
  • Документация явно разделяет private key для AmneziaVPN и public key для authorized_keys.
  • Есть пример заполнения AmneziaVPN fields: Server IP address [:port], SSH Username, Password or SSH private key.
  • Описаны Windows пути %USERPROFILE%\.ssh\amnezia_key и %USERPROFILE%\.ssh\amnezia_key.pub.
  • Описан runtime config bootstrap из /usr/share/doc/awg2-harness/repo/config.example.toml.
  • Clean install подтверждает, что /etc/awg2-harness/config.toml не создаётся автоматически.

K3. AmneziaDNS

  • AmneziaDNS установлен из приложения AmneziaVPN.
  • Контейнер amnezia-dns в состоянии healthy.
  • docker inspect amnezia-dns показывает "53/tcp":null и "53/udp":null.
  • amnezia-dns и активный AWG-контейнер (amnezia-awg2, amnezia-awg3 или amnezia-awg) подключены к одной Amnezia DNS Docker-сети.
  • Публичный DNS-запрос к <server-public-ip>:53 не резолвит домены; допустимы connection refused, timeout или no servers could be reached.
  • DNS-резолвинг у VPN-клиента работает при активном подключении.
  • AWG2 host firewall не открывает 53/tcp и 53/udp.

L. Host firewall (ports/SSH safety)

  • awg2-harness validate-config падает, если отсутствует секция [host_firewall].
  • apply-firewall не блокирует SSH на host_firewall.ssh_port (default 2323).
  • После apply-firewall порт host_firewall.amnezia_port (default 443) доступен по всем протоколам из host_firewall.amnezia_protocols.
  • Если эксплуатационно требуется закрыть legacy SSH 22, это явно подтверждено проверкой доступа на ssh_port перед закрытием 22.
  • При host_firewall.enabled = false удаляется только таблица inet awg2_host_firewall.
  • Повторный apply-firewall идемпотентен и не вносит лишних изменений в healthy state.
  • При невалидном host_firewall.amnezia_protocols (icmp, foo, дубли) validate-config завершается ошибкой.
  • После docker restart anti-torrent правила продолжают восстанавливаться корректно и не ломаются host firewall логикой.
  • Проверено, где реально проходит VPN-трафик в вашей Docker-схеме (INPUT vs FORWARD) и это отражено в эксплуатационной заметке.
  • Для release-кандидата зафиксирован итог проверки INPUT vs FORWARD на реальном сервере (команды/логи/вывод) и подтверждено, что текущие правила firewall покрывают реальный путь трафика.
  • status показывает блок Docker/Amnezia port path с полями docker_userland_proxy, published_udp_port и note про Docker-managed FORWARD.
  • status показывает поля host_forward_policy_drop и docker_forward_rules_present в блоке Docker/Amnezia port path.
  • Если docker_userland_proxy=false и FORWARD policy DROP, при отсутствии явных Docker accept-признаков status выводит warning, но awg2-harness не изменяет FORWARD автоматически.
  • Сверено соответствие: config host_firewall.amnezia_port ↔ опубликованный Docker UDP host port ↔ docker exec amnezia-awg2 awg show (listening port).
  • При нескольких UDP bindings без однозначного соответствия host_firewall.amnezia_port диагностика published_udp_port показывает unknown (ambiguous), а не произвольный первый порт.
  • Для диагностики FORWARD используется best-effort проверка: iptables -S FORWARD и fallback через nft list ruleset; status не падает при недоступности одной из команд.