Визначте динамічно розподілений порт для OpenSSH RemoteForward


13

Питання (TL; DR)

Коли динамічно призначається порти для віддаленого переадресації (також -Rопція), як скрипт на віддаленій машині (наприклад, отриманий з .bashrc) визначає, які порти було обрано OpenSSH?


Фон

Я використовую OpenSSH (з обох кінців) для підключення до нашого центрального сервера, яким я ділюся з кількома іншими користувачами. Для мого віддаленого сеансу (зараз) я хотів би переслати X, чашки та pulseaudio.

Найбільш тривіальним є пересилання X, використовуючи -Xопцію. Виділена X-адреса зберігається в змінній навколишнього середовища, DISPLAYі з цього можу визначити відповідний порт TCP, у більшості випадків у будь-якому випадку. Але мені навряд чи потрібно, бо Xlib шанує DISPLAY.

Мені потрібен аналогічний механізм для чашок і пульс-аудіо. Основи для обох служб існують у вигляді змінних навколишнього середовища CUPS_SERVERі PULSE_SERVER, відповідно. Ось приклади використання:

ssh -X -R12345:localhost:631 -R54321:localhost:4713 datserver

export CUPS_SERVER=localhost:12345
lowriter #and I can print using my local printer
lpr -P default -o Duplex=DuplexNoTumble minutes.pdf #printing through the tunnel
lpr -H localhost:631 -P default -o Duplex=DuplexNoTumble minutes.pdf #printing remotely

mpg123 mp3s/van_halen/jump.mp3 #annoy co-workers
PULSE_SERVER=localhost:54321 mpg123 mp3s/van_halen/jump.mp3 #listen to music through the tunnel

Проблема в налаштуванні CUPS_SERVERта PULSE_SERVERкоректності.

Ми багато використовуємо переадресацію портів, тому мені потрібні динамічні розподіли портів. Статичні порти не є варіантом.

OpenSSH має механізм динамічного розподілу портів на віддаленому сервері, вказавши 0як порт прив'язки для віддаленого переадресації ( -Rопція). Використовуючи наступну команду, OpenSSH динамічно розподіляє порти для чашок та пересилання імпульсів.

ssh -X -R0:localhost:631 -R0:localhost:4713 datserver

Коли я використовую цю команду, sshнадрукую наступне на STDERR:

Allocated port 55710 for remote forward to 127.0.0.1:4713
Allocated port 41273 for remote forward to 127.0.0.1:631

Є інформація, яку я хочу! Зрештою, я хочу створити:

export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710

Однак повідомлення "Виділений порт ..." створюються на моїй локальній машині та надсилаються до STDERRяких я не можу отримати доступ на віддаленій машині. Як не дивно, але, схоже, OpenSSH не має коштів для отримання інформації про переадресацію портів.

Як я отримаю цю інформацію, щоб помістити її в сценарій оболонки для адекватного встановлення CUPS_SERVERта PULSE_SERVERвіддаленого хоста?


Мертві закінчується

Єдине легке, що я міг знайти, - це підвищена багатослівність, sshdпоки ця інформація не може бути прочитана з журналів. Це неможливо, оскільки ця інформація розкриває набагато більше інформації, ніж розумно зробити доступною для некореневих користувачів.

Я думав над виправленням OpenSSH для підтримки додаткової послідовності евакуації, яка друкує добре представлення внутрішньої структури permitted_opens, але навіть якщо це те, що я хочу, я все ще не можу сценарій доступу до клієнтських послідовностей виходу з боку сервера.


Має бути кращий спосіб

Наступний підхід видається дуже нестабільним і обмежується одним таким сеансом SSH на користувача. Однак мені потрібно щонайменше два паралельних таких сеансів та інших користувачів ще більше. Але я спробував ...

Коли зірки вирівняні належним чином, пожертвувавши куркою чи двома, я можу зловживати тим фактом, який sshdне починається моїм користувачем, але скидає привілеї після успішного входу, щоб це зробити:

  • отримати список номерів портів для всіх прослуховувальних сокетів, що належать моєму користувачеві

    netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'

  • отримати список номерів портів для всіх прослуховувальних сокетів, які належать до процесів, які розпочав мій користувач

    lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'

  • Всі порти , які перебувають в першому сеті, але не в другому сеті мають високу ймовірність бути мої експедиційні порти, і в самому справі віднімаючи врожайності набори 41273, 55710і 6010; чашки, пульс і Х відповідно.

  • 6010ідентифікується як X-порт, що використовує DISPLAY.

  • 41273- порт чашок, тому що lpstat -h localhost:41273 -aповертається 0.
  • 55710є імпульсний порт, тому що pactl -s localhost:55710 statповертається 0. (Він навіть друкує ім'я хоста мого клієнта!)

(Щоб зробити встановлену субстракцію I sort -uі зберегти висновок з вищевказаних командних рядків і використовувати commдля виконання субстракції.)

Pulseaudio дозволяє мені ідентифікувати клієнта, і для всіх намірів і цілей це може слугувати якорем для окремих сеансів SSH, які потребують розділення. Тим НЕ менше, я не знайшов спосіб зв'язати 41273, 55710і 6010до того ж sshdпроцесу. netstatне розголошуватимуть цю інформацію для некористуючих користувачів. Я отримую тільки -в PID/Program nameколонці , де я хотів би читати 2339/54(в даному конкретному випадку). Дуже близько ...


fwiw, точніше сказати, що netstatне покаже вам PID для процесів, якими ви не володієте, або які є простором ядра. Наприклад
Братчлі

Найбільш надійним способом було б виправити sshd ... Швидкий та брудний патч - це лише кілька рядків у тому місці, де сервер отримує свій локальний порт від ОС, записуючи номер порту у файл, ім'я, створене від користувача, віддаленого хоста та порт. Якщо припустити, що сервер знає порт на стороні клієнта, що не певно, можливо, навіть не вірогідно (інакше функція вже існуватиме).
Гайда

@hyde: точно. Віддалений сервер не знає про переадресовані порти. Він просто створює кілька прослуховувань розеток і дані передаються через ssh-з'єднання. Він не знає про місцеві порти призначення.
Банангуін

Відповіді:


1

Візьміть два (див. Історію для версії, яка робить scp з боку сервера і трохи простіша), це слід зробити. Суть її полягає в цьому:

  1. передайте змінну середовища від клієнта до сервера, повідомляючи серверу, як він може виявити, коли інформація про порт доступна, а потім отримати та використовувати його.
  2. як тільки інформація про порт буде доступна, скопіюйте її з клієнта на сервер, дозволяючи серверу отримати її (за допомогою частини 1 вище) та скористайтеся нею

По-перше, налаштування на віддаленій стороні, вам потрібно включити надсилання змінної env у конфігурації sshd :

sudo yourfavouriteeditor /etc/ssh/sshd_config

Знайдіть рядок AcceptEnvі додайте MY_PORT_FILEдо нього (або додайте рядок під правим Hostрозділом, якщо його ще немає). Для мене рядок став таким:

AcceptEnv LANG LC_* MY_PORT_FILE

Також не забудьте перезапустити sshd, щоб це набуло чинності.

Крім того, для роботи нижче сценаріїв, робіть mkdir ~/portfilesна віддаленій стороні!


Потім на локальній стороні, фрагмент сценарію, який буде

  1. створити ім'я файлу temp для перенаправлення stderr
  2. залиште фонове завдання, щоб дочекатися вмісту файла
  3. передайте ім'я файлу серверу як змінну env, перенаправляючи ssh stderr у файл
  4. фонове завдання продовжується скопіювати тимчасовий файл stderr на сторону сервера, використовуючи окремий scp
  5. фонове завдання також копіює файл прапора на сервер, щоб вказати, що файл stderr готовий

Фрагмент сценарію:

REMOTE=$USER@datserver

PORTFILE=`mktemp /tmp/sshdataserverports-$(hostname)-XXXXX`
test -e $PORTFILE && rm -v $PORTFILE

# EMPTYFLAG servers both as empty flag file for remote side,
# and safeguard for background job termination on this side
EMPTYFLAG=$PORTFILE-empty
cp /dev/null $EMPTYFLAG

# this variable has the file name sent over ssh connection
export MY_PORT_FILE=$(basename $PORTFILE)

# background job loop to wait for the temp file to have data
( while [ -f $EMPTYFLAG -a \! -s $PORTFILE ] ; do
     sleep 1 # check once per sec
  done
  sleep 1 # make sure temp file gets the port data

  # first copy temp file, ...
  scp  $PORTFILE $REMOTE:portfiles/$MY_PORT_FILE

  # ...then copy flag file telling temp file contents are up to date
  scp  $EMPTYFLAG $REMOTE:portfiles/$MY_PORT_FILE.flag
) &

# actual ssh terminal connection    
ssh -X -o "SendEnv MY_PORT_FILE" -R0:localhost:631 -R0:localhost:4713 $REMOTE 2> $PORTFILE

# remove files after connection is over
rm -v $PORTFILE $EMPTYFLAG

Потім фрагмент для віддаленої сторони, придатний для .bashrc :

# only do this if subdir has been created and env variable set
if [ -d ~/portfiles -a "$MY_PORT_FILE" ] ; then

       PORTFILE=~/portfiles/$(basename "$MY_PORT_FILE")
       FLAGFILE=$PORTFILE.flag
       # wait for FLAGFILE to get copied,
       # after which PORTFILE should be complete
       while [ \! -f "$FLAGFILE" ] ; do 
           echo "Waiting for $FLAGFILE..."
           sleep 1
       done

       # use quite exact regexps and head to make this robust
       export CUPS_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:631[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       export PULSE_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:4713[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       echo "Set CUPS_SERVER and PULSE_SERVER"

       # copied files served their purpose, and can be removed right away
       rm -v -- "$PORTFILE" "$FLAGFILE"
fi

Примітка . Наведений вище код, звичайно, не дуже ретельно перевірений і може містити всілякі помилки, помилки копіювання та вставки тощо. Кожен, хто його використовує, також краще зрозуміє це, використовуйте на свій страх і ризик! Я перевірив його, використовуючи просто localhost-з'єднання, і він працював на мене, в моїй тестовій середовищі. YMMV.


Що, звичайно, вимагає, що я можу scpвід віддаленої сторони до місцевої сторони, а що я не можу. У мене був аналогічний підхід, але я перейшов sshдо фонового режиму після встановлення з'єднання, а потім надішле цей файл з локального на віддалений через, scpа потім витягніть sshклієнта на передній план і запустіть сценарій на віддаленій стороні. Я не придумав, як красиво скриптувати фонове зображення та випереджати місцеві та віддалені процеси. Обгортання та інтеграція локального sshклієнта з деякими віддаленими сценаріями, схоже, не є хорошим підходом.
Банангуїн

Ага. Я думаю , ви повинні фон на стороні клієнта УПП тільки: (while [ ... ] ; do sleep 1 ; done ; scp ... )&. Потім зачекайте на передньому плані на сервері .bashrc(припускаючи, що клієнт надсилає правильну змінну env), щоб файл з'явився. Відповідь я оновлю пізніше після тестування (мабуть, немає часу до завтра).
Гайда

@Bananguin Нова версія зроблена. Здається, це працює для мене, тому має бути адаптованим до вашого випадку використання. Щодо "приємного підходу", так, але я не думаю, що тут дійсно можливий приємний підхід. Інформацію потрібно якось передавати, і це завжди буде злом, якщо ви не патч і ssh-клієнта, і сервера, щоб це зробити чисто через одне з'єднання.
Гайда

І я все більше і більше замислююся над виправленням opensh. Це не здається великою справою. Інформація вже доступна. Мені просто потрібно надіслати його на сервер. Щоразу, коли сервер отримує таку інформацію, він записує його~/.ssh-${PID}-forwards
Bananguin

1

Фрагмент для місцевої сторони, придатний для .bashrc:

#!/bin/bash

user=$1
host=$2

sshr() {
# 1. connect, get dynamic port, disconnect  
port=`echo "exit" | ssh -R '*:0:127.0.0.1:52698' -t $1 2>&1 | grep 'Allocated port' | awk '/port/ {print $3;}'`
# 2. reconnect with this port and set remote variable
cmds="ssh -R $port:127.0.0.1:52698 -t $1 bash -c \"export RMATE_PORT=$port; bash\""
($cmds)
}

sshr $user@$host

0

Я домігся того ж, створивши трубу на локальному клієнті, потім перенаправивши stderr на трубу, яка також перенаправлена ​​на вхід ssh. Він не вимагає декількох з'єднань ssh, щоб припустити відомий вільний порт, який може вийти з ладу. Таким чином банер для входу та текст "Виділений порт ### ..." перенаправляються на віддалений хост.

У мене простий скрипт на хості, getsshport.shякий запускається на віддаленому хості, який зчитує переспрямований вхід і аналізує порт. Поки цей сценарій не закінчується, віддалений ssh-пульт залишається відкритим.

місцева сторона

mkfifo pipe
ssh -R "*:0:localhost:22" user@remotehost "~/getsshport.sh" 3>&1 1>&2 2>&3 < pipe | cat > pipe

3>&1 1>&2 2>&3 це невелика хитрість замінити stderr і stdout, щоб stderr потрапив до кота, і весь нормальний вихід з ssh показаний на stderr.

віддалений бік ~ / getsshport.sh

#!/bin/sh
echo "Connection from $SSH_CLIENT"
while read line
do
    echo "$line" # echos everything sent back to the client
    echo "$line" | sed -n "s/Allocated port \([0-9]*\) for remote forward to \(.*\)\:\([0-9]*\).*/client port \3 is on local port \1/p" >> /tmp/allocatedports
done

Я спробував grepспочатку надіслати повідомлення про "виділений порт" на локальній стороні, перш ніж надсилати його через ssh, але, схоже, ssh заблокує очікування відкриття труби на stdin. grep не відкриває трубу для запису, поки щось не отримає, тому це в основному тупики. catоднак, схоже, немає такої самої поведінки, і відкриває трубку для запису відразу, дозволяючи ssh відкрити з'єднання.

це та сама проблема на віддаленій стороні, і чому readрядок за рядком замість просто grep від stdin - інакше `/ tmp / selectedports 'не виписується, поки не закриється тунель ssh, який перемагає всю мету

~/getsshport.shПереважно передавати stderr ssh в таку команду , як без вказівки команди, текст банера або що-небудь ще є в трубі, виконується на віддаленій оболонці.


приємно. я додав renice +10 $$; exec catдо, doneщоб зберегти ресурси.
Спонгмен

0

Це складний варіант, додаткова обробка на стороні сервера, SSH_CONNECTIONабо DISPLAYбуло б чудово, але додати це непросто: частина проблеми полягає в тому, що лише sshклієнт знає місцеве призначення, пакет запитів (до сервера) містить лише віддалена адреса та порт.

Інші відповіді тут містять різні неприховані рішення щодо захоплення цього клієнтського боку та відправлення його на сервер. Ось альтернативний підхід, який не дуже гарний, щоб бути чесним, але принаймні ця потворна вечірка тримається на стороні клієнта ;-)

  • клієнтська сторона, додавати / змінювати, SendEnvщоб ми могли надсилати деякі змінні середовища спочатку через ssh (можливо, не за замовчуванням)
  • стороні сервера, додати / змінити, AcceptEnvщоб прийняти те саме (можливо, за умовчанням не включено)
  • контролювати sshвихід клієнта stderr за допомогою динамічно завантаженої бібліотеки та оновлювати середовище ssh для клієнта під час налаштування з'єднання
  • підберіть сторону сервера змінних середовища в сценарії профілю / входу

Це працює (на щастя, на сьогоднішній день так чи інакше), оскільки віддалені форварди встановлюються та записуються до обміну середовищем (підтвердження з ssh -vv ...). Динамічно завантажена бібліотека повинна захоплювати функцію write()libc ( ssh_confirm_remote_forward()logit()do_log()write()). Перенаправлення або обгортання функцій у двійковій версії ELF (без перекомпіляції) на порядок складніше, ніж робити те ж саме для функції в динамічній бібліотеці.

У клієнті .ssh/config(або командному рядку -o SendEnv ...)

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

На сервері sshd_config(потрібна коренева / адміністративна зміна)

AcceptEnv LC_* SSH_RFWD_*

Цей підхід працює для клієнтів Linux і не вимагає нічого особливого на сервері, він повинен працювати для інших * nix з деякими незначними налаштуваннями. Працює щонайменше з OpenSSH 5.8p1 до 7.5p1.

Поєднати gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c Invoke з:

LD_PRELOAD=./rfwd.so ssh -R0:127.0.0.1:4713 -R0:localhost:631 somehost

Кодекс:

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <stdlib.h>

// gcc -Wall -shared  -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c

#define DEBUG 0
#define dfprintf(fmt, ...) \
    do { if (DEBUG) fprintf(stderr, "[%14s#%04d:%8s()] " fmt, \
          __FILE__, __LINE__, __func__,##__VA_ARGS__); } while (0)

typedef ssize_t write_fp(int fd, const void *buf, size_t count);
static write_fp *real_write;

void myinit(void) __attribute__((constructor));
void myinit(void)
{
    void *dl;
    dfprintf("It's alive!\n");
    if ((dl=dlopen(NULL,RTLD_NOW))) {
        real_write=dlsym(RTLD_NEXT,"write");
        if (!real_write) dfprintf("error: %s\n",dlerror());
        dfprintf("found %p write()\n", (void *)real_write);
    } else {
        dfprintf(stderr,"dlopen() failed\n");
    }
}

ssize_t write(int fd, const void *buf, size_t count)
{
     static int nenv=0;

     // debug1: Remote connections from 192.168.0.1:0 forwarded to local address 127.0.0.1:1000
     //  Allocated port 44284 for remote forward to 127.0.0.1:1000
     // debug1: All remote forwarding requests processed
     if ( (fd==2) && (!strncmp(buf,"Allocated port ",15)) ) {
         char envbuf1[256],envbuf2[256];
         unsigned int rport;
         char lspec[256];
         int rc;

         rc=sscanf(buf,"Allocated port %u for remote forward to %256s",
          &rport,lspec);

         if ( (rc==2) && (nenv<32) ) {
             snprintf(envbuf1,sizeof(envbuf1),"SSH_RFWD_%i",nenv++);
             snprintf(envbuf2,sizeof(envbuf2),"%u %s",rport,lspec);
             setenv(envbuf1,envbuf2,1);
             dfprintf("setenv(%s,%s,1)\n",envbuf1,envbuf2);
         }
     }
     return real_write(fd,buf,count);
}

(Є кілька пасток ведмедя glibc, пов'язаних з версією символів при такому підході, але write()ця проблема не має.)

Якщо ви почуваєтесь сміливими, можете взяти setenv()відповідний код і переправити його у ssh.c ssh_confirm_remote_forward()функцію зворотного дзвінка.

Це встановлює названі змінні середовища SSH_RFWD_nnn, перевіряйте їх у своєму профілі, наприклад, уbash

for fwd in ${!SSH_RFWD_*}; do
    IFS=" :" read lport rip rport <<< ${!fwd}
    [[ $rport -eq "631" ]] && export CUPS_SERVER=localhost:$lport
    # ...
done

Застереження:

  • в коді не так багато перевірок помилок
  • зміна середовища може спричинити проблеми, пов'язані з потоками, PAM використовує теми, я не очікую проблем, але я цього не перевіряв
  • sshна даний момент не чітко записується повна форма переадресації форми * local: port: remote: port * (при необхідності буде потрібно подальший розбір debug1повідомлень з ssh -v), але це не потрібно для вашого випадку використання

Як не дивно, але, схоже, OpenSSH не має коштів для отримання інформації про переадресацію портів.

Ви можете (частково) це робити інтерактивно із втечею ~#, як не дивно, впровадження пропускає канали, які прослуховуються, у ньому перераховані лише відкриті (тобто TCP ESTABLISHED) канали, і він не друкує корисні поля ні в якому разі. Подивитисяchannels.c channel_open_message()

Ви можете зафіксувати цю функцію для друку деталей для SSH_CHANNEL_PORT_LISTENERслотів, але це отримує лише локальну переадресацію ( канали - це не те саме, що фактично вперед ). Або ви можете виправити його, щоб скинути дві таблиці переадресації з глобальної optionsструктури:

#include "readconf.h"
Options options;  /* extern */
[...]
snprintf(buf, sizeof buf, "Local forwards:\r\n");
buffer_append(&buffer, buf, strlen(buf));
for (i = 0; i < options.num_local_forwards; i++) {
     snprintf(buf, sizeof buf, "  #%d listen %s:%d connect %s:%d\r\n",i,
       options.local_forwards[i].listen_host,
       options.local_forwards[i].listen_port,
       options.local_forwards[i].connect_host,
       options.local_forwards[i].connect_port);
     buffer_append(&buffer, buf, strlen(buf));
}
snprintf(buf, sizeof buf, "Remote forwards:\r\n");
buffer_append(&buffer, buf, strlen(buf));
for (i = 0; i < options.num_remote_forwards; i++) {
     snprintf(buf, sizeof buf, "  #%d listen %s:%d connect %s:%d\r\n",i,
       options.remote_forwards[i].listen_host,
       options.remote_forwards[i].listen_port,
       options.remote_forwards[i].connect_host,
       options.remote_forwards[i].connect_port);
     buffer_append(&buffer, buf, strlen(buf));
}

Це чудово працює, хоча це не "програмне" рішення, але застереження, яке не має клієнтський код (все-таки він є позначеним XXX у вихідному коді), оновлює список, коли ви додаєте / видаляєте пересилання на ходу ( ~C)


Якщо сервер (и) є Linux, у вас є ще один варіант, це я, який я використовую в цілому, хоча для локальної переадресації, а не віддаленої. loє 127.0.0.1/8, в Linux можна прозоро прив’язати до будь-якої адреси в 127/8 , тому ви можете використовувати фіксовані порти, якщо використовуєте унікальні адреси 127.xyz, наприклад:

mr@local:~$ ssh -R127.53.50.55:44284:127.0.0.1:44284 remote
[...]
mr@remote:~$ ss -atnp src 127.53.50.55
State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port 
LISTEN     0      128            127.53.50.55:44284                    *:*    

Це підлягає прив'язці привілейованих портів <1024, OpenSSH не підтримує можливості Linux і має жорстко кодовану перевірку UID на більшості платформ.

Мудро обрані октети (порядкові мнемоніки ASCII в моєму випадку) допомагають розв’язати безлад в кінці дня.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.