VPN не блокирует psql: ваш клиент обращается в другое место
psql и GUI-клиент могут обращаться к разным конечным точкам с одной машины. Сравните хост, сокет, роль и TLS, прежде чем винить VPN.
Оба клиента находятся на одном ноутбуке, проходят через один и тот же туннель, и подключается только один из них. Это сравнение кажется доказательством избирательности VPN, хотя куда более частое объяснение состоит в том, что два клиента обращаются к разным адресам. GUI хранит собственные хост, порт, роль и настройки TLS и тихо строит SSH-перенаправление, когда оно нужно, а psql собирает соединение из значений libpq по умолчанию, которые ваша оболочка, возможно, уже задала. Воспроизвести работающее соединение — значит прочитать настройки GUI, а не переустанавливать туннель.
Миф: один работающий клиент доказывает путь
Если GUI подключается, туннель передаёт трафик, и это единственное, что доказывает GUI. Он ничего не говорит о том, какой адрес разрешает psql, под какой ролью проходит аутентификация и покидает ли машину открытый им сокет. Путь общий; параметры — нет.
Локальное перенаправление увеличивает разрыв. GUI может подключаться к 127.0.0.1 на порту, который он перенаправил через SSH, и этот слушатель существует только пока длится сеанс GUI. psql, направленный на localhost без перенаправления, получает отказ в соединении, не имеющий отношения к VPN.
Прочитайте конечную точку, которую GUI действительно использует
Откройте свойства соединения и скопируйте четыре значения: хост, порт, базу данных и роль. Затем поищите вкладку туннеля, потому что SSH-перенаправление превращает частный адрес в локальный порт и меняет то, что нужно сообщить psql.
| Setting | What the GUI holds | What psql uses when the value is absent |
|---|---|---|
| Destination | A saved hostname, sometimes behind a forward | A local Unix socket, not the remote endpoint |
| Port | The direct port or a forwarded local port | 5432, or the port from a service file |
| Role and database | Stored per connection profile | Your operating-system user name |
| TLS identity | Its own CA bundle and verification mode | Whatever PGSSLMODE or the service file says |
| Startup files | Not read at all | Read unless -X is passed |
Каждая строка терпит неудачу на своём уровне, и полученное сообщение называет этот уровень. Вставка дружественного имени сохранённого профиля в psql не передаёт ни одного из перечисленных значений, а имя службы разрешается через файл, который терминал, возможно, никогда не прочитает.
Что libpq наследует от вашей оболочки
psql — тонкий клиент поверх libpq, и libpq читает окружение раньше всего остального. Переменные вроде PGHOST, PGHOSTADDR, PGPORT, PGDATABASE, PGUSER, PGSERVICE и PGSSLMODE могут каждая перенаправить часть соединения. Они наследуются, а не вводятся, поэтому ничто в выполненной команде их не показывает. Экспортированная PGSSLMODE тоже может изменить то, что принимает клиент, без единого слова в командной строке.
Две из этих переменных заслуживают подозрения в первую очередь. PGHOSTADDR задаёт числовой адрес, который обходит разрешение имён, пока имя хоста по-прежнему определяет идентичность сертификата, а PGSERVICE вытаскивает хост, базу данных и выбор TLS из файла службы. Выгружать всё окружение для отладки — дурная привычка, так как в нём могут быть пароли и посторонние токены; лучше выберите нужные переменные.
Пропуск и host, и hostaddr выбирает сокет домена Unix, а не сетевой адрес. Поэтому вызов psql без -h может локально завершиться успешно или с ошибкой сокета, так и не коснувшись маршрута, который использует GUI.
Передайте те же параметры вручную
Имея значения GUI, передайте их явно и пропустите файл запуска, который мог бы добавить переопределения. Строка подключения ниже приведена для примера: замените конечную точку, порт, базу данных и роль значениями, утверждёнными вашим администратором, и сохраните конфигурацию доверия, которая уже работает.
psql -X "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5"Три детали в этой команде намеренны. -X подавляет файл запуска psql, хотя не подавляет окружение libpq или определение службы. connect_timeout=5 не даёт маршруту в никуда подвесить терминал на минуты. sslmode=verify-full оставляет включёнными проверки сертификата и имени хоста, а это именно та часть, которую люди отключают при появлении ошибки TLS.
Если утверждённая конечная точка стоит за пулером, порт 6432 и порт 5432 могут достигать одних и тех же данных через разные идентичности сервера, и проверка может не пройти из-за имени пулера, а не базы данных. Исправьте имя в строке подключения вместо ослабления режима.
Пусть сообщение об ошибке выбирает следующий шаг
Текст сбоя говорит, как далеко продвинулась попытка, и это лучше любого предположения о туннеле. Имя, которое не разрешается, терпит неудачу ещё до появления сокета, тайм-аут означает, что маршрут или брандмауэр отбросил пакеты, отказ означает, что что-то ответило и отклонило, а ошибка аутентификации означает, что сеть уже работала.
psql -X -w "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5" -c '\conninfo'
env | grep -E '^PG'
psql --versionСравните вывод \conninfo с живым сеансом GUI: тот же хост, тот же порт, та же роль, то же состояние TLS. Когда эти четыре совпадут, VPN больше нечего объяснять. Пока они не совпали, любое дополнительное изменение туннеля — догадка о соединении, которое терминал никогда не устанавливал.