Возникла задача заворачивать трафик в тоннель, основываясь на имени домена ресурса, к которому идёт обращение.Многие интернет-ресурсы имеют большой пул ip-адресов, более того, этот пул может меняться. Делать nslookup для каждого интересующего сервиса и заворачивать все выдаваемые подсети - неудобно и неэлегантно. На помощь может придти прокси-сервер squid, настроенный прозрачно с функцией ssl_bump.

Сборка squid из исходников описана в сети многократно, в том числе и на habr. В Ubuntu версии 22.04 появился пакет squid-openssl, который, вроде бы, должен избавлять от необходимости такой сборки, но свежие squid 5 и 6 версии, собранные вручную или установленные из репозитория, почему-то некоректно устанавливают ssl-соединения с некоторыми сайтами, в т.ч. иногда и google.com. Возникает ошибка в браузере:

SSL_ERROR_RX_RECORD_TOO_LONG

В причине возникновения данной ошибки я разобраться не смог — просто откатился в версии squid до 4.10. Инструкции в интернете обычно описывали процесс сборки из исходников squid3 — с ним всё работает. На 4й версии у меня тоже всё получилось: версия 4.10 прямо сейчас работает у меня на двух шлюзах в качестве прозрачного прокси с ограничением доступа по имени сайта. При устанвке squid 4.10 на ubuntu 24.04 возникают проблемы с зависимостями от старых версий libldap-2.4-2 и libnettle7. Эти проблемы я решал временным подключением репозиториев bionic и jammy. Собственно и исходники самого squid4.10 тоже нужно брать из старых репозиториев. Я же когда-то собрал для себя набор пакетов и храню их в виде готовых deb-файлов:

  • squid_4.10-1ubuntu1.2_amd64.deb

  • squidclient_4.10-1ubuntu1.2_amd64.deb

  • squid-purge_4.10-1ubuntu1.2_amd64.deb

  • libressl_3.0.2-1_amd64.deb

  • squid-cgi_4.10-1ubuntu1.2_amd64.deb

  • squid-common_4.10-1ubuntu1.2_all.deb

Все эксперименты производились в виртуальной среде

Есть условный шлюз на Ubuntu 24.04 с тремя сетевыми интерфейсами: eth0, eth1 и wg0. Трафик из локальной сети (eth1) заворачивается на порты сквида вместо NAT и выходит в глобальную сеть (eth0). Есть также интерфейс wg0 — тонель в некоторую другую подсеть, через которую мы, в качестве эксперимента, хотим попробовать завернуть интересующий нас трафик. Трафик признаётся интересующим нас на основании имени домена, к которому идёт обращение.

Для начала опишем конфигурацию iptables на шлюзе:

#!/bin/bash
#Очищаем правила IPTABLES
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X

# Разрешаем по умолчанию только исходящий трафик
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

# Разрешаем ответы на установленные соединения
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
#Разрешаем доступ по ssh (если надо извне)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
#Разрешаем входящие соединения для локальной сети
iptables -A INPUT -i eth1 -j ACCEPT
#Перенаправляем web-трафик на прокси
iptables -A PREROUTING -t nat -i eth1 -p tcp -m multiport --dport 80,8080 -j DNAT --to-destination 10.10.10.1:3129
iptables -A PREROUTING -t nat -i eth1 -p tcp -m multiport --dport 443 -j DNAT --to-destination 10.10.10.1:3130

Поднимаем интерфейс wg0

Скрипты wg-quick, для сложных режимов маршрутизации, я использовать не люблю, так как диррективу AllowedIPs они воспринимают и как разрешение и как правила маршрутизации. Данные скрипты делают, на мой взгляд, не совсем прозрачные изменеия в ip route и ip rule. Ввиду этого, будем поднимать интерфейс средствами команды ip.

Создадим файл конфигурации /etc/wireguard/wg0.conf вида:

[Interface]
PrivateKey = uMгщр78н8ZCот7867orkmaOWwK+fJZMc088898999NQC4Xc=
ListenPort = 51820

[Peer]
#Удалённый сервер
Endpoint = 62.63.64.65:51820
PublicKey = aevUokB2цсцусgCqPCnKкикеиM3z5/hнгьнгS6о=
AllowedIPs = 0.0.0.0/0

Как видно, AllowedIPs = 0.0.0.0/0, но этот параметр отвечает только за разрешение на доступ, а не маршрутизацию. Внутренний ip-адрес для интерфейса в этом файле не задаётся.

Далее создадим и сконфигурируем интерфейс:

#!/bin/bash
ip link add dev wg0 type wireguard
ip address add dev wg0 10.80.0.3/24
wg setconf wg0 /etc/wireguard/wg0.conf
ip link set up dev wg0

# Добавим описание таблицы маршрутизации 200 — tunnel:
echo 200 tunnel >> /etc/iproute2/rt_tables

# Зададим дефолтный гейт для таблицы 200:
ip r replace default dev wg0 table tunnel
# Укажем пакетам с fwmark 5 использовать таблицу 200
ip ru add from all fwmark 0x5 lookup tunnel

Для проверки функционирования пустим пинг на тот конец тоннеля:

root@L7test:/# ping 10.80.0.1
PING 10.80.0.1 (10.80.0.1) 56(84) bytes of data.
64 bytes from 10.80.0.1: icmp_seq=1 ttl=64 time=9.69 ms
64 bytes from 10.80.0.1: icmp_seq=2 ttl=64 time=4.06 ms

(Пинг в обратном направлении не будет проходить, пока мы не разрешим icmp-трафик на вход wg0)

Далее сконфигурируем squid

В данном тестовом случае ограничивать доступ средствами squid мы не будем — только «L7-маршрутизация».

nano /etc/squid/squid.conf:

dns_v4_first on
visible_hostname L7-router
check_hostnames off
max_filedescriptors 65535

acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http

acl CONNECT method CONNECT
acl localnet src 10.10.10.0/24
acl ssl13 dst "/etc/squid/ssl13.txt"
acl redirect_http dstdomain "/etc/squid/redirect.txt"
acl redirect_https ssl::server_name "/etc/squid/redirect.txt"

acl step1 at_step SslBump1

http_port 3128
http_port 3129 intercept
https_port 3130 intercept ssl-bump options=ALL:NO_SSLv3:NO_SSLv2 connection-auth=off cert=/etc/squid/sslcert/squid.pem key=/etc/squid/sslcert/squid.key

sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/ssl_db -M 4MB

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

http_access allow localhost
http_access allow localnet
http_access deny manager
http_access deny all

tcp_outgoing_mark 5 redirect_http
tcp_outgoing_mark 5 redirect_https

ssl_bump peek step1 !ssl13
sslproxy_cert_error allow all
sslproxy_flags DONT_VERIFY_PEER
tls_outgoing_options flags=DONT_VERIFY_PEER
ssl_bump splice all
ssl_bump terminate all

no_cache deny all
always_direct allow localnet
coredump_dir /var/spool/squid
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern .               0       20%     4320
cache deny all
cache_dir aufs /var/spool/squid 2 1 1
maximum_object_size 1 KB
minimum_object_size 1 KB

cache_swap_low 90
cache_swap_high 95
maximum_object_size_in_memory 512 KB
memory_replacement_policy lru
logfile_rotate 2

В SQUID есть директива tcp_outgoing_mark, позволяющая маркировать исходящие пакеты и применять соответствующие правила маршрутизации ip rule для этих пакетов.

домены для альтернативной маршрутизации указываются в файле вида /etc/squid/redirect.txt

.habr.com
.vk.com
.ok.ru
...

Если доступ к указанным ресурсам осуществляется через http, то ресурс обрабатывается через

acl redirect_http dstdomain "/etc/squid/redirect.txt"

Если https, то

acl redirect_https ssl::server_name "/etc/squid/redirect.txt"

При этом домены можно описывать в одном файле.

Squid, в данном случае, работает в режиме intercept (прозрачно). Применяется алгоритм peek and splice. Сервисы, использующие ssl_v1.3 не проходять через peek. К таким сервисам относится Telegram и большиство банк-клиентов. Для этих сервисов создаётся файл ssl13.txt с указанием ip и подсетей вида:

91.108.56.0/22
91.108.4.0/22
...

После чего создаём acl вида:

acl ssl13 dst "/etc/squid/ssl13.txt"

Далее описыаем директивы peek and splice

ssl_bump peek step1 !ssl13 (не подглядываем в заголовок сертификата для ssl_v1.3)
ssl_bump splice all

На основе подсмотренного в заголовке применяем маркировку исходящего пакета

tcp_outgoing_mark 5 redirect_http
tcp_outgoing_mark 5 redirect_https

Следует обратить внимание, что некоторые ресурсы используют разные сертификаты и имена узлов для функционирования сервисов. Если какой-то ресурс работает частично и загружается не до конца, следует покопаться в последних записях /var/log/squid/access.log и посмотреть какие имена узлов ещё задействованы при обращении к сервису.

Пример:

Вы настроили для туннелирования .foo.com и .spam.ru, но ресурсы работают некорректно или вовсе не грузятся, но в логах есть обращения на cdnfoo.com и spam-video.ru, соответственно необходимо добавить записи типа:

.cdnfoo.com
.spam-video.ru

Точка в начале означает «все поддомены верхних уровней»