Files
AWG2_harness/docs/CONFIGURATION.md
T
founder a895295701 fix(swap): never touch an unowned swapfile, activate before deactivating [P0]
Two more release blockers in the swap module.

1. Foreign files could be destroyed.

   swap.path was validated against a blacklist of exact strings. /etc/../etc/fstab is not
   string-equal to /etc/fstab but resolves to it, and nothing normalized the path or
   checked the parent chain for symlinks. ensure_desired_swapfile then removed any
   existing regular file whose size did not match the configured one, so a mistyped or
   hostile path meant deleting /etc/passwd, an application config, or any other regular
   file. disable_foreign_swaps additionally deleted foreign swapfiles outright.

   Ownership is now explicit. A sidecar marker <swap.path>.awg2-harness-owned sits next to
   the managed swapfile, and inspect_swapfile_ownership classifies the path as Missing,
   ManagedValid, ManagedWrongSize or Foreign. Foreign is always a hard error - the file is
   never resized, rewritten or removed. Foreign active swaps are now only swapped off;
   deleting an administrator's swapfile is not this package's call, and swapoff plus the
   fstab rewrite already produce the exclusive runtime state.

   Path validation is split in two. The lexical half rejects relative paths, '.' and '..'
   components, whitespace, control characters and backslashes, the system path list, and
   anything under /proc, /sys or /dev; it touches no filesystem, so validate-config stays
   side-effect free on a host where the file does not exist yet. It runs after '..' is
   rejected, so the string is already normalized and the blacklist cannot be bypassed. The
   filesystem half runs just before use and refuses a symlink anywhere in the parent chain,
   not only at the final component.

   One deliberate deviation from the audit: arbitrary absolute paths stay allowed rather
   than being restricted to a fixed managed location. Pinning the path would break
   deployments that already configured their own, and the ownership marker is the guard
   that actually prevents data loss - a wrong path can now only fail, never destroy.

2. The migration order left the host swapless on failure.

   ensure_swap ran disable_foreign_swaps first, then created the swapfile, then activated
   it. Any failure in fallocate, dd, mkswap or swapon after that first step left the host
   with no swap at all - on a box provisioned with swap precisely because it is short on
   memory.

   The sequence is now a transaction: preflight, prepare the managed swapfile, swapon,
   verify it actually appears in the active list, and only then swapoff the others,
   rewrite fstab, apply swappiness and verify. Nothing destructive happens until the
   replacement is confirmed live. The one unavoidable gap is deactivating our own swap to
   resize it, which cannot be done in place; foreign swap stays up throughout.

   chmod 0600 now also happens before mkswap, so the file is never briefly world-readable
   while it already holds swap structure.

config.rs no longer carries its own copy of the path blacklist and calls the shared
validator.

Twelve tests: the four ownership states, refusal to recreate a foreign file with its
content asserted intact afterwards, '..' traversal, dangerous and virtual paths,
whitespace and control characters, relative and directory paths, a missing parent
directory, an accepted chain, and a symlinked parent (unix only).

Docs updated: IMPLEMENTATION_SPEC section 17 gained the ownership table, the ordered
transaction and the path rules; CONFIGURATION and the acceptance checklist gained the
operator-facing constraints and nine checks.

cargo test --workspace: 136 + 4 passed. clippy -D warnings clean.
2026-08-06 21:07:55 +05:00

15 KiB
Raw Blame History

Конфигурация AWG2 Harness

AWG2 Harness разрабатывался для self-hosted AmneziaWG/AWG 2.0 и совместимого legacy-сценария AmneziaWG/AWG 1.5. Конфиг живёт в /etc/awg2-harness/config.toml. Он общий для всего пакета и должен содержать только эксплуатационно значимые параметры.

Создание runtime config

Пакет намеренно не создаёт /etc/awg2-harness/config.toml автоматически. Это снижает риск случайно применить firewall с неверным SSH-портом или неверным Amnezia-портом.

После установки .deb создайте runtime config вручную:

install -d -m 0755 /etc/awg2-harness
cp /usr/share/doc/awg2-harness/repo/config.example.toml /etc/awg2-harness/config.toml
nano /etc/awg2-harness/config.toml

Перед первым apply-firewall/apply-limits оператор обязан сверить:

  • host_firewall.ssh_port — должен совпадать с реально слушающим sshd портом;
  • host_firewall.amnezia_port — должен совпадать с published Docker/Amnezia UDP port;
  • amnezia.peer_ipv4_cidr — должен совпадать с реальной peer-подсетью;
  • anti_torrent.peer_ipv4_cidr — обычно тот же CIDR.

Discovery-команды:

docker port amnezia-awg2 2>/dev/null || docker port amnezia-awg3 2>/dev/null || docker port amnezia-awg

C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg3|amnezia-awg)$' | head -n1)"
docker exec "$C" ip -4 addr
docker exec "$C" awg show

Рекомендуемый config.toml

[amnezia]
container_names = ["amnezia-awg2", "amnezia-awg3", "amnezia-awg"]
interface_candidates = ["awg0", "wg0", "amnezia0", "tun0"]
interface_patterns = ["awg*", "wg*", "amnezia*", "tun*"]
wg_binaries = ["awg", "wg"]
peer_ipv4_cidr = "10.8.1.0/24"

[traffic]
enabled = true
peer_limit = "50mbit"
root_rate = "500mbit"
reconcile_interval_secs = 15
container_ready_timeout_secs = 120
class_start_id = 10
ifb_name = "ifb0"

[sysctl_tuning]
enabled = true
apply_on_daemon_start = true
ip_forward = true
tcp_bbr = "auto"          # off | auto | require
default_qdisc = "fq"      # none | fq | fq_codel
rmem_max = 67108864
wmem_max = 67108864
netdev_backlog_enabled = true
netdev_max_backlog = 50000
udp_rmem_min_enabled = true
udp_rmem_min = 8192

[host_firewall]
enabled = true
ssh_port = 2323
amnezia_port = 443
amnezia_protocols = ["udp"]
allow_icmp = true
default_input_policy = "drop"

[anti_torrent]
enabled = true
max_connections_per_peer = 150
peer_ipv4_cidr = "10.8.1.0/24"
apply_in_amnezia_netns = true

[reboot]
enabled = true
window_start_utc = "01:00"
window_end_utc = "02:00"

[swap]
enabled = true
path = "/swapfile"
size_mb = 2048
priority = 1
swappiness = 1

Пояснения

amnezia.container_names — имена контейнеров, которые считаются Amnezia/AWG. Порядок важен, сопоставление точное. По текущему production-снимку основной вариант — amnezia-awg2, fallback — amnezia-awg. amnezia-awg3 перечислен на будущее: в AmneziaVPN 5.0 отдельного типа контейнера под AWG 3.0 нет, клиент по-прежнему знает только Awg и Awg2.

amnezia.interface_candidates — точные имена интерфейса, которые пробуются первыми.

amnezia.interface_patterns — fallback-паттерны для устойчивости к обновлениям Amnezia. Они нужны для снижения риска переименования интерфейса между awg0, wg0, amnezia0, tun0.

amnezia.wg_binaries — утилита чтения peer state внутри контейнера, пробуется по порядку до первого успеха. Default — ["awg", "wg"]. В официальном образе AmneziaWG реальный бинарь /usr/bin/awg, а /usr/bin/wg — symlink на него, поэтому awg идёт первым; wg остаётся для контейнеров с чистым WireGuard. Значения выполняются внутри контейнера и валидируются как имена команд: непустые, без пробелов и без разделителей пути. Параметр опциональный — конфиги, созданные до его появления, продолжают работать с default.

Хостовый wireguard-tools не нужен и не проверяется в preflight. Peer state читается только через docker exec: AmneziaWG — userspace-реализация, её UAPI-сокет находится в RUNSTATEDIR/amneziawg/ внутри mount namespace контейнера, а nsenter -n входит только в network namespace, поэтому хостовый wg до него не достаёт.

amnezia.peer_ipv4_cidr — подсеть VPN peers. Production default — 10.8.1.0/24; значение конфигурируемое.

traffic.peer_limit — per-peer лимит в обе стороны. Текущий production default — 50mbit.

traffic.root_rate — верхний HTB-класс, в текущем production default — 500mbit.

traffic.reconcile_interval_secs — период фоновой проверки. Он нужен не вместо Docker events, а вместе с ними.

traffic.container_ready_timeout_secs — сколько ждать появления интерфейса после старта контейнера.

traffic.class_start_id — первый class id. Production default — 10, что даёт классы 1:10, 2:10 и дальше.

traffic.ifb_name — имя IFB-интерфейса для ingress shaping. Production default — ifb0. Этот параметр можно оставить в конфиге, потому что бинарь реально создаёт/удаляет этот интерфейс.

sysctl_tuning — отдельный host-level модуль управляемого сетевого baseline через sysctl:

  • persistent-файл: /etc/sysctl.d/90-awg2-harness-vpn.conf;
  • дополнительный managed-файл /etc/modules-load.d/90-awg2-harness-bbr.conf создаётся только при фактически активном BBR и удаляется, когда BBR неактивен;
  • оба managed-файла несут в первой строке маркер # Managed by awg2-harness sysctl_tuning; файл без маркера не перезаписывается и не удаляется, вместо этого возвращается ошибка;
  • net.ipv4.tcp_congestion_control и net.core.default_qdisc управляются только при активном BBR;
  • runtime apply: только sysctl -w и только для drifted keys;
  • на установке .deb модуль не применяется;
  • на старте daemon модуль применяется, если enabled=true и apply_on_daemon_start=true;
  • tcp_bbr = auto не валит запуск при недоступном BBR (warning), require — валит;
  • для sysctl_tuning: modprobe обязателен только для tcp_bbr = "require".

Отдельно для всего пакета:

  • traffic-модуль требует modprobe для IFB при traffic.enabled = true.

Совместимость обновлений:

  • если в существующем runtime-конфиге отсутствует блок [sysctl_tuning], он не включается автоматически (enabled=false по default для missing block);
  • для новых установок рекомендуется явный блок [sysctl_tuning] из примера, где enabled=true.

Ограничения модуля:

  • не использует /etc/sysctl.conf;
  • не запускает sysctl --system;
  • не меняет nftables, Docker bridge и container-local sysctl;
  • не управляет net.ipv4.udp_wmem_min (в Linux документации параметр указан как не имеющий эффекта).

При drift orchestrator всегда делает controlled rebuild owned topology.

anti_torrent.enabled — включает nftables connection-count guard против high-peer-count P2P-нагрузки. Это отдельный AWG2 Harness module и часть текущего production-контракта.

host_firewall.enabled — включает host-level INPUT firewall в отдельной AWG2-owned таблице inet awg2_host_firewall.

host_firewall.ssh_port — SSH порт хоста, который обязательно должен быть открыт. Production default/example: 2323. Значение обязано совпадать с реально слушающим sshd портом до применения apply-firewall.

host_firewall.amnezia_port — внешний UDP/TCP порт Amnezia, который AWG2 открывает в host INPUT (в зависимости от host_firewall.amnezia_protocols). В текущей целевой конфигурации default — 443; значение должно совпадать с реально опубликованным Amnezia UDP/TCP port.

Важно: host_firewall.amnezia_port — это transport-порт AmneziaWG/AWG (например, 443/udp). Это не порт AmneziaDNS.

  • Не задавайте amnezia_port = 53.
  • Не добавляйте DNS/53 в amnezia_protocols.
  • Не открывайте 53/tcp или 53/udp в AWG2 host firewall для ожидаемого production-профиля.

В expected production layout AmneziaDNS работает как внутренний Docker/VPN сервис и не публикуется на public host interface.

Для диагностического блока Docker/Amnezia port path в status приоритет сопоставления такой: сначала ищется published UDP binding с HostPort == host_firewall.amnezia_port, затем fallback на ключ "<amnezia_port>/udp" как container port, затем (если UDP binding ровно один) берётся он. Если UDP bindings несколько и однозначного совпадения нет, статус показывает published_udp_port: unknown.

Диагностика FORWARD в этом блоке является best-effort: сначала используется iptables -S FORWARD, затем fallback через nft list ruleset; результаты только отображаются в status и не изменяют host FORWARD/Docker NAT.

host_firewall.amnezia_protocols — протоколы для порта Amnezia (udp, tcp или оба).

Важно: awg2-harness не управляет host FORWARD/Docker NAT-цепочками. Проект сознательно не добавляет универсальные правила в FORWARD и не меняет Docker-managed forwarding path.

host_firewall.allow_icmp — разрешать ICMP/ICMPv6 на входе.

host_firewall.default_input_policy — политика INPUT chain (drop или accept). Для production рекомендуется drop.

anti_torrent.max_connections_per_peer — максимум одновременных conntrack flows на один VPN peer IP. Текущий пример использует 150: это мягче, чем строгий 100, и обычно безопаснее как первый production baseline. 100 можно использовать как более жёсткий режим; 200/250 — более permissive варианты.

anti_torrent.peer_ipv4_cidr — подсеть, на которую распространяется правило. Обычно должна совпадать с amnezia.peer_ipv4_cidr.

anti_torrent.apply_in_amnezia_netns — выбор namespace для применения anti-torrent правил:

  • true (default): применять в network namespace контейнера Amnezia;
  • false: применять в host namespace.

Для production рекомендуется true, если peer source IP (10.8.1.x) видны именно внутри netns контейнера.

apply-firewall в текущей модели делает оба reconcile: host firewall (host namespace) и anti-torrent firewall (configured namespace).

reboot.enabled — включает плановую перезагрузку.

reboot.window_start_utc / reboot.window_end_utc — UTC-окно плановой перезагрузки. Production default/example — 01:00–02:00 UTC.

swap.enabled — включает setup-задачу swap.

swap.path, swap.size_mb, swap.priority, swap.swappiness — параметры managed swap. При swap.enabled=true пакет приводит swap-state к exclusive состоянию: активен ровно один swap (swap.path), остальные выключаются и swap-entries в /etc/fstab пересобираются в одну managed строку. Рекомендуемый low priority — 1, swappiness = 1.

Ограничения swap.path: абсолютный, без компонентов . и .., без пробелов, управляющих символов и обратных слэшей, не из списка системных путей (/, /etc, /etc/fstab, /etc/passwd, /etc/shadow, /dev, /proc, /sys, /run, /boot) и не под /proc, /sys, /dev. Дополнительно на этапе применения проверяется, что ни один родительский каталог не является symlink.

Пакет создаёт рядом с swapfile маркер владения <swap.path>.awg2-harness-owned. Файл без этого маркера считается чужим: он никогда не пересоздаётся и не удаляется, вместо этого возвращается ошибка. Чужие активные swap только выключаются — их файлы пакет не удаляет.

Что не выносить в конфиг

Не нужно выносить:

  • handles 1:, 2:, ffff:;
  • имена внутренних Rust-модулей;
  • формат команд tc;
  • retry micro-tuning;
  • формат логов;
  • путь lock-файла, если нет эксплуатационной причины;
  • hardening systemd;
  • nftables hook/priority/table names, если нет осознанной причины.

Handles и структура HTB + fq_codel + ingress + ifb — это contract traffic module, а не настройка оператора.