サーバーの状態を把握するために、CGIを使用したサイト単位の監視スクリプトが稼働している。HTTP/80が通っていることが前提なのだが、SSH/22しか使えない事案がある。しからば監視スクリプト側で一工夫して、SSHポートフォワードを利用してHTTP/80へ到達しようではないか。
ssh port forwardingの記事で基本的な考え方を把握できる。今回のケースでは相手先のF/W通過が可能なのはSSH/22のみなので、ローカル側でSSHフォワードの設定をする必要がある。HTTPクライアントとなるサーバー(A)上ローカルホストの指定したポートへの通信を、リモートWEBサーバー(B)への指定したユーザーでログインしたSSHセッションを利用し、当該リモートWEBサーバー(C = Bを兼務)上(つまり、Bに対してローカルホスト上)のHTTP/80への接続を確保すればよい。このようなシナリオを実現するには、PuTTY等で端末接続を確立し、サーバーA上で、以下のようにsshコマンドを使用すればよい。
ただし、12345はサーバーA上ローカルストにバインドするポート(ここへHTTPクライアントがアクセスする。すなわち、http://127.0.0.1:12345/path/to/your/contents)、127.0.0.1はサーバーBからみたサーバーCのIPアドレス(サーバーB = サーバーCなので127.0.0.1、つまりローカルIPとなる)、80はサーバーCのHTTPサーバーのポート(つまり、ここへアクセスしたい)、userは踏み台となるサーバーBへのログインユーザー、そして1.2.3.4は踏み台となるサーバーBのIPアドレス(ホスト名やFQDNでもよいが、名前解決できる必要がある)である。
これをスクリプト化したいのだが、問題点がいくつかある。
- まず、sshをスクリプト中で動かすには、空パスの公開鍵認証でアクセスできるようにしておく必要がある。
- このような設定では、サーバーBへログインした状態となってしまい、このままではスクリプト内で利用できない。
1)については、ちまたの解説を見て解決しておいてください。きわめて一般的な事案だから。2)については、上記sshコマンドの末尾に何かコマンドを書き込めば、コマンドを実行した後に制御がサーバーA側に戻る。しかし、ある程度の時間(CGIのレスポンスが帰ってくるのに必要な時間)は接続を持続させたいので、小さなスクリプトでカウンタループを回してやると良い。
do sleep 60; cnt=\`expr \$cnt + 1\`; done" &
sshコマンド行の改行記法("\")は表示上の便宜のためにいれているので、実際は一行に書いてしまえばよい。この事例では60秒のスリープを3回カウントしたら(つまり3分経つと)接続が自動的に切れてsshコマンドが終了する。接続先たるサーバーBのログインシェルは、もちろんBourne shell(まぁ、Linuxならば上位コンパチのbashだろうけど)を想定している。このようにコマンドの最後に"&"を入れててバックグラウンドに押し込んでしまえば、端末をすぐに解放して他の作業を実施することができる。しかし、この状態でPuTTY等を閉じてしまうと、端末と一緒にsshコマンドもストンと落ちてしまう。これはUnixの仕様だからしょうがないんだよね。
しょうがないんだけど、それでは困る。このsshトンネルを監視スクリプトから起動して監視CGIのURLを叩くんだから。端末が閉じる時にsshコマンドがSIGINTまたはSIGHUPを受けて強制終了してしまうのが原因なので、小さなシェルスクリプト(pf.shとする)を作成して、これらシグナルをトラップしてしまえばよい。
#!/bin/sh
...
trap "" 1 2 3 5 6 8 10 12 13 14 17 18 20 21 22 24 25 26 27 28 31
...
ssh -L 12345:127.0.0.1:80 [email protected] "cnt=0;while [ \$cnt -lt 3 ]; \
do sleep 60; cnt=\`expr \$cnt + 1\`; done" &
...
serverA$
このようにするとsshコマンドはpf.shの子プロセスとして実施される。しかも、"シグナルハンドラ"がそのまま継承されるので、SIGINTやSIGHUPを受けても「無視」される。ここでは他のシグナルも色々と無視している。sshコマンドがバックグラウンドで起動されるのでpf.shプロセスは残らずに終了し、結局sshコマンドのプロセスはPID=1(init)を親として残る。しかし、デーモンとしての一定の手続きを踏んではいないので、本来シグナルには無防備であるが、親であったpf.shスクリプトのシグナル設定(trapコマンドでシグナルを補足して「無視」を決め込んでいる)を継承しているので、端末(正確には「制御端末」という)を閉じても一緒になって死ぬことはない。これって、デーモンじゃないけどデーモン風だよね。しかも、時間が経てば勝手に終了してくれる。
このままじゃベタ過ぎるから、変数を使って少し工夫したスクリプトをどうぞ。コンフィグファイルを読み込むようにすると更によいね。宿題ね。
#
# pf.sh : SSH local port forwarding test script, whose core
# port forwarding command (ssh) is almost'daemonized'.
#
# This script enables HTTP Web, especially CGI access via
# ssh secure connection channel to the Step Server that
# can be also Target Server like this example.
#
# Local Server(A) ==> Step Server(B) ==> Target Server(C)
#
# Be careful. The channel from Step Server(B) to
# Target Server(C) is *NOT* secure.
#
# In this setting. The above Step Server(B) and Target Server(C)
# is the same host. Step Server(B) may have private IP with
# NAT'ed (through firewall) global IP for remote access.
#
# Copyright(c) 2011 Ryuichi Kurishima
#
# Enable batch mode of ssh client. Specify yes or no.
ENABLE_BATCH_MODE=yes
#######################
# Remote loop script execution timeout setting.
# The loop script is to hold this ssh connection
# for some limited time period.
#
# Timeout of ssh connection happens after
# SLEEP_INTERVAL * SLEEP_COUNT seconds passed.
#
SLEEP_INTERVAL=60
SLEEP_COUNT=3
LOCAL_PORT=12345
TARGET_IP=127.0.0.1
TARGET_PORT=80
STEPSERVER_USER=user
STEPSERVER_HOST=1.2.3.4
#######################
# Sort of 'signal handler'.
#
# Special concern is SIGINT and SIGHUP, which must be prevented
# to keep background program's life, after closing command terminal.
#
# Refer to /usr/include/bits/signum.h for SIGNAL-integer mapping.
# (Linux)
#
# 9(SIGKILL) cannot be masked. 15(SIGTERM) left untouched.
trap "" 1 2 3 5 6 8 10 12 13 14 17 18 20 21 22 24 25 26 27 28 31
#######################
# ssh port forwarding.
#
# ** A good article to work with is here **
# http://d.hatena.ne.jp/tashen/20080730/1217439319
#
# You want to setup empty-passphrase public key authentication.
#
ssh -o "BatchMode $ENABLE_BATCH_MODE" -L ${LOCAL_PORT}:${TARGET_IP}:${TARGET_PORT} ${STEPSERVER_USER}@${STEPSERVER_HOST} "cnt=0;while [ \$cnt -lt $SLEEP_COUNT ]; do sleep $SLEEP_INTERVAL; cnt=\`expr \$cnt + 1\`; done" &
# EOF
そうそう。リモートWEBサーバーのコンテンツを見たかったんだよね。コマンドラインのWEBブラウザlynxでお楽しみあれ。(シェルしか使えない端末環境では色々と便利。)
ssh接続さえできれば、ほとんどすべてのことがリモートで実現できるよ。やっぱりLinuxは*icrosoftのWindozeよりも使い勝手がよいよねぇ。