From aa2a53808afba693e8f42026691970674424a1a3 Mon Sep 17 00:00:00 2001 From: Ivan Vasilev Date: Tue, 27 Oct 2026 21:52:51 +0300 Subject: [PATCH] Random AWG port and obfuscation params on install; open ports in firewalld/iptables --- install.sh | 176 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 141 insertions(+), 35 deletions(-) diff --git a/install.sh b/install.sh index 8b7fb45..a9b95db 100644 --- a/install.sh +++ b/install.sh @@ -149,7 +149,7 @@ NODE_TLS_KEY="$AWG_DIR/node_tls.key" NODE_TLS_CRT="$AWG_DIR/node_tls.crt" NODE_KEY_FILE="$AWG_DIR/node.key" IFACE="awg1" -AWG_PORT="47619" +AWG_PORT="${AWG_PORT:-}" # UDP для клиентов; пусто — выбирает choose_awg_params SUBNET="10.9" MTU="1376" @@ -357,27 +357,92 @@ fi DB_EXISTS="n" [[ -f "$DB_FILE" ]] && DB_EXISTS="y" -# TODO: рандомизировать параметры обфускации при установке. -# awg-ctrl уже читает их из [Interface] awg1.conf (readAwgParams), поэтому -# достаточно генерировать случайные значения здесь — править awg-ctrl не нужно. -# Ограничения, которые обязан соблюсти генератор: -# - Jc: 3–10 (больше — лишний трафик); Jmin < Jmax, оба < MTU -# - S1, S2: < ~150, в части версий S1 != S2 -# - S1–S4: при заданном HeaderProtectionKey (AmneziaWG 3.x) НИ ОДНО из них -# не может быть меньше 12 (HEADER_PROTECTION_NONCE_SIZE). Модуль на -# нарушение отвечает только «Invalid argument», причина видна лишь при -# `echo "module amneziawg +p" > /sys/kernel/debug/dynamic_debug/control` -# - H1–H4: уникальны между собой, НЕ равны 1/2/3/4 (зарезервированные -# типы сообщений WireGuard), большие uint32 без пересечений -# - I1–I5 НЕ трогать: сейчас уходят в vpn:// ключ пустыми плейсхолдерами -# Параметры фиксируются на весь срок жизни сервера: при KEEP_DATA=y их -# менять нельзя (иначе все ранее выданные vpn:// ключи станут невалидными). -JC=6; JMIN=10; JMAX=50 -S1=90; S2=45; S3=37; S4=14 -H1="1224800044-2116730834" -H2="2122053282-2133204808" -H3="2133604274-2140756116" -H4="2143656228-2147444225" +# Параметры обфускации и UDP-порт AWG. На новой установке — случайные, у каждого сервера +# свои: известные всем значения по умолчанию — готовая сигнатура для блокировки. При KEEP_DATA=y +# берутся из прежнего awg1.conf (choose_awg_params ниже): они зашиты во все выданные vpn:// +# ключи, их смена сделала бы каждый ключ невалидным. +# Ограничения генератора (gen_awg_params): +# - Jc 4–8; Jmin < Jmax, оба много меньше MTU +# - S1–S4 ≥ 12: при HeaderProtectionKey (AmneziaWG 3.x) меньшее модуль отвергает с одним +# «Invalid argument» (причина видна лишь при +# `echo "module amneziawg +p" > /sys/kernel/debug/dynamic_debug/control`) +# - размеры пакетов после добавки разные: 148+S1 (init), 92+S2 (response), 64+S3 (cookie) — +# иначе тип пакета снова узнаётся по длине; отсюда же S1 != S2 и S2 != S1+56 +# - S4 добавляется к каждому пакету данных — держим маленьким, как прежние 14 +# - H1–H4: непересекающиеся диапазоны в [10^8, 2^31−1] — далеко от 1–4 (типы сообщений +# WireGuard) и не выше прежних значений (некоторые клиенты разбирают их как int32) +# - I1–I5 НЕ трогать: в vpn:// ключ уходят пустыми плейсхолдерами +# Прежние постоянные значения: ими собраны ключи установок до рандомизации — нужны, если +# при KEEP_DATA=y awg1.conf потерялся. +LEGACY_AWG_PORT="47619" +LEGACY_JC=6; LEGACY_JMIN=10; LEGACY_JMAX=50 +LEGACY_S1=90; LEGACY_S2=45; LEGACY_S3=37; LEGACY_S4=14 +LEGACY_H1="1224800044-2116730834" +LEGACY_H2="2122053282-2133204808" +LEGACY_H3="2133604274-2140756116" +LEGACY_H4="2143656228-2147444225" + +rand_int() { shuf -i "$1-$2" -n 1; } + +gen_awg_params() { + JC=$(rand_int 4 8) + JMIN=$(rand_int 8 40) + JMAX=$(( JMIN + $(rand_int 20 80) )) + while :; do + S1=$(rand_int 15 150); S2=$(rand_int 15 150); S3=$(rand_int 15 60) + local a=$(( 148 + S1 )) b=$(( 92 + S2 )) c=$(( 64 + S3 )) + [[ "$S1" != "$S2" && "$a" != "$b" && "$a" != "$c" && "$b" != "$c" ]] && break + done + S4=$(rand_int 12 20) + # 8 разных чисел по возрастанию → 4 непересекающихся диапазона, раздаём H1–H4 вперемешку. + local n r + n=($(shuf -i 100000000-2147483647 -n 8 | sort -n)) + r=($(printf '%s\n' "${n[0]}-${n[1]}" "${n[2]}-${n[3]}" "${n[4]}-${n[5]}" "${n[6]}-${n[7]}" | shuf)) + H1=${r[0]}; H2=${r[1]}; H3=${r[2]}; H4=${r[3]} +} + +legacy_awg_params() { + JC=$LEGACY_JC; JMIN=$LEGACY_JMIN; JMAX=$LEGACY_JMAX + S1=$LEGACY_S1; S2=$LEGACY_S2; S3=$LEGACY_S3; S4=$LEGACY_S4 + H1=$LEGACY_H1; H2=$LEGACY_H2; H3=$LEGACY_H3; H4=$LEGACY_H4 +} + +# Случайный UDP-порт, который никто на сервере не слушает. +gen_awg_port() { + local p + while :; do + p=$(rand_int 20000 59999) + [[ -z "$(ss -Hulnp "sport = :$p" 2>/dev/null)" ]] && { echo "$p"; return; } + done +} + +# Порт и обфускация: при KEEP_DATA=y — из прежнего awg1.conf (все J/S/H разом или ни одного: +# смешивать с новыми нельзя), иначе случайные. AWG_PORT из окружения важнее всего. +choose_awg_params() { + local k prev_port="" + # || true: без файла sed падает, а с pipefail и set -e это молча роняло бы установку. + conf_val() { sed -n "s/^$1 *= *//p" "$AWG_CONF" 2>/dev/null | head -1 || true; } + if [[ "$KEEP_DATA" == "y" ]]; then + JC=$(conf_val Jc); JMIN=$(conf_val Jmin); JMAX=$(conf_val Jmax) + S1=$(conf_val S1); S2=$(conf_val S2); S3=$(conf_val S3); S4=$(conf_val S4) + H1=$(conf_val H1); H2=$(conf_val H2); H3=$(conf_val H3); H4=$(conf_val H4) + for k in "$JC" "$JMIN" "$JMAX" "$S1" "$S2" "$S3" "$S4" "$H1" "$H2" "$H3" "$H4"; do + if [[ -z "$k" ]]; then + legacy_awg_params + warn "Параметры обфускации не прочитаны из $AWG_CONF — взяты прежние постоянные" + break + fi + done + prev_port=$(conf_val ListenPort) + [[ -z "$prev_port" ]] && prev_port=$(prev_val SERVER_PORT) + AWG_PORT="${AWG_PORT:-${prev_port:-$LEGACY_AWG_PORT}}" + else + gen_awg_params + AWG_PORT="${AWG_PORT:-$(gen_awg_port)}" + fi + [[ "$AWG_PORT" =~ ^[0-9]+$ && "$AWG_PORT" -gt 0 && "$AWG_PORT" -lt 65536 ]] \ + || fail "AWG_PORT «${AWG_PORT}» — не номер порта" +} # Поколение протокола и закрепление версии пакета — управляются переменными # окружения, задаются ДО запуска: `AWG_GEN=auto bash install.sh`. @@ -686,6 +751,8 @@ if [[ "$ROLE" != "node" ]]; then fi fi +[[ "$ROLE" != "core" ]] && choose_awg_params + # Нода: к какой панели подключаться. Строку awgjoin://… даёт панель («Добавить сервер»): # base64url от JSON {v, h, p, pin, n, s}. Разбираем здесь, до установки, чтобы опечатка не # обнаружилась через десять минут сборки модуля. Значения в строке ограничены @@ -1282,29 +1349,65 @@ else ok "AmneziaWG $GEN_LABEL · модуль ${MOD_VER:-?} · tools ${TOOLS_VER:-?}$([[ "$AWG_PIN" == "y" ]] && echo " · версия закреплена")" fi -# UFW. PostUp делает `iptables -A FORWARD` — правило ДОПИСЫВАЕТСЯ в конец -# цепочки, а UFW вставляет свои переходы в начало, поэтому до нашего ACCEPT -# дело не доходит и трафик клиентов наружу режется: в dmesg сыплется -# «[UFW BLOCK] IN=awg1 OUT=eth0». Симптом — «VPN подключается, интернета нет». -# Лечится штатным `ufw route allow`, менять DEFAULT_FORWARD_POLICY не нужно. +# Порты, которые должны быть доступны снаружи. Нода открывает только UDP для клиентов: к панели +# она подключается сама. +OPEN_PORTS=() +[[ "$ROLE" != "core" ]] && OPEN_PORTS+=("${AWG_PORT}/udp") +[[ "$ROLE" != "node" ]] && OPEN_PORTS+=("${UI_PORT}/tcp" "${SUB_PORT}/tcp") +[[ "$ROLE" != "node" && -n "$NODE_PORT" ]] && OPEN_PORTS+=("${NODE_PORT}/tcp") + +# Закрывает ли голый iptables входящие: политика DROP или правило DROP/REJECT в INPUT. +iptables_input_closed() { + command -v iptables &>/dev/null || return 1 + iptables -S INPUT 2>/dev/null | grep -qE '^-P INPUT DROP|^-A INPUT .*-j (DROP|REJECT)' +} + +# Фаервол на самом сервере. Фаервол хостера (панель провайдера, security group) отсюда не +# виден — о нём напоминаем в итоге установки. if command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -qi '^Status: active'; then + # UFW. PostUp делает `iptables -A FORWARD` — правило ДОПИСЫВАЕТСЯ в конец + # цепочки, а UFW вставляет свои переходы в начало, поэтому до нашего ACCEPT + # дело не доходит и трафик клиентов наружу режется: в dmesg сыплется + # «[UFW BLOCK] IN=awg1 OUT=eth0». Симптом — «VPN подключается, интернета нет». + # Лечится штатным `ufw route allow`, менять DEFAULT_FORWARD_POLICY не нужно. info "UFW активен, открываем $([[ "$ROLE" != "core" ]] && echo "маршрут и ")порты" - UFW_OPEN=() if [[ "$ROLE" != "core" ]]; then run ufw route allow in on "$IFACE" out on "$NET_IFACE" \ || warn "ufw route allow не сработал — трафик клиентов может резаться" - UFW_OPEN+=("${AWG_PORT}/udp") fi - # Нода наружу открывает только UDP для клиентов: к панели она подключается сама. - [[ "$ROLE" != "node" ]] && UFW_OPEN+=("${UI_PORT}/tcp" "${SUB_PORT}/tcp") - [[ "$ROLE" != "node" && -n "$NODE_PORT" ]] && UFW_OPEN+=("${NODE_PORT}/tcp") - for P in "${UFW_OPEN[@]}"; do + for P in "${OPEN_PORTS[@]}"; do run ufw allow "$P" || warn "не удалось открыть $P" done run ufw reload || true - ok "UFW:$([[ "$ROLE" != "core" ]] && echo " форвардинг $IFACE → $NET_IFACE,") порты ${UFW_OPEN[*]}" + ok "UFW:$([[ "$ROLE" != "core" ]] && echo " форвардинг $IFACE → $NET_IFACE,") порты ${OPEN_PORTS[*]}" +elif command -v firewall-cmd &>/dev/null && firewall-cmd --state &>/dev/null; then + info "firewalld активен, открываем порты" + for P in "${OPEN_PORTS[@]}"; do + run firewall-cmd --permanent --add-port="$P" || warn "не удалось открыть $P" + done + run firewall-cmd --reload || true + ok "firewalld: порты ${OPEN_PORTS[*]}" + if [[ "$ROLE" != "core" ]]; then + warn "Если VPN подключается, а интернета нет — firewalld режет форвардинг:" + warn " firewall-cmd --permanent --zone=trusted --add-interface=${IFACE} && firewall-cmd --reload" + fi +elif iptables_input_closed; then + # Голый iptables без обёрток: вставляем ACCEPT в начало INPUT (повторно не дублируем). + info "iptables закрывает входящие, открываем порты" + for P in "${OPEN_PORTS[@]}"; do + iptables -C INPUT -p "${P#*/}" --dport "${P%/*}" -j ACCEPT 2>/dev/null \ + || run iptables -I INPUT -p "${P#*/}" --dport "${P%/*}" -j ACCEPT \ + || warn "не удалось открыть $P" + done + if command -v netfilter-persistent &>/dev/null; then + run netfilter-persistent save || warn "правила iptables не сохранены — после перезагрузки порты закроются" + ok "iptables: порты ${OPEN_PORTS[*]} (сохранено)" + else + ok "iptables: порты ${OPEN_PORTS[*]}" + warn "Правила iptables не переживут перезагрузку: apt install iptables-persistent && netfilter-persistent save" + fi else - info "UFW не активен, правила фаервола не трогаем" + info "Фаервол на сервере входящие не закрывает — открывать нечего" fi step "5/6 Команда awg-ctrl" @@ -1432,6 +1535,9 @@ if [[ "$ROLE" == "node" ]]; then echo -e " Сервер «${SERVER_NAME}» появится в списке серверов панели — управляй им оттуда." fi rule +echo -e " ${YLW}⚠${NC} Если у хостера свой фаервол (панель провайдера, security group) — открой там:" +echo -e " ${BLD}${OPEN_PORTS[*]}${NC} ${DIM}иначе клиенты не подключатся, а на сервере всё будет выглядеть исправным${NC}" +rule echo -e " ${DIM}awg-ctrl ${NC} меню: запуск, статус, логин и пароль, join/unjoin" echo -e " ${DIM}systemctl restart awg-control${NC} перезапуск ${DIM}(автозапуск на загрузке включён)${NC}" echo