Цепочка из двух портов ssh

Цепочка из двух портов ssh

Я просматривал ответы здесь, на superuser, однако не нашел ни одного ответа, который соответствовал бы моему варианту использования.

Я хочу объединить две команды, которые я использую, чтобы проложить туннель через него:

ssh -L 22:hostB:22 user@hostA

и

ssh -L 10002:localhost:10001 user@localhost

Сейчас я ввожу первую команду, открываю другой терминал и ввожу вторую. Если я делаю что-то вроде

ssh … && ssh …

вторая команда выполняется только после того, как я «выйду» из первого соединения.

Более продвинутые параметры SSH, такие как jumphost, не работают из-за конфигурации сервера на hostA, поэтому я почти уверен, что это единственный вариант сделать порт 10001 доступным таким образом.

Есть ли способ объединить эти две команды в один псевдоним или использовать какой-нибудь простой bash-скрипт с таймаутами и т. д.?

решение1

Я предполагаю, что вы не можете сделать это просто ssh -L 10002:hostB:10001 user@hostAпотому, что

  • либо все, что прослушивает B, 10001привязывается только к локальному интерфейсу,
  • или брандмауэр на B запрещает подключение к hostB:10001A,
  • или что-то в этом роде.

ssh … && ssh …

вторая команда выполняется только после того, как я «выйду» из первого соединения.

Да, это нормально. Часть перед &&должна завершиться и вернуть статус выхода, прежде чем будет запущена следующая команда (или нет, в зависимости от статуса).

Это почти сразу же запустит второй код, sshно он некорректен:

( сш … & ) && сш …

Он несовершенен, потому что первый sshне влияет на второй. Второй, вероятно, запустится задолго до того, как первый установит туннель. Запрос паролей также может стать проблематичным.

Для этого варианта использования sshесть -fвариант. Отman 1 ssh:

-f
Запрашивает sshпереход в фоновый режим непосредственно перед выполнением команды. […]

Итак, основная команда может быть такой:

ssh -fNL 22:hostB:22 user@hostA && ssh -NL 10002:localhost:10001 user@localhost

Вам решать, хотите ли вы использовать -Nвторой sshили нет. Теперь трюк в том, что первый sshпереходит в фоновый режим после запроса пароля (если применимо) и после установления первого туннеля, так что временно есть два "первых" sshпроцесса. Тот, что в фоновом режиме, начинает обрабатывать туннель; тот, что на переднем плане, завершает работу, это позволяет &&работать.

Вы, вероятно, хотите -o ExitOnForwardFailure=yes. Если первый туннель не может быть установлен, первый sshзавершится неудачно, а второй не будет запущен. Улучшенная команда:

ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
    user@hostA &&
ssh -NL 10002:localhost:10001 user@localhost

Обратите внимание, что когда второй процесс sshзавершается, первый (в фоновом режиме) остается. Преимущество в том, что вы можете запустить единственный второй процесс sshпозже. Недостаток в том, что вам придется вручную завершить первый процесс, если вы не хотите, чтобы он оставался. Имея это в виду, рассмотрите следующий подход:

ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
    user@hostA
ssh -NL 10002:localhost:10001 user@localhost

Обратите внимание, что нет &&. Теперь, если старый первый sshвсе еще работает, то новый потерпит неудачу, потому что он не сможет привязаться к порту. Второй sshбудет работать независимо, он будет использовать первый туннель, и неважно, насколько старый туннель.

Может случиться, что старого нет ssh, и первый sshпо каким-то причинам выходит из строя. Если так, то второй будет работать и, вероятно, выйдет из строя (соединение отклонено), потому что порт никто не слушает 22.

Тем не менее, вы можете не захотеть оставлять "простой" sshпроцесс, вы можете захотеть завершить его автоматически после второго sshвыхода. Очевидно, killall sshчто это может вызвать сопутствующий ущерб. Давайте найдем что-то более тонкое:

-M
Переводит sshклиент в режим «главного» для совместного использования подключения. […]

[…]

-O ctl_cmd
Управление активным главным процессом мультиплексирования соединений. […]

[…]

-S ctl_path
Указывает местоположение управляющего сокета для совместного использования соединения […]

#!/bin/sh

socket=/tmp/hostA.socket

ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
    -MS "$socket"
    user@hostA &&
{
ssh -NL 10002:localhost:10001 user@localhost
ssh -S "$socket" -O exit dummy
}

После завершения второй команды sshтретья прикажет первой выйти. В третьей sshкоманде важен сокет, а не имя хоста; все равно нужно указать какое-то имя хоста, поэтому dummy. Первая команда sshудалит сокет перед выходом, мусора не должно остаться.

Остается еще несколько проблем:

  • Если скрипт прервется, то sshостанется первый. Либо сбросьте &&, либо отправьте -O exitв самом начале на всякий случай (он будет нулевым ssh).
  • Если первый sshвнезапно умрет, сокет может остаться. Это заставит любой новый первый sshвыйти из строя. Хорошей идеей может быть расширение нулевого sshследующим образом:

    ssh -S "$socket" -O exit dummy || rm "$socket"
    
  • /tmp/hostA.socketможет быть не лучшим местом для сокета. В ssh_configэквиваленте -Sопции командной строки есть ControlPath. Смотрите, чтоman 5 ssh_configговорит об этом:

    Рекомендуется, чтобы любой файл, ControlPathиспользуемый для совместного использования соединений, включал как минимум %h, %p, и %r(или альтернативно %C) и был помещен в каталог, недоступный для записи другими пользователями.

    «Каталог, недоступный для записи другим пользователям» может быть /run/user/$UID, если ваша ОС поддерживает это (я полагаю, это systemdособенность ).

Связанный контент