224 lines
21 KiB
Markdown
224 lines
21 KiB
Markdown
# Конфигурация AWG2 Harness
|
||
|
||
Основной target AWG2 Harness 0.3.1 — self-hosted AmneziaWG 3.1. AWG 3.0/2.x и `amnezia-awg` сохраняются как regression/legacy compatibility. Точная upstream-база описана в [`UPSTREAM_COMPATIBILITY.md`](UPSTREAM_COMPATIBILITY.md). Конфиг живёт в `/etc/awg2-harness/config.toml`, общий для всего пакета и содержит только эксплуатационно значимые параметры.
|
||
|
||
## Создание runtime config
|
||
|
||
Пакет намеренно **не** создаёт `/etc/awg2-harness/config.toml` автоматически. Это снижает риск случайно применить firewall с неверным SSH-портом или проверять не тот Amnezia UDP-порт.
|
||
|
||
После установки `.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` портом;
|
||
- `amnezia.expected_host_udp_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-awg
|
||
|
||
C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg)$' | head -n1)"
|
||
docker exec "$C" ip -4 addr
|
||
docker exec "$C" awg show
|
||
```
|
||
|
||
## Рекомендуемый `config.toml`
|
||
|
||
```toml
|
||
[amnezia]
|
||
container_names = ["amnezia-awg2", "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"
|
||
expected_host_udp_port = 443
|
||
|
||
[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 = "off" # off | auto | require
|
||
default_qdisc = "fq" # none | fq
|
||
rmem_max = 67108864
|
||
wmem_max = 67108864
|
||
netdev_backlog_enabled = false
|
||
netdev_max_backlog = 10000
|
||
udp_rmem_min_enabled = false
|
||
udp_rmem_min = 8192
|
||
|
||
[host_firewall]
|
||
enabled = true
|
||
ssh_port = 2323
|
||
allow_icmp = true
|
||
default_input_policy = "drop"
|
||
|
||
[anti_torrent]
|
||
enabled = true
|
||
max_connections_per_peer = 500
|
||
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. Основной официальный вариант — `amnezia-awg2`, затем проверяется legacy `amnezia-awg`. Отдельного официального типа `amnezia-awg3` в зафиксированной upstream-версии нет. Другое имя можно явно добавить в runtime config; неизвестные имена автоматически не угадываются.
|
||
|
||
`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` официальным `awg` внутри контейнера. Это единая container-side точка доступа для kernel `amneziawg` и fallback `amneziawg-go`, не зависящая от версии host-side tools.
|
||
|
||
`amnezia.peer_ipv4_cidr` — подсеть VPN peers. Production default — `10.8.1.0/24`; значение конфигурируемое.
|
||
|
||
Поддерживаемая server-side topology строго соответствует официальной self-hosted схеме Amnezia/AWG 3.1: у каждого peer либо нет маршрута (`(none)`), либо есть ровно один IPv4 host route `/32` внутри `amnezia.peer_ipv4_cidr`. IPv6, смешанные IPv4+IPv6, несколько маршрутов, более широкая маска и адрес вне подсети дают явную ошибку `unsupported topology` до изменения `tc` и anti-torrent state. Клиентские `0.0.0.0/0, ::/0` и внешний IPv6 Docker publish относятся к другим плоскостям и этим ограничением не запрещаются.
|
||
|
||
`amnezia.expected_host_udp_port` — ожидаемый published UDP host port Docker для AmneziaWG. Поле обязательное и используется диагностикой `status` для контракта `expected host port ↔ Docker binding ↔ awg/wg listen-port`. Оно не создаёт host INPUT allow-rule: трафик к published bridge container проходит через Docker `FORWARD`/NAT path, а не через host `INPUT`.
|
||
|
||
`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` и дальше. Допустимый диапазон `10..=9999`; каждый фактический peer class также обязан остаться не выше `9999`. Это граница текстового представления: `tc` разбирает class minor как hexadecimal, поэтому следующая десятичная строка `10000` уже вышла бы за 16-bit minor id.
|
||
|
||
`traffic.ifb_name` — имя IFB-интерфейса для ingress shaping. Production default — `ifb0`. Harness ставит созданному IFB alias `awg2-harness:traffic` и удаляет его только при совпадении type+alias. Существующий чужой интерфейс с тем же именем вызывает ошибку и не заменяется.
|
||
|
||
`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 выбран desired policy (`auto`/`require` и BBR доступен), и удаляется при `tcp_bbr = "off"` или недоступном `auto`;
|
||
- оба managed-файла несут в первой строке маркер `# Managed by awg2-harness sysctl_tuning`; файл без маркера не перезаписывается и не удаляется, вместо этого возвращается ошибка;
|
||
- `net.ipv4.tcp_congestion_control` и `net.core.default_qdisc` управляются только когда BBR желателен по policy; `default_qdisc` принимает только `none | fq` и не управляет per-peer `fq_codel`;
|
||
- 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"`.
|
||
|
||
Production default — `tcp_bbr = "off"`: BBR влияет на локальные TCP sockets хоста, но не ускоряет UDP transport AWG и не является частью HTB peer limiter. `auto`/`require` остаются осознанным opt-in. Status разделяет три разных факта: `bbr_available`, `bbr_desired` и фактический `bbr_runtime_active`, вычисленный по текущему `net.ipv4.tcp_congestion_control`.
|
||
|
||
`udp_rmem_min_enabled = false` — production default, потому что `net.ipv4.udp_rmem_min` является per-network-namespace sysctl: запись на хосте не меняет UDP stack AWG-контейнера. Значение `udp_rmem_min = 8192` сохранено как opt-in для локальных UDP sockets хоста; отдельный container-side reconcile ради него не добавляется.
|
||
|
||
`netdev_backlog_enabled = false` — безопасный production default: harness не управляет `net.core.netdev_max_backlog`, пока оператор не включит это явно после измерений. Значение `netdev_max_backlog = 10000` используется только при `netdev_backlog_enabled = true`.
|
||
|
||
При обновлении с конфига, где backlog раньше был включён, переключение флага в `false` удаляет key из managed-файла, но не перезаписывает уже изменённое runtime-значение. До следующей перезагрузки его можно вернуть вручную к baseline дистрибутива. Перед осознанным включением backlog снимите pre/post-метрики `/proc/net/softnet_stat` (в том числе dropped/time_squeeze) и CPU/latency под реальной нагрузкой.
|
||
|
||
Отдельно для всего пакета:
|
||
|
||
- `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. Harness владеет только помеченным comment переходом из встроенного `INPUT` и изолированными цепочками `AWG2H_A`/`AWG2H_B` в `iptables` и `ip6tables`. Встроенная policy, Docker `FORWARD`/NAT и чужие правила не очищаются и не меняются.
|
||
|
||
`host_firewall.ssh_port` — SSH порт хоста, который обязательно должен быть открыт. Production default/example: `2323`. Значение обязано совпадать с реально слушающим sshd портом до применения `apply-firewall`.
|
||
|
||
Поля `host_firewall.amnezia_port` и `host_firewall.amnezia_protocols` удалены. Если они остались в старом runtime config, `validate-config` завершится ошибкой `unknown field`, чтобы оператор явно мигрировал значение в `amnezia.expected_host_udp_port`. Host firewall не должен открывать published Docker port в `INPUT`.
|
||
|
||
Не открывайте `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 == amnezia.expected_host_udp_port`, затем fallback на ключ `"<expected_host_udp_port>/udp"` как container port, затем (если UDP binding ровно один) берётся он. Внутри контейнера выполняется `awg show <interface> listen-port` с fallback на `wg`. `port_contract: ok` означает, что host port совпал с config, а container port Docker binding — с фактическим listen-port. При нескольких UDP bindings без однозначного соответствия или ошибке probe выводится `unknown`, а не произвольный первый порт.
|
||
|
||
Диагностика `FORWARD` в этом блоке является best-effort: сначала используется `iptables -S FORWARD`, затем fallback через `nft list ruleset`; результаты только отображаются в `status` и не изменяют host `FORWARD`/Docker NAT.
|
||
|
||
Важно: `awg2-harness` не управляет host `FORWARD`/Docker NAT-цепочками. Проект сознательно не добавляет универсальные правила в `FORWARD` и не меняет Docker-managed forwarding path.
|
||
|
||
`host_firewall.allow_icmp` — разрешать ICMP/ICMPv6 на входе.
|
||
|
||
`host_firewall.default_input_policy` — конечный verdict собственной цепочки (`drop` или `accept`). Для production рекомендуется `drop`. Это поле не меняет policy встроенной цепочки `INPUT`.
|
||
|
||
`anti_torrent.max_connections_per_peer` — максимум одновременных conntrack flows на один VPN peer IP. Штатный production baseline — `500`, выбранный по эксплуатационным измерениям проекта так, чтобы connection guard оставался включённым и не вмешивался в нормальный повседневный профиль. Значение ниже `500` является сознательно более агрессивной политикой и требует проверки на реальных клиентах. Поле обязательное и не скрыто в Rust default.
|
||
|
||
`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: iptables/ip6tables host firewall (host namespace) и nftables anti-torrent firewall (configured namespace). Host-цепочки обновляются через A/B staging: обе IP-family сначала строятся и проверяются, после чего jump переключается; при ошибке IPv6 выполненный IPv4 switch откатывается.
|
||
|
||
`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`: путь должен быть либо `/swapfile`, либо файлом непосредственно в `/var/lib/awg2-harness/`. Дополнительно: абсолютный, без компонентов `.` и `..`, без пробелов, управляющих символов и обратных слэшей; на этапе применения проверяется, что ни один родительский каталог не является symlink.
|
||
|
||
Ограничение корней обязательно, а не косметика. Если родительский каталог доступен на запись кому-то кроме root, между проверкой пути и запуском `mkswap --file` этот пользователь успевает подменить файл на symlink и перенаправить операцию, выполняемую от root. Оба разрешённых корня принадлежат root, поэтому окна не возникает. `/swapfile` оставлен для совместимости с уже развёрнутыми конфигурациями.
|
||
|
||
Владение фиксируется в `/var/lib/awg2-harness/swap-ownership.conf` по `device` + `inode` + пути, а не по маркеру рядом с файлом. Файл, которого нет в этой записи, или файл с тем же путём, но другими `device`/`inode` (то есть подменённый), считается чужим: он никогда не пересоздаётся и не удаляется, вместо этого возвращается ошибка. Чужие активные 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, а не настройка оператора.
|