Заблокировали IP у МТС Exolve: как отправлять запросы к API через прокси на другом сервере

Linux symbolЯ пишу собственный сервис Incident Garden — on-call систему для дежурных девопсов и SRE. Одной из важнейших функций системы являются звонки на мобильные телефоны. Это позволяет разбудить дежурного хоть ночью и это гораздо надежней, чем пуши в ТГ или сообщения на почту.

Телеграм-канал автора Егорыч поясняет за AI Помогаю кожаным и AI жить в любви и понимании — шутим, даём советы, рефлексируем. Подписаться

Запросы к API через прокси на другом сервере

В качестве SIP-провайдера используется МТС Exolve и вот в один прекрасный момент запросы с нашей виртуальной машины в Яндекс.Облаке перестали проходить, хотя с других серверов API отвечал нормально, то есть проблема была именно в связанности сетей двух провайдеров.

В поддержке МТС проблему подтвердили и исправили, но на это ушло два дня, а звонки были нужны сразу. Пришлось городить решение для обхода буквально на коленке: поднял обычный forward-прокси на другом сервере, пустил на него запросы только к API, весь остальной трафик шел как раньше.

В статье расскажу про эксперименты с прокси и то как вы можете у себя это воспроизвести.

Блокировка по IP: с одной ВМ API недоступен, с другой отвечает

Идея

Нужен второй сервер с другим IP с развернутым прокси. В него отправляем только запросы к api.exolve.ru.

Только запросы к API идут через прокси, остальной трафик напрямую

Открытую проксю выставлять в интернет было бы безумием, поэтому ограничим подключения только для одного клиента.

Прокси на базе Squid в Docker

Создайте на втором сервере файл squid.conf (укажите вместо api.exolve.ru любой ваш адрес):

Подробнее о каждой строке:

  • http_port 3128 прокси слушает порт 3128 (значение по умолчанию, выставлено для наглядности).
  • acl vm src <IP_КЛИЕНТА_ПРОКСИ> клиент, которому разрешено пользоваться прокси. Подставьте внешний IP вашей ВМ.
  • acl api dstdomain api.exolve.ru единственный домен, куда можно ходить.
  • acl tls port 443 порт назначения. Без него разрешённый домен открыл бы туннель на любой порт, например api.exolve.ru:8443.
  • http_access allow vm CONNECT api tls разрешаем только туннель CONNECT от нашего клиента к целевому домену на 443 порт.
  • http_access deny all всё остальное запрещено.

Запускаем контейнер:

Проверить синтаксис конфига можно командой docker exec squid squid -k parse. Применить правки без перезапуска позволяет docker exec squid squid -k reconfigure.

Файрвол

Не стоит полагаться только на правила Squid по src, порт лучше закрыть и на сетевом уровне.

Примечание: порт опубликован через -p и обычное правило в цепочке INPUT на него не действует. Docker подменяет адрес назначения ещё до неё, трафик идёт по цепочке FORWARD и правило в INPUT его просто не видит. Используйте цепочку DOCKER-USER, а порт в правиле указывается контейнерный, то есть после подмены:

Если накосячили с правилами, то увидите следующую ошибку. Чужой клиент вместо 403 не получает ничего и ждёт до таймаута:

Создаваемые через iptables правила обычно не переживают перезагрузку сервера, сохраните их иными инструментами, например iptables-save.

Проверяем через curl

Попробуем проверить подключение с исходной виртуалки, для которой мы и строим этот прокси. Ошибки самого сервиса нас тут не волнуют. Если получим их, то как минимум будем уверены, что запрос доходит до конечного сервиса (это 7 уровень OSI). В противном случае получили бы ошибку сетевого уровня/транспортного:

Ответ API:

Теперь запрещённый вызов (другой домен через тот же прокси, вспомните список разрешенных доменов в конфиге сквида выше):

Ожидаемый ответ и уже от прокси:

Тот же текст получит и чужой клиент, пытающийся обратиться даже к api.exolve.ru. Простой http без туннеля тоже отклоняется (последняя строка лога).

Наблюдать происходящее нужно в логе внутри контейнера (да, вот такой вот бред), docker logs покажет только запуск:

Так выглядит лог (видны все случаи: разрешённый, чужой домен, чужой клиент и простой http без туннеля):

Первый столбец это unix-время, третий — IP клиента. TCP_TUNNEL/200 — запрос прошёл, а TCP_DENIED/403 — прокси его отклонил.

Подключаем к сервису

Простейший способ это выставить переменную HTTPS_PROXY, но есть нюанс. Дело в том, что она действует на все HTTPS-запросы процесса, а не только на звонки. Любой другой исходящий вызов тоже пойдёт в прокси и упрётся в наш белый список:

Ответ прокси:

Например в Go такая переменная читается стандартным клиентом (http.DefaultTransport использует ProxyFromEnvironment). Всё, что в сервисе ходит через http.Get и другие клиенты по умолчанию, сломается. Исключения можно перечислить в NO_PROXY, но это путь в никуда.

Надёжнее прописать прокси только в том HTTP-клиенте, который общается с целевым сервисом и чтобы он сам управлял маршрутом. Например на том же Go это будет выглядеть вот так:

Запросы к Exolve отправляем через exolve, остальной код по-прежнему пользуется http.DefaultClient и ходит напрямую. Clone() сохраняет таймауты и пул соединений стандартного транспорта, а Proxy подменяет только адрес прокси.

Если IP сервиса сменится, а прокси защищён только правилами Squid, клиент получит:

С правилом файрвола запрос повиснет до таймаута клиента, поэтому таймаут в клиенте обязателен:

Вместо вывода

Для меня было большим сюрпризом, что между двумя огромными провайдерами могут быть такие проблемы с сетевой связанностью и что для их устранения может потребоваться почти двое суток.

С тех пор для подобных случаев держу под боком сценарий с прокси.

Яндекс.Метрика