リモート転送用にポートを動的に割り当てる場合(別名_-R
_オプション)、リモートマシン上のスクリプト(たとえば、_.bashrc
_から供給される)は、OpenSSHによって選択されたポートをどのように判別できますか?
私は(両端で)OpenSSHを使用して中央サーバーに接続し、他の複数のユーザーと共有しています。私のリモートセッション(今のところ)では、X、cups、pulseaudioを転送したいと思います。
最も簡単なのは、_-X
_オプションを使用してXを転送することです。割り当てられたXアドレスは環境変数DISPLAY
に格納され、そこから対応するTCPポートを決定できます。ほとんどの場合、とにかくです。しかし、Xlibがあるため、ほとんど必要ありません。 DISPLAY
を尊重します。
カップとpulseaudioにも同様のメカニズムが必要です。両方のサービスの基本は、それぞれ環境変数_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
の詳細度を上げることでした。その情報は、root以外のユーザーがアクセスできるようにするために賢明な情報よりもはるかに多くの情報を開示しているため、これは実行可能ではありません。
OpenSSHにパッチを適用して、内部構造体_permitted_opens
_のニース表現を出力する追加のエスケープシーケンスをサポートすることを考えていましたが、それでもいいのに、サーバーからクライアントエスケープシーケンスにアクセスするスクリプトを実行できません側。
次のアプローチは非常に不安定に見え、ユーザーごとに1つのそのようなSSHセッションに制限されています。ただし、少なくとも2つのそのようなセッションと、他のユーザーが同時に必要です。しかし、私は試しました...
星が正しく配置され、1羽か2羽の鶏を犠牲にした場合、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/'
最初のセットにはあるが2番目のセットにはないすべてのポートは、転送ポートである可能性が高く、実際にセットを差し引くと、_41273
_、_55710
_および_6010
_;が得られます。カップ、それぞれパルスとX。
_6010
_は、DISPLAY
を使用してXポートとして識別されます。
41273
_は_lpstat -h localhost:41273 -a
_を返すため、_0
_はcupsポートです。55710
_は_pactl -s localhost:55710 stat
_を返すため、_0
_はPulseポートです。 (クライアントのホスト名も表示されます!)(設定された減算を行うには、I _sort -u
_を実行し、上記のコマンドラインからの出力を保存し、comm
を使用して減算を実行します。)
Pulseaudioを使用すると、クライアントを識別できます。これは、すべての目的と目的で、分離が必要なSSHセッションを分離するためのアンカーとして機能する場合があります。ただし、_41273
_、_55710
_、および_6010
_を同じsshd
プロセスに関連付ける方法が見つかりませんでした。 netstat
はその情報をroot以外のユーザーに開示しません。 _-
_を読みたい_PID/Program name
_列に_2339/54
_しか表示されません(この特定のインスタンスでは)。とても近い...
.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
2つ取ってください(サーバー側からscpを実行し、少し単純なバージョンの履歴を参照してください)、これで実行できるはずです。その要点はこれです:
まず、リモート側でセットアップし、sshd構成で環境変数の送信を有効にする必要があります。
Sudo yourfavouriteeditor /etc/ssh/sshd_config
AcceptEnv
を含む行を見つけてMY_PORT_FILE
を追加します(まだ行がない場合は、右側のHost
セクションの下に行を追加します)。私にとって、線はこれになりました:
AcceptEnv LANG LC_* MY_PORT_FILE
また、これを有効にするために、sshdを再起動することを忘れないでください。
さらに、以下のスクリプトを機能させるには、リモート側でmkdir ~/portfiles
を実行してください。
次に、ローカル側で、スクリプトスニペットが
スクリプトスニペット:
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
注:もちろん、上記のコードは十分にテストされておらず、あらゆる種類のバグ、コピー/貼り付けエラーなどを含む可能性があります。それを使用する人は、それをよりよく理解することもできます自己責任で使用してください!ローカルホスト接続のみを使用してテストしましたが、テスト環境で機能しました。 YMMV。
ローカルクライアントでパイプを作成し、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はcatにパイプされ、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
Sshを介して送信する前に、まずローカル側で「割り当てられたポート」メッセージをgrep
しようとしましたが、sshはstdinでパイプが開くのを待つのをブロックするようです。 grepは何かを受け取るまで書き込み用のパイプを開かないため、これは基本的にデッドロックになります。 cat
はこれと同じ動作をしているようには見えず、書き込み用のパイプを開き、sshが接続を開くことをすぐに許可します。
これはリモート側でも同じ問題であり、なぜstdinからのgrepではなく1行ずつread
が出力されます。目的
コマンドやバナーテキストなどを指定せずにリモートシェルで実行されるため、sshのstderrを~/getsshport.sh
などのコマンドにパイプすることをお勧めします。
これはトリッキーなもので、_SSH_CONNECTION
_またはDISPLAY
の行に沿った追加のサーバー側処理は素晴らしいでしょうが、追加するのは簡単ではありません。問題の一部はssh
クライアントはローカル宛先を知っており、(サーバーへの)要求パケットにはリモートアドレスとポートのみが含まれています。
ここでの他の回答には、このクライアント側をキャプチャしてサーバーに送信するためのさまざまな解決策があります。これは正直に言うとそれほどきれいではない別のアプローチですが、少なくともこの醜いパーティーはクライアント側にとどまっています;-)
SendEnv
を追加/修正して、sshを介していくつかの環境変数をネイティブに送信できるようにする(おそらくデフォルトではない)AcceptEnv
を追加/修正して同じものを受け入れます(おそらくデフォルトでは有効になっていません)ssh
client 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でも機能するはずです。少なくともOpenSSH5.8p1から7.5p1まで動作します。
_gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c
_でコンパイルします。
_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
_
警告:
ssh
は現在、* local:port:remote:port *という形式の完全な転送を明確に記録していません(必要に応じて、_debug1
_を含む_ssh -v
_メッセージの解析がさらに必要になります)。ユースケースではこれは必要ありません奇妙なことに、OpenSSHにはポート転送に関する情報を取得する手段がないようです。
エスケープ_~#
_を使用して(部分的に)これをインタラクティブに行うことができます。奇妙なことに、実装はリッスンしているチャネルをスキップし、開いている(つまり、TCP ESTABLISHED)チャネルのみをリストします。いずれの場合も、有用なフィールドを出力しません。 _channels.c
_channel_open_message()
を参照してください。
その関数にパッチを当てて_SSH_CHANNEL_PORT_LISTENER
_スロットの詳細を出力することはできますが、それはローカル転送のみを取得します(channelsは実際のforwardsと同じではありません) 。または、パッチを適用して、グローバルoptions
構造体から2つの転送テーブルをダンプすることもできます。
_#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の場合、もう1つのオプションがあります。これは、リモートではなくローカル転送用ですが、私が一般的に使用するオプションです。 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序数ニーモニック)は、一日の終わりに混乱を解くのに役立ちます。