chore: добавлен скрипт для создания прозрачного прокси из сервера
This commit is contained in:
1 parent
064bb70f32
commit
9bb3a2fc5d
7 files changed
+100
-14
No files matched your search
+4
-1
@@ -125,7 +125,10 @@ def _install_panel(server: Server):
|
||||
|
||||
|
||||
def _redirect_to_panel(server: Server):
|
||||
_check(server, serverus.RedirectProxy(server.host()).redirect(server.point.panel.address))
|
||||
point = server.point
|
||||
# 80 - выпуск и продление сертификата (HTTP-01), 443 - подключения клиентов
|
||||
ports = (80, 443, point.panel_port, point.sub_port)
|
||||
_check(server, serverus.RedirectProxy(server.host()).redirect(point.panel.address, ports))
|
||||
|
||||
|
||||
def _check(server: Server, result: serverus.Result) -> serverus.Result:
|
||||
|
||||
@@ -125,6 +125,7 @@ class InstallTests(TestCase):
|
||||
|
||||
_, extravars = runner.call('proxy', '10.0.0.2')
|
||||
self.assertEqual(extravars['target_ip'], '10.0.0.1')
|
||||
self.assertEqual(extravars['ports'], sorted([80, 443, point.panel_port, point.sub_port]))
|
||||
|
||||
def test_install_point_without_mask(self):
|
||||
point = create_point(mask=None)
|
||||
|
||||
@@ -63,7 +63,7 @@ server.release(generate_password())
|
||||
- `XuiPanel(host).issue_certificate(domain='')` - сертификат панели и подписки на домен или на ip: так домен ставится, меняется или убирается после установки. Домен должен уже указывать на сервер (или на его маскировку).
|
||||
- `XuiPanel(host).set_outbound(proxy='')` - весь исходящий трафик клиентов через прокси: `socks5://user:password@host:port`, `http(s)://...` или `CF_WARP` (устройство WARP регистрирует панель, выход по wireguard). Пустая строка - напрямую. Прокси ставится первым outbound Xray, перед применением панель его проверяет. `parse_proxy(url)` проверяет адрес заранее.
|
||||
- `XuiPanel(host).create_inbound(remark='main', target='')` - подключение vless + xhttp + reality на 443, uTLS firefox, сброс трафика ежемесячно. Цель reality подбирает сканер панели (TLS 1.3, h2, X25519, валидный сертификат, минимальная задержка) из `target` или из своих кандидатов. Ключи x25519 генерирует панель, shortId и путь xhttp - serverus. Существующее подключение с тем же `remark` не трогает. Возвращает `data`: `inbound_id`, `target`, `server_name`, `public_key`, `short_ids`, `path`.
|
||||
- `RedirectProxy(host).redirect(target_ip)` - разрешает транзитный трафик и запускает `playbooks/files/redirect.sh <target_ip>`, который перенаправляет весь трафик, кроме ssh, на target_ip. **Сейчас скрипт - заглушка.**
|
||||
- `RedirectProxy(host).redirect(target_ip, ports)` - разрешает транзитный трафик и запускает `playbooks/files/redirect.sh <target_ip> <ports>`, который через nftables перенаправляет tcp-порты `ports` на те же порты target_ip с masquerade и MSS clamping. Точка передает 80, 443, порт панели и порт подписки; ssh остается на самом сервере, скрипт откажется перенаправлять его порт. Правила - `/etc/nftables.d/serverus-redirect.nft`, подключаются include из `/etc/nftables.conf`. Заодно включает bbr, fq и увеличенные буферы и таблицу conntrack.
|
||||
|
||||
Порты и логины: `generate_port(exclude=...)` - порт сервиса из 10000-20000 (ssh - 20000-60000), `generate_username()` - 6 символов.
|
||||
|
||||
|
||||
@@ -1,13 +1,66 @@
|
||||
#!/bin/sh
|
||||
# Перенаправляет весь входящий трафик сервера на целевой ресурс.
|
||||
# Использование: serverus-redirect <target_ip>
|
||||
# Перенаправляет tcp-порты сервера на те же порты целевого ресурса (прозрачный прокси через nftables).
|
||||
# Использование: serverus-redirect <target_ip> <port>[,<port>...]
|
||||
# SSH_PORT - порт ssh этого сервера, его перенаправлять нельзя, иначе доступ к серверу будет потерян.
|
||||
#
|
||||
# TODO: заглушка. Заменить на настоящий скрипт перенаправления через фаервол.
|
||||
# Правила должны переживать перезапуск nftables: базовый конфиг /etc/nftables.conf начинается с flush ruleset.
|
||||
# Правила лежат в отдельном файле и подключаются из /etc/nftables.conf через include:
|
||||
# базовый конфиг начинается с flush ruleset, поэтому только так они переживают перезапуск nftables.
|
||||
# Транзитный трафик (forward) и ip_forward должен разрешить вызывающий плейбук.
|
||||
set -eu
|
||||
|
||||
TARGET_IP="${1:?usage: $0 <target_ip>}"
|
||||
TARGET_IP="${1:?usage: $0 <target_ip> <port>[,<port>...]}"
|
||||
PORTS="${2:?usage: $0 <target_ip> <port>[,<port>...]}"
|
||||
SSH_PORT="${SSH_PORT:?SSH_PORT is required}"
|
||||
|
||||
echo "redirect stub: all traffic except tcp/${SSH_PORT} -> ${TARGET_IP}"
|
||||
RULES=/etc/nftables.d/serverus-redirect.nft
|
||||
CONF=/etc/nftables.conf
|
||||
|
||||
# ip route get заодно проверяет, что это ipv4-адрес и что до него есть маршрут
|
||||
ROUTE=$(ip -4 route get "$TARGET_IP" 2>/dev/null) || { echo "no route to ${TARGET_IP}" >&2; exit 1; }
|
||||
case "$ROUTE" in
|
||||
local\ *) echo "${TARGET_IP} is this server, refusing to redirect to itself" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
for port in $(echo "$PORTS" | tr ',' ' '); do
|
||||
case "$port" in
|
||||
''|*[!0-9]*) echo "bad port: ${port}" >&2; exit 1 ;;
|
||||
esac
|
||||
if [ "$port" -eq "$SSH_PORT" ]; then
|
||||
echo "refusing to redirect ssh port ${SSH_PORT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$(dirname "$RULES")"
|
||||
cat > "$RULES" <<EOF
|
||||
# Сгенерировано serverus-redirect, не править руками.
|
||||
table ip serverus_redirect {
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
|
||||
# nat видит только новые соединения: ответы на исходящие соединения самого сервера (apt, dns) не трогаются
|
||||
fib daddr type local tcp dport { ${PORTS} } dnat to ${TARGET_IP}
|
||||
}
|
||||
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat; policy accept;
|
||||
|
||||
# ответ от цели должен вернуться через этот сервер, иначе клиент получит пакет с чужого адреса
|
||||
ip daddr ${TARGET_IP} ct status dnat masquerade
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority mangle; policy accept;
|
||||
|
||||
# mtu по пути может быть меньше, чем думает клиент
|
||||
tcp flags & (syn | rst) == syn tcp option maxseg size set rt mtu
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
grep -qxF "include \"${RULES}\"" "$CONF" || printf '\ninclude "%s"\n' "$RULES" >> "$CONF"
|
||||
|
||||
nft -c -f "$CONF"
|
||||
systemctl restart nftables
|
||||
|
||||
echo "redirect: tcp/{${PORTS}} -> ${TARGET_IP}"
|
||||
@@ -1,6 +1,6 @@
|
||||
# extravars: target_ip
|
||||
# extravars: target_ip, ports
|
||||
#
|
||||
# Превращает сервер в прозрачный прокси: весь трафик, кроме ssh, уходит на target_ip.
|
||||
# Превращает сервер в прозрачный прокси: tcp-порты ports уходят на те же порты target_ip.
|
||||
- name: Redirect traffic to target
|
||||
hosts: target
|
||||
gather_facts: false
|
||||
@@ -10,12 +10,35 @@
|
||||
firewall_tcp_ports: [ "{{ ansible_port }}" ]
|
||||
firewall_forward: true
|
||||
|
||||
# sysctl при загрузке применяется раньше nftables: без модулей ключи conntrack и bbr не выставятся
|
||||
- name: Load forwarding kernel modules on boot
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modules-load.d/serverus-forward.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
nf_conntrack
|
||||
tcp_bbr
|
||||
|
||||
- name: Load forwarding kernel modules
|
||||
ansible.builtin.command: modprobe -a nf_conntrack tcp_bbr
|
||||
changed_when: false
|
||||
|
||||
- name: Enable ip forwarding
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sysctl.d/90-serverus-forward.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
net.ipv4.ip_forward = 1
|
||||
net.core.default_qdisc = fq
|
||||
net.ipv4.tcp_congestion_control = bbr
|
||||
net.netfilter.nf_conntrack_max = 2000000
|
||||
net.core.netdev_max_backlog = 250000
|
||||
net.core.rmem_max = 16777216
|
||||
net.core.wmem_max = 16777216
|
||||
net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||
net.ipv4.tcp_wmem = 4096 65536 16777216
|
||||
net.ipv4.tcp_mtu_probing = 1
|
||||
net.ipv4.tcp_slow_start_after_idle = 0
|
||||
|
||||
- name: Apply sysctl
|
||||
ansible.builtin.command: sysctl --system
|
||||
@@ -28,6 +51,7 @@
|
||||
mode: "0755"
|
||||
|
||||
- name: Redirect traffic
|
||||
ansible.builtin.command: "/usr/local/sbin/serverus-redirect {{ target_ip }}"
|
||||
ansible.builtin.command:
|
||||
argv: [ /usr/local/sbin/serverus-redirect, "{{ target_ip }}", "{{ ports | join(',') }}" ]
|
||||
environment:
|
||||
SSH_PORT: "{{ ansible_port }}"
|
||||
@@ -25,7 +25,9 @@
|
||||
state: absent
|
||||
loop:
|
||||
- /usr/local/sbin/serverus-redirect
|
||||
- /etc/nftables.d/serverus-redirect.nft
|
||||
- /etc/sysctl.d/90-serverus-forward.conf
|
||||
- /etc/modules-load.d/serverus-forward.conf
|
||||
|
||||
- name: Reload sysctl
|
||||
ansible.builtin.command: sysctl --system
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from collections.abc import Iterable
|
||||
|
||||
from .runner import Result
|
||||
from .server import Server
|
||||
|
||||
|
||||
class RedirectProxy(Server):
|
||||
"""Сервер, который перенаправляет весь трафик на другой сервер, скрывая его адрес."""
|
||||
"""Сервер, который перенаправляет трафик на другой сервер, скрывая его адрес."""
|
||||
|
||||
def redirect(self, target_ip: str) -> Result:
|
||||
return self.run('proxy', target_ip=target_ip)
|
||||
def redirect(self, target_ip: str, ports: Iterable[int]) -> Result:
|
||||
"""Перенаправляет tcp-порты ports на те же порты target_ip. Остальные порты сервер обслуживает сам."""
|
||||
return self.run('proxy', target_ip=target_ip, ports=sorted(set(ports)))
|
||||
Reference in new issue
Block a user