6643328567
Both the code comment and the docs said the interface row is "14 columns for AWG 2.0, 28 for AWG 3.0". That reads as a version signal and it is not one. The width of the row is decided by the version of amneziawg-tools doing the printing, not by which protocol parameters the tunnel has configured: AWG 3.0 tools emit the wide row for an AWG 2.0 configuration too, with the newer fields simply zero, empty or (none). Any rule of the form "N columns means version X" is wrong by construction. The classifier never relied on column count for that purpose - it discriminates on the allowed-ips column - so this is a correction to what the code claims about itself and to what the docs would lead a future change to assume. Five fixtures added from the audit's list, all previously uncovered: - AWG 3.0 interface row with header protection disabled: the key column is (none) and the range fields collapse to single values; - an AWG 2.0 configuration read through AWG 3.0 tools, asserted to have the same width as a native AWG 3.0 row and to still be rejected while its peer parses; - a dump containing several interfaces, each with its own peers; - an interface with no peers at all; - malformed rows - truncated, non-numeric handshake, non-CIDR allowed-ips - which must be skipped rather than partially interpreted. Docs also pin what was verified and against which versions, rather than stating it as timeless: AmneziaVPN 5.0.0.5 and the master branches of amneziawg-tools and amneziawg-go at the time of the change. amnezia-awg3 is now described as what it is - not a confirmed official name. AmneziaVPN 5.0.0.5 added AWG 3.0 support but still exposes only Awg and Awg2 container variants. It stays in the example list on purpose, and the reasoning is written down: a name that does not match costs nothing because the search continues down the list, while a name missing from the list produces a silent "container not found". This is a deliberate deviation from the audit's suggestion to build defaults only from names in use today. cargo fmt --check, clippy -D warnings, and cargo test --locked --workspace --release all clean: 163 + 4 passed.
219 lines
16 KiB
Markdown
219 lines
16 KiB
Markdown
# Конфигурация 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 вручную:
|
||
|
||
```bash
|
||
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-команды:
|
||
|
||
```bash
|
||
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`
|
||
|
||
```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.0.5` поддержка AWG 3.0 добавлена, но варианты контейнера по-прежнему представлены как `Awg` и `Awg2`, отдельного типа `Awg3` нет. Имя оставлено в примере потому, что несовпавшее имя не стоит ничего — поиск просто идёт дальше по списку, — тогда как отсутствующее в списке имя даст молчаливое «контейнер не найден». Если Amnezia введёт другое имя, его нужно будет дописать сюда.
|
||
|
||
`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`;
|
||
- модуль `swap` отдельно управляет `/etc/sysctl.d/90-awg2-harness-swap.conf` (только `vm.swappiness`); прежний путь `/etc/sysctl.d/99-awg2-harness.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, а не настройка оператора.
|