dumbproxy — что нового?

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-а обычно выглядит примерно так:

5f923c8aa59e92535b5e926c0083c781.png

Остаётся навесить на IP-адрес машины доменное имя и всё готово. Если доменного имени нет, то вот списочек с идеями где его взять:

  1. https://freemyip.com/

  2. https://www.dnshome.de/ — выбирать только поддомены *.dnshome.de, на остальных могут быть проблемы с лимитами на выпуски сертификатов.

  3. https://www.duckdns.org/

  4. https://dynv6.com/

  5. https://www.noip.com/

  6. https://now-dns.com/

  7. https://www.nsupdate.info/

Можно сказать, что этот список для бесплатных доменных имён исчерпывающий. Любые другие (например nip.io) не присутствуют в public domain suffix list-е и будут иметь проблемы с выпусками сертификатов из-за лимитов.

Вспомнить, как настраивать клиенты, можно в прошлом посте (архив).

Что ещё?

Habrahabr.ru прочитано 1584 раза