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):
|
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:
|
def _check(server: Server, result: serverus.Result) -> serverus.Result:
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ class InstallTests(TestCase):
|
|||||||
|
|
||||||
_, extravars = runner.call('proxy', '10.0.0.2')
|
_, extravars = runner.call('proxy', '10.0.0.2')
|
||||||
self.assertEqual(extravars['target_ip'], '10.0.0.1')
|
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):
|
def test_install_point_without_mask(self):
|
||||||
point = create_point(mask=None)
|
point = create_point(mask=None)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ server.release(generate_password())
|
|||||||
- `XuiPanel(host).issue_certificate(domain='')` - сертификат панели и подписки на домен или на ip: так домен ставится, меняется или убирается после установки. Домен должен уже указывать на сервер (или на его маскировку).
|
- `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).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`.
|
- `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 символов.
|
Порты и логины: `generate_port(exclude=...)` - порт сервиса из 10000-20000 (ssh - 20000-60000), `generate_username()` - 6 символов.
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,66 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Перенаправляет весь входящий трафик сервера на целевой ресурс.
|
# Перенаправляет tcp-порты сервера на те же порты целевого ресурса (прозрачный прокси через nftables).
|
||||||
# Использование: serverus-redirect <target_ip>
|
# Использование: serverus-redirect <target_ip> <port>[,<port>...]
|
||||||
# SSH_PORT - порт ssh этого сервера, его перенаправлять нельзя, иначе доступ к серверу будет потерян.
|
# SSH_PORT - порт ssh этого сервера, его перенаправлять нельзя, иначе доступ к серверу будет потерян.
|
||||||
#
|
#
|
||||||
# TODO: заглушка. Заменить на настоящий скрипт перенаправления через фаервол.
|
# Правила лежат в отдельном файле и подключаются из /etc/nftables.conf через include:
|
||||||
# Правила должны переживать перезапуск nftables: базовый конфиг /etc/nftables.conf начинается с flush ruleset.
|
# базовый конфиг начинается с flush ruleset, поэтому только так они переживают перезапуск nftables.
|
||||||
|
# Транзитный трафик (forward) и ip_forward должен разрешить вызывающий плейбук.
|
||||||
set -eu
|
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}"
|
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
|
- name: Redirect traffic to target
|
||||||
hosts: target
|
hosts: target
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
@@ -10,12 +10,35 @@
|
|||||||
firewall_tcp_ports: [ "{{ ansible_port }}" ]
|
firewall_tcp_ports: [ "{{ ansible_port }}" ]
|
||||||
firewall_forward: true
|
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
|
- name: Enable ip forwarding
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/sysctl.d/90-serverus-forward.conf
|
dest: /etc/sysctl.d/90-serverus-forward.conf
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
content: |
|
content: |
|
||||||
net.ipv4.ip_forward = 1
|
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
|
- name: Apply sysctl
|
||||||
ansible.builtin.command: sysctl --system
|
ansible.builtin.command: sysctl --system
|
||||||
@@ -28,6 +51,7 @@
|
|||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: Redirect traffic
|
- 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:
|
environment:
|
||||||
SSH_PORT: "{{ ansible_port }}"
|
SSH_PORT: "{{ ansible_port }}"
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop:
|
loop:
|
||||||
- /usr/local/sbin/serverus-redirect
|
- /usr/local/sbin/serverus-redirect
|
||||||
|
- /etc/nftables.d/serverus-redirect.nft
|
||||||
- /etc/sysctl.d/90-serverus-forward.conf
|
- /etc/sysctl.d/90-serverus-forward.conf
|
||||||
|
- /etc/modules-load.d/serverus-forward.conf
|
||||||
|
|
||||||
- name: Reload sysctl
|
- name: Reload sysctl
|
||||||
ansible.builtin.command: sysctl --system
|
ansible.builtin.command: sysctl --system
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from .runner import Result
|
from .runner import Result
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
|
||||||
|
|
||||||
class RedirectProxy(Server):
|
class RedirectProxy(Server):
|
||||||
"""Сервер, который перенаправляет весь трафик на другой сервер, скрывая его адрес."""
|
"""Сервер, который перенаправляет трафик на другой сервер, скрывая его адрес."""
|
||||||
|
|
||||||
def redirect(self, target_ip: str) -> Result:
|
def redirect(self, target_ip: str, ports: Iterable[int]) -> Result:
|
||||||
return self.run('proxy', target_ip=target_ip)
|
"""Перенаправляет tcp-порты ports на те же порты target_ip. Остальные порты сервер обслуживает сам."""
|
||||||
|
return self.run('proxy', target_ip=target_ip, ports=sorted(set(ports)))
|
||||||
Reference in new issue
Block a user