dumbproxy уже упоминался на Хабре в одной из моих прошлых статей. Это HTTP(S) прокси-сервер, который работает через TLS, напрямую совместим с браузерами и браузерными расширениями, и имеет заметное количество дополнительных функций, расширяющих его границы применимости и облегчающих его использование.
Новые возможности
За последние месяцы проект заметно подрос. Пройдёмся по новым функциям!
Аутентификация
В дополнение к базовой аутентификации по логину-паролю и аутентификации по сертификатам, появилась возможность использовать HMAC-коды, передаваемые как логин и пароль. В первом приближении это похоже на JWT с HS256-подписями, но в более компактном, бинарном формате.
Это позволяет использовать централизованную авторизацию на множестве экземпляров dumbproxy без нужды взаимодействовать с центральным сервисом авторизации на каждый запрос доступа. Клиент самостоятельно получает от центрального сервиса логин и пароль, который будет действовать какое-то время, при этом такой логин и пароль нигде не хранится и передаётся серверу dumbproxy клиентом в момент запроса и может быть верифицирован без сетевых взаимодействий с центральным сервером авторизации. Эта возможность потенциально может заинтересовать разработчиков прокси-сервисов на базе dumbproxy и позволяет использовать его как прокси-бэкенд в таких сервисах.
Из менее значительного: добавился блэклист сертификатов при использовании авторизации по сертификатам.
Скриптинг
Некоторые стадии обработки запроса в dumbproxy теперь могут быть определены скриптами на JS. На данный момент это фильтрация запросов (разрешение или запрещение доступа) и выбор вышестоящего прокси или исходящего сетевого интерфейса.
Фильтрация запросов
Проиллюстрируем, как это выглядит на практике. Для примера, вот как выглядит access-скрипт, повторяющий правила из стандартного конфига Squid, которые ограничивают подключения только на "безопасные" порты:
// Deny unsafe ports for HTTP and non-SSL ports for HTTPS.
const SSL_ports = [
443,
]
const Safe_ports = [
80, // http
21, // ftp
443, // https
70, // gopher
210, // wais
280, // http-mgmt
488, // gss-http
591, // filemaker
777, // multiling http
]
const highPortBase = 1025
function access(req, dst, username) {
if (req.method == "CONNECT") {
if (SSL_ports.includes(dst.port)) return true
} else {
if (dst.port >= highPortBase || Safe_ports.includes(dst.port)) return true
}
return false
}
Для его применения достаточно задать опцию -js-access-filter
с путём к скрипту.
Выбор вышестоящего прокси или исходящего адреса
Работает аналогично предыдущему, рассмотрим примеры.
Нужно отправить onion-домены в Tor? Запросто.
// Redirect .onion hidden domains to Tor SOCKS5 proxy
function getProxy(req, dst, username) {
if (dst.originalHost.replace(/.$/, "").toLowerCase().endsWith(".onion")) {
return "socks5://127.0.0.1:9050"
}
return ""
}
Размазать трафик по нескольким машинам с переменой адреса раз в 10 минут? Легко.
const serverList = [
"https://USERNAME:PASSWORD@m1.example.com:443",
"https://USERNAME:PASSWORD@m2.example.com:443",
"https://USERNAME:PASSWORD@m3.example.com:443",
"https://USERNAME:PASSWORD@m4.example.com:443",
"https://USERNAME:PASSWORD@m5.example.com:443",
"https://USERNAME:PASSWORD@m6.example.com:443",
"https://USERNAME:PASSWORD@m7.example.com:443",
"https://USERNAME:PASSWORD@m8.example.com:443",
]
const rotationPeriod = 600 // 10 minutes
function getProxy() {
const ts = Math.floor(Date.now() / 1000)
return serverList[Math.floor(ts / rotationPeriod) % serverList.length]
}
Отправить трафик на какие-то домены в VPN-туннель? Ну... если правила PBR насчёт выбора таблицы маршрутизации на основе исходящего адреса уже готовы, то пожалуйста.
function getProxy(req, dst, username) {
const normalizedHostname = dst.originalHost.replace(/.$/, "").toLowerCase()
if (normalizedHostname === "2ip.ru" || normalizedHostname.endsWith(".2ip.ru")) {
return "set-src-hints://?hints=10.2.0.2"
}
return ""
}
Кэширование выписываемых сертификатов
dumbproxy поддерживает автоматический выпуск сертификатов на своё доменное имя достаточно давно, и для этого использовался простой кэш сертификатов в локальной директории. Однако теперь возможности в этой области расширены:
-
Теперь в качестве хранилища можно использовать Redis и Redis Cluster.
-
Опционально доступна кэширующая прослойка в памяти процесса.
-
Опционально доступно шифрование кэша AEAD-шифрованием (XChaCha20Poly1305)
Вынос кэша сертификатов во внешнее хранилище прокладывает путь к stateless-развёртываниям. То есть сервер не хранит никакого состояния локально и может быть удалён/пересоздан хоть каждые 10 минут без проблем с (пере)выпуском сертификатов. К слову, бесплатные инстансы редиса на https://cloud.redis.io/ неплохо справляются, файл с опциями выглядит примерно так:
OPTIONS=-auth basicfile://?path=/var/lib/dumbproxy/dumbproxy.htpasswd
-autocert
-autocert-local-cache-ttl 24h
-autocert-cache-redis redis://default:xxxxxxxx@redis-16273.c528.europe-west3-1.gce.redns.redis-cloud.com:16273/0
DUMBPROXY_CACHE_ENC_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Функции для операторов крупных инсталляций
Локальный кэш DNS
Появилась опция, которая принудительно кэширует соответствия доменных имён адресам. Главным образом она нужна операторам прокси с множеством клиентов, которые почему-то используют публичные DNS-резолверы типа 8.8.8.8 и 1.1.1.1, и получают от них негативные ответы за большую частоту запросов.
Я бы вместо этого лучше посоветовал бы развернуть локальный knot-resolver или unbound с кэшом, но не для всех это приемлемо.
Лимит полосы пропускания для каждого пользователя
Эта функция полезна для тех же самых операторов крупных инсталляций, чтобы более честно распределить канал между пользователями, да и в целом сделать сверхпотребление (например, ютуб в 4к) непрактичным.
Есть ещё несколько мелочей, о которых я не упомянул, но лучше оставим это внимательным читателям документации и поговорим о развёртывании.
Новые развёртывания
А развёртывание dumbproxy с авторизацией и общепризнаваемыми TLS-сертификатами теперь совсем простое. Не нужно даже заходить на сервер по SSH!
Для хостеров, которые поддерживают cloud-init (24.3.1 или новее, берите ubuntu 24.10 — не ошибётесь), достаточно при создании виртуалки указать следующий конфиг (не забудьте заменить USERNAME
и PASSWORD
на настоящие!):
## template: jinja
#cloud-config
write_files:
- source:
{% if v1.machine == 'aarch64' %}
uri: https://github.com/SenseUnit/dumbproxy/releases/download/v1.20.0/dumbproxy.linux-arm64
{% else %}
uri: https://github.com/SenseUnit/dumbproxy/releases/download/v1.20.0/dumbproxy.linux-amd64
{% endif %}
path: /usr/local/bin/dumbproxy
permissions: '0755'
- content: |
OPTIONS=-auth basicfile://?path=/etc/dumbproxy.htpasswd -autocert -bind-address :443
path: /etc/default/dumbproxy
- content: |
[Unit]
Description=Dumb Proxy
Documentation=https://github.com/SenseUnit/dumbproxy/
After=network.target network-online.target
Requires=network-online.target
[Service]
EnvironmentFile=/etc/default/dumbproxy
User=root
Group=root
ExecStart=/usr/local/bin/dumbproxy $OPTIONS
TimeoutStopSec=5s
PrivateTmp=true
ProtectSystem=full
LimitNOFILE=20000
[Install]
WantedBy=default.target
path: /etc/systemd/system/dumbproxy.service
runcmd:
- [dumbproxy, -passwd, /etc/dumbproxy.htpasswd, USERNAME, PASSWORD]
- [systemctl, daemon-reload]
- [systemctl, enable, --now, --no-block, dumbproxy.service]
На всякий случай, если возникнут проблемы с ориентированием, окошко для cloud-init-а обычно выглядит примерно так:
Остаётся навесить на IP-адрес машины доменное имя и всё готово. Если доменного имени нет, то вот списочек с идеями где его взять:
-
https://www.dnshome.de/ — выбирать только поддомены *.dnshome.de, на остальных могут быть проблемы с лимитами на выпуски сертификатов.
Можно сказать, что этот список для бесплатных доменных имён исчерпывающий. Любые другие (например nip.io) не присутствуют в public domain suffix list-е и будут иметь проблемы с выпусками сертификатов из-за лимитов.
Вспомнить, как настраивать клиенты, можно в прошлом посте (архив).
Что ещё?
Автор: YourChief