Динамическое перенаправление SSH как SOCKS-прокси поверх вашего VPN
У приложения, которое говорит только на SOCKS, в обычном VPN нет прокси-эндпоинта. Два перехода, один локальный слушатель и один поиск имени, который сервер SSH должен выполнить сам.
Приложению, которое говорит только на SOCKS, не с кем разговаривать в обычном VPN, потому что туннель переносит пакеты, а приложению нужен прокси-эндпоинт. OpenSSH может предоставить эту точку, если владелец доступа разрешает, поскольку динамическое перенаправление превращает одну сессию SSH в локальный прокси. Дальше есть два перехода, которые нужно держать раздельно: туннель, достигающий хоста SSH, и соединение, которое этот хост открывает дальше. Их разделение объясняет и то, почему схема работает, и то, чего она никогда не сделает.
Чем слушатель SSH становится, а чем нет
Динамическое перенаправление заставляет клиент SSH слушать локально и говорить SOCKS4 или SOCKS5 со всем, что подключается. Каждый запрос называет пункт назначения, и сервер SSH открывает следующее соединение, поэтому поиск пункта назначения может происходить на дальней стороне. Это перенаправление TCP на уровне приложения, а не VPN для всего устройства. Оно не несёт ни UDP-ассоциации, ни ICMP, ни чего-либо для приложений, которые полностью игнорируют настройки прокси. Переход от сервера SSH к приложению тоже не находится внутри вашего туннеля, что важно, когда пункт назначения принимает трафик только из известных сетей.
Предварительные условия реальны: маршрут до хоста SSH, авторизованная учётная запись, проверенный ключ хоста сервера и разрешение на перенаправление. Ни одно из них не создаёт прокси, а последнее обычно отсутствует первым. Подтвердите все четыре, прежде чем разбираться с чем-то ещё, потому что со стороны клиента сбой выглядит одинаково, какого бы ни не хватало.
Запустите его на loopback с явным портом
Выберите свободный локальный порт и явно привяжите его к loopback, а не оставляйте адрес не указанным. Три варианта ниже существуют каждый по одной причине.
ssh -D 127.0.0.1:1080 -NT -o ControlPath=none -o ExitOnForwardFailure=yes \
[email protected]
-N пропускает удалённую команду, а -T пропускает псевдотерминал, поэтому сессия существует только для перенаправления. ExitOnForwardFailure заставляет клиент завершиться, когда слушателя нельзя создать, вместо работы без запрошенного перенаправления. ControlPath=none держит сессию вне существующего мультиплексированного соединения, которое может добавить свои перенаправления. Loopback держит слушатель вне LAN, но на общей машине это не граница авторизации, потому что другие локальные процессы тоже могут до него добраться. Справочник клиента OpenSSH документирует все эти флаги, а предупреждение о loopback следует из того, что такое слушающий сокет.
Отправляйте имена через прокси, а не в обход него
Клиент SOCKS5 может разрешить пункт назначения сам или попросить об этом прокси, и этот выбор определяет, какой резолвер ответит. С socks5h curl отправляет имя хоста через прокси, поэтому поиск происходит на сервере SSH. С обычным socks5 ноутбук разрешает первым, и путешествует только соединение, что тихо обесценивает смысл, когда имя внутреннее. Запрос ниже также очищает список без прокси, чтобы унаследованное значение NO_PROXY не могло провести его в обход прокси.
curl -sS --noproxy '' --proxy socks5h://127.0.0.1:1080 https://app.corp.example/health
Проверка сертификата и собственный вход приложения по-прежнему действуют, потому что прокси не заменяет ни то, ни другое. Остаются два поиска: ноутбук должен достичь хоста SSH по разрешённому пути VPN и DNS, а сервер SSH должен сам разрешить имя приложения. Второй можно проверить отдельно, прежде чем через прокси пойдёт любой трафик.
ssh [email protected] getent hosts app.corp.example
Докажите один разрешённый пункт назначения, затем закройте сессию
Проверьте пункт назначения, который политика разрешает, и тот, который она должна отклонить, потому что работающее перенаправление доказывает только то, что слушатель работает, и ничего больше. Приложение видит адрес источника, который выбирает сервер SSH, и это не адрес туннеля вашего ноутбука. Там, где дальняя сторона журналирует соединения по источнику, именно это различие и нужно проверить первым, когда запрос отклонён.
Остановите сессию через процесс, который ею владеет, вместо того чтобы убивать постороннюю оболочку. Затем убедитесь, что слушатель исчез, потому что порт, который всё ещё отвечает, — это сессия, которую вы на самом деле не закрыли. Удалите только настройку прокси, которую ввёл этот пример, и оставьте остальную конфигурацию приложения в покое. Терминал без присмотра с правилом перенаправления — не сервис, и постоянному прокси нужен одобренный жизненный цикл, а не забытое окно.
ss -ltn | grep 1080
Сбои, которые эта схема исправить не может
Некоторые симптомы указывают прямо на ограничения выше, и раннее их распознавание экономит полдня отладки прокси. Таблица связывает то, что вы видите, с тем, что это обычно означает. Всё в последнем столбце — вопрос проектирования, а не ошибка конфигурации.
| What you see | What it usually means | Where to look |
|---|---|---|
| The app connects and nothing follows | It ignores proxy settings entirely | Its own network settings, not the SSH session |
| A name resolution error for the destination | The SSH server cannot resolve it | Server resolver and search domains |
| A UDP-only application fails | Dynamic forwarding carries TCP only | This arrangement cannot serve it |
| curl works, the browser does not | The browser is not using the socks5h endpoint | Browser proxy configuration |
| The listener answers after the session ended | Another process owns port 1080 | The ss output, then the owning process |
Первые три строки — те, что люди диагностируют неверно, потому что каждая даёт попытку соединения вместо ошибки о самом прокси. Имя, которое разрешается на ноутбуке, ничего не говорит о резолвере сервера, и сбой читается скорее как проблема сертификата или тайм-аута. Приложения только с UDP стоят вне схемы, а не сломаны внутри неё, и никакая дополнительная опция ssh этого не меняет. Строка о браузере — обычное напоминание, что настройки прокси задаются для каждого приложения. Последняя строка — причина, по которой существует проверка при закрытии: порт, который всё ещё слушает, означает, что теперь им владеет что-то другое.