Chienomi

【Linux複合技】 SSHポートフォワーディングしてセッションを維持しつつ多段SSHでファイル転送

systemd

  • TOP
  • Old Archives
  • 【Linux複合技】 SSHポートフォワーディングしてセッションを維持しつつ多段SSHでファイル転送

SSHでリモートからポートフォワーディング

例えばNAT内に存在するコンピュータに対してサーバーに代理応答してもらう方法になる。

この方法を使えばNAT内のコンピュータに対してサーバーを経由してアクセスすることが可能になる。

このポートフォワーディングは以下のように行う。

ssh -R 5000:host:10000

-Lと比べ-Rはその意味を見失いやすい。

5000はリモートホスト側のポートであり、リモートホストのこのポートにアクセスすることによりローカルホスト側にアクセスすることができるようになる。

hostはローカルホストからみたポートだ。 sshを実行しているコンピュータ自身に転送するのであればlocalhostだし、LANの他のコンピュータ、例えばbobにアクセスしたいのならbobになる。

10000はhostのポートである。 ここではSSHに対してログインさせたいので、変更していなければ22になるだろう。

リモート側ポートはそのポートを開くため、1024より小さい値を指定するには特権がいる。 逆にホスト側ポートは単にそのポートに転送するだけなので転送したいポートを指定する。

このままだとログインしてしまうのでバックグラウンドで実行するオプション-fと、何もコマンドを実行しない-Nを組み合わせるのが一般的だ。

ssh -f -N -R 5000:localhost:22

なお、-gをつけない限りはリモートホストではループバックネットワークインターフェイスにのみバインドされるため、リモートホストに対して外部からアクセス可能になるわけではない。

セキュリティを考えれば、公開はせずにSSHでサーバーにログインし、そこから転送するようにしたほうがいいだろう。

接続を維持する

このままでは環境によっては入出力がないSSHセッションはすぐ切断されてしまう。 そこで、このSSHセッションは維持してもらいたい。

ピンポンのための双方向入出力

結局使わなかったアイディア。

通常のシェルスクリプトではあるプロセスに対して別のプロセスが読むことも書くこともする、ということはできない。 そういうことがしたい場合の方法は主にふたつ。

Procfs

/proc/<PID>/fd/0に対して書き込めば標準入力に入力が与えられる。

このとき注意すべきは、標準入力がつながっているのが端末だと端末に書いてしまうので、標準入力はパイプにつながっている必要がある。 特にパイプから何も入れる予定がないのであればsleepにつなぐと良いだろう。

sleep 30 d | tr 'a-z' 'A-Z'

出力はパイプで受け取れば良い。

FIFO

こっちのほうが普通。 FIFOを使えばそこに書かれた出力を一括して受け取れる。

fifoname="$$.sshsession"
mkfifo /tmp/$fifoname
tr 'a-z' 'A-Z' < $fifoname
rm $fifoname

複数のプロセスが書く場合はちゃんと排他制御すること。 また、後処理を忘れないこと。

リモートがぽん

こんな感じでよかった。

while sleep 30 do; print PING; done | ssh somehost pong.sh | (
  while read -t 45
  do
    :
  done
)

pong.sh

while read
do
  [[ "$REPLY" == PING ]] && print PONG
done

readの-tオプションでタイムアウトしている。Zshスクリプト。

30秒ごとにPINGしていて、45秒間PONGが返ってこなかったら、たぶんコネクションは死んでいる。 まぁ、多分ぴんぽんする必要はないけど。(片方が送り続けていればいいはず)

ただ、死活チェックのために返してほしい。 どちらかといえば向こう側に送り続けてもらう必要がある。

コネクションが切れたら確実に死んでもらおう

こんな感じ。

export mainpid=$$

ssh somehost pong.sh | (
  while read -t 45
  do
    :
  done
    kill -TERM $mainpid
)

もしくはこんな感じ。

ssh somehost pong.sh | {
  while read -t 45
  do
    :
  done
    exit 1
}

Systemdでrespawn

systemdで起動させることにして、死んだら復活させてもらう。

[Unit]
Description=Connect for SSH port forwarding

[Service]
ExecStart=/home/jrh/bin/sshforward.zsh
ExecStop=/bin/kill -TERM $MAINPID
Restart=always

enableする予定はないので、Installは省略。

プロセスが死んだらRestart=alwaysなので復活する。 停止するときはユニットをstopすること。

ちなみに、KillModeを省略しているので、停止時にはsshもkillされる。

SSHの設定

SSHログインできるようにする

ここらへんは基本手順。

まず鍵の生成

$ ssh-keygen -f ~/.ssh/server_rsa

これを何らかの方法でサーバーの~/.ssh/authorized_keysに追記する。 ない場合は作成。パーミッションは600であること。

続いて~/.ssh/configに設定

Host server
  Uesr jrh
  Port 22
  HostName server.example.com
  IdentityFile ~/.ssh/server_rsa

これで簡単にログインできるようになった。

$ ssh server

こちらはログインする側の端末の設定である。

コマンド専用鍵を作る

まずは前項と同じように鍵を作って登録する。

転送は許可しないといけないので、こんな感じ。

authorized_keysのコマンド用鍵の行の先頭に以下のようなフィールドを追加する。

command="/usr/local/bin/pong.sh",no-pty,no-X11-forwarding

今回はno-port-forwardingしてしまうと動作しなくなる。 ポートフォワーディングと、標準入出力を使ったやりとりを行うためだ。

なお、この時SSH鍵を使用してアクセスした場合 コマンドは入れなくて良い。 そのコマンドしか実行できないので、勝手にそのコマンドが実行される。

なお、configファイルにはRemoteForwardの項目を入れるようにすると良いだろう。 次のように。

Host proxy-server
  User jrh
  Port 22
  HostName server.example.com
  RemoteForward 10000 localhost:22
  IdentityFile ~/.ssh/server-proxy_rsa

こちらはログインされる側の端末の設定。

多段ログイン

ログインする端末はサーバーにログインしたあと、SSHポートフォワーディングを利用してログインされる端末にログインする。 ログインする端末から見るとSSHを二度行うことになる。

もちろん、このようなことはできないわけではないのだが、これだとSCPやSFTPなどは利用しづらい。 また、できればコマンド一発で簡単にログインしたいところだ。

そこで、まずはログインする側の鍵をログインされる側に登録する。 これでまず、サーバーを経由せず直接に鍵認証可能な状態になる。

その上で設定ファイル(~/.ssh/config)にProxyCommandとしてサーバーのSSHを経由して接続する設定を記述する。

Host target-proxy
  user jrh
  Port 10000
  HostName localhost
  IdentityFile ~/.ssh/proxylogin_rsa
  ProxyCommand ssh -CW %h:%p server
  • Port-Rによってサーバーに開かれているポート
  • HostNameはサーバーにログインしてから接続するものなので、localhost
  • IdentityFileは直接のログインにも使用できるログインされる側に登録されているもの
  • ProxyCommandとして先程の設定に記載したHostの値を利用する

これで外出中でもサーバーを経由して端末にログイン可能になった。

複雑なので手順のまとめ

本文は知識順に記述しているが、ここではミニマムな達成順で記述する。

ここではログインする側の端末をlaptop、ログインされる側の端末をdesktop、サーバーをserverと呼称する。

  1. desktopで鍵を生成し、serverauthorized_keysに登録する
  2. laptopで鍵を生成し、serverauthorized_keysに登録する
  3. laptopで鍵を生成し、desktopauthorized_keysに登録する
  4. serverで動作するPONGコマンドを作成する
  5. server上のdesktopの鍵をPONGコマンドに結びつける。
  6. desktop上でserverに接続するためのコマンドを作成する。このコマンドは基本的に <PING> | <SSH> | <READ>
  7. desktopで作成したコマンドを反復起動するためのsystemdユーザーユニットを作成する
  8. desktop~/.ssh/configにコマンドに結びつけられたserverにログインする設定を記述する。ポートフォワーディングも記述する
  9. laptop~/.ssh/configserverへのログイン、及びdesktopに対してProxyCommandserverを通じてログインする設定を記述する

これで準備は完了。あとはdesktopでユニットを起動し、laptopからserver経由desktopログインのsshを実行するだけ。

おわりに

SSHの応用, systemdユーザーユニット, シェルスクリプト, 入出力とファイルデスクリプタ, procfs, FIFO, プロセスとシグナルなど一般デスクトップユーザーは触れずにいるような基礎知識が詰まったものになり、計らずもさながら中級Linuxer認定試験のような内容になった。

基礎に関する知識と理解があればこのように便利に利用することもできるので、 この記事がmagicalに見える方はぜひがんばって取り組んでいただきたいと思う。