実践で学ぶ、一歩進んだサーバ構築・運用術

第 11 回 ssh (後編)


ポート・フォワード

前回少しだけ紹介したように, ssh にはクライアント側あるいはサーバー側のポートを反対側へ転送する ポート・フォワード機能があります。 この機能を使えば任意のプロトコルを暗号化できるので重宝します。 ポート・フォワード機能で真価を発揮するのは, ProxyCommand *4 を設定してファイアウオールを越えて ssh 接続を行っているときです。 例えば出張に持参したノート PC と社内 LAN 上の PC との間で 任意のプロトコルを使って盗聴の心配なく通信できます。

ローカルからリモートへ

ssh クライアントを実行したローカル・ホストのポートを ssh サーバーが動いている内部 LAN 上のリモート・ホストに転送すれば, ローカル・ホスト上で実行する任意のクライアントから 内部 LAN 上のサーバーに接続することができます。 例えば, klab.org の内部 LAN 上のホスト kamiya へ ssh 接続する際は, 図 1 のように実行して, ローカル・ホストの 10080 番ポートを内部 LAN 上の Web プロキシ proxy.klab.org *5 の 8080 番ポートへ転送します。


ssh -L 10080:proxy.klab.org:8080 kamiya
図 1 ローカル・ホストのポートをリモート・ホストへ転送(その1)

図 1 中の「-L 10080:proxy.klab.org:8080」が ローカル・ホストのポート(10080 番)を リモート・ホスト(proxy.klab.org の 8080 番ポート)へ転送するオプションです。 コマンドライン・オプションで指定する代りに, ~/.ssh/config で図 2 のように指定することもできます。


Host kamiya
    ProxyCommand /home/sengoku/bin/proxy-into %h %p
LocalForward 10080 proxy.klab.org:8080
図 2 ローカル・ホストのポートをリモート・ホストへ転送(その2)

するとローカル・ホスト上の Web ブラウザの設定で, Web プロキシを localhost の 10080 番ポートに設定すれば, klab.org の内部 LAN 上の任意の Web サーバーに アクセスできるようになります(図 3)。

インターネットから内部 LAN 上の Web サーバーへアクセス
図 3 インターネットから内部 LAN 上の Web サーバーへアクセス

ただし, 内部 LAN 上の Web プロキシ proxy.klab.org が, 内部 LAN 上の Web サーバーへのアクセスを許可していることが必要です。 LAN によっては, LAN 上の Web サーバーにアクセスするときは, Web ブラウザから直接接続させるために, Web プロキシ経由の接続を禁止しているかもしれません。

そのような場合は, LAN 上の Web サーバーへアクセスするための Web プロキシを新たに設置する必要があります。 LAN 上の Web サーバーへのアクセスだけならキャッシュする必要が無いので, stone *6 の簡易 http プロキシ機能を使うこともできます。 例えば, 図 4 のように stone を動かします。


stone -l proxy 8080 &
図 4 簡易 http プロキシとして stone を実行

さて, これで出先のノート PC から内部 LAN 上の任意の Web サーバーに アクセスできるようになりました。 ただしこのままでは, インターネット上の Web サーバーにアクセスするときにも内部 LAN 上の Web プロキシ経由でアクセスすることになり, 少々非効率です。

Web プロキシの自動設定

Netscape などの Web ブラウザであれば, アクセスする URL ごとに Web プロキシを自動設定することができます。 例えば,図 5 のような内容のファイル 「/home/sengoku/proxy.pac 」を作成し, Netscape Communicator 4.7 の「Edit」メニューで「Preferences...」を選び, 写真 1 のように「Automatic proxy configuration」を選択して 「Configuration location(URL)」に 「file:/home/sengoku/proxy.pac 」を指定します。


function FindProxyForURL(url, host) {
  if (dnsDomainIs(host, ".klab.org")) {
    if (host == "www.klab.org") {
      return "DIRECT";
    }
    return "PROXY localhost:10080; DIRECT";
  } else {
    return "DIRECT";
  }
}
図 5 プロキシ自動設定スクリプト「proxy.pac」の中身

プロキシ自動設定スクリプトの指定
写真 1 プロキシ自動設定スクリプトの指定

図 5 のスクリプトは, Web ブラウザがアクセスする URL ごとにプロキシを自動的に設定するためのものです。 このスクリプトは JavaScript で記述します*7

プロキシ自動設定スクリプトが設定されると, ブラウザは新しい URL へアクセスしようとするごとに, このスクリプトで定義された関数 FindProxyForURL を, URL およびホスト名を引数として呼び出します。 例えば, 「http://www.klab.org/」をアクセスする場合は, 第一引数が「http://www.klab.org/」, 第二引数が「www.klab.org」になります。

そして FindProxyForURL の返り値に基づき, ブラウザは Web プロキシを自動設定します。 例えば, 返り値が「DIRECT」であればプロキシを設定せず, Web サーバーに直接アクセスしますし, 返り値が「PROXY localhost:10080」であれば localhost の 10080 番ポートのプロキシ経由でアクセスします。 複数の設定を列挙することも可能で, 例えば返り値「PROXY localhost:10080;DIRECT」は, まず localhost の 10080 番ポートのプロキシを使用し, もしこのプロキシが使用不可であれば, Web サーバーへ直接アクセス(「DIRECT」)します。

図 5 中, 「dnsDomainIs(host,".klab.org")」は ホスト名のドメイン名部分が「klab.org」であれば「true」を返す関数です。 つまりこのスクリプトは,

ドメイン名が klab.org ならば,
ホスト名が www.klab.org で無い限り localhost の 10080 番ポートのプロキシを使用する。
ホスト名が www.klab.org である場合や, ドメイン名が klab.org で無い場合は,
プロキシを使用せず直接 Web サーバーへアクセスする。

という意味になります。

proxy.pac は, ファイルとして読むこともできますが, Web サーバーから読み込むこともできます。 Web サーバーから読み込む場合, Web ブラウザ側は, 写真 1 の「Configuration location(URL)」に proxy.pac がある URL を入力するだけで良いのですが, Web サーバー側は proxy.pac を送信する際, 「Content-Type」が「application/x-ns-proxy-autoconfig」 になるよう設定しなければなりません。 例えば Apache の場合であれば, 設定ファイル httpd.conf に図 6 を加えるか, mime.types に図 7 を加えます。


AddType application/x-ns-proxy-autoconfig .pac
図 6 httpd.conf での設定


application/x-ns-proxy-autoconfig pac
図 7 mime.types での設定

リモートからローカルへ

逆方向のポート・フォワード, すなわち ssh サーバーが動いているリモート・ホストのポートを, ssh クライアントを実行したローカル側の LAN 上のホストへ転送することもできます。 この機能を使えば, 本来一方向のアクセスしか許可されていない環境下で, 逆方向のアクセスが可能になります。 例えば, 内部 LAN からファイアウオールを越えてインターネットへアクセスするための プロキシは用意されているけれども, インターネットから内部 LAN へアクセスする手段は セキュリティ上の理由などから一般ユーザーには提供されていないサイトが 特に大企業などに多いのではないかと思います。 あるいは, 内部 LAN がプライベート・アドレスで構築されていて, ファイアウォールのアドレス・ポート変換機能により 内部 LAN からインターネットへは任意の接続が可能だけれども, 逆方向は接続不可能というケースは, 特にケーブル・テレビ会社などが提供する インターネット接続サービスに多いでしょう。 いずれの場合でも, 内部 LAN からインターネット上の ssh サーバーに ssh 接続できれば, 逆方向のアクセスも可能になります。

例えば klab.org の内部 LAN 上のホスト kamiya から インターネット上のホスト asao.gcd.org へ ssh 接続する際, 図 8 のように実行すれば, ssh 接続先(asao.gcd.org)の 10022 番ポートを ローカル・ホストの 22 番ポートへ転送 (「-R 10022:localhost:22」オプション)し, ssh 接続先の 10080 番ポートを 内部 LAN 上の Web プロキシ proxy.klab.org の 8080 番ポートへ転送 (「-R 10080:proxy.klab.org:8080」オプション)します。


ssh -R 10022:localhost:22 -R 10080:proxy.klab.org:8080 asao.gcd.org
図 8 リモート・ホストのポートをローカル側へ転送

「-L」オプションと同様, 「-R」オプションも, コマンドライン・オプションで指定する代りに, ~/.ssh/config に指定することもできます(図 9)。 するとリモート側で asao.gcd.org の 10022 番ポートへ ssh 接続すれば, 内部 LAN 上のホスト kamiya へ ssh 接続できるようになりますし, リモート側で実行する Web ブラウザの設定で asao.gcd.org の 10080 番ポートを Web プロキシに設定すれば, 内部 LAN 上の Web サーバーへアクセスすることができるようになります (図 10)。 図 10 では, ssh サーバーはインターネット上にありますが, 別のサイトの内部 LAN 上の ssh サーバーであっても, ssh 接続できる限り同様のことが可能です。


Host asao.gcd.org
  RemoteForward 10022:localhost:22
  RemoteForward 10080:proxy.klab.org:8080
図 9 ローカル・ホストのポートをリモート・ホストへ転送

プライベート・アドレス上にあるサーバーへのアクセス
図 10 プライベート・アドレス上にあるサーバーへのアクセス

ただし, ssh サーバー側から内部 LAN へアクセスできるのは, 図 8 の ssh 接続が続いている限りにおいてです。 ssh クライアントあるいはサーバーのいずれかのマシンをリブートすれば 当然 ssh 接続は切れますし, リブートしなくてもネットワークの不調などでパケットが届かない状態が続けば ssh 接続が切れてしまうことはあります。 そのたびに内部 LAN に(物理的に)出向いて行って, 図 8 を実行し直すというのは面倒です*8

常時 ssh 接続

そこで, ssh 接続が切れたら再接続を行うスクリプトを書いて, ブート時に自動実行する方法を考えます。 例えば, 図 11 に示す rc.ssh スクリプトを /etc/rc.d/rc.ssh に置き, /etc/rc.d/rc.local に図 12 の行を挿入してブート時に実行します。 セキュリティ上の理由から, rc.ssh スクリプトを実行するユーザーは なるべく権限の無いユーザーにすべきです。 ここでは nobody 権限で実行しています(図 12)が, rc.ssh を実行する専用のユーザーを作る方が望ましいでしょう。


#!/usr/bin/perl $Interval = 60; $opt{'asao.gcd.org'} = "-R 10022:localhost:22 ". "-R 10080:proxy.klab.org:8080"; $ENV{'PATH'} = "/bin:/usr/bin:/usr/ucb:/usr/local/bin"; $Child = 0; sub alarm() { kill 1, $Child if $Child > 0; exit 0; } sub link() { my($host) = @_; $Child = open(SSH,"-|"); if (!$Child) { exec "ssh", split(' ',$opt{$host}), $host, "sh -c 'while :;do sleep 30;echo 1;done'"; } $SIG{'ALRM'} = 'alarm'; alarm($Interval * 2); while(<SSH>) { alarm $Interval; } close(SSH); sleep $Interval; exit 1; } for $host (keys %opt) { $pid = fork; if (!$pid) { &link($host); } $host{$pid} = $host; } while(($pid=wait) >= 0) { $host = $host{$pid}; undef $host{$pid}; $pid = fork; if (!$pid) { &link($host); } $host{$pid} = $host; } exit 1;
図 11 rc.ssh スクリプト
図 8のssh 接続を行い,接続が切れたら再接続を行う。


su - nobody -c "/etc/rc.d/rc.ssh &"
図 12 rc.ssh (図 11) をブート時に実行

rc.ssh スクリプトは, リモート・ホスト上で図 13 の sh スクリプトを実行します。 つまり 30 秒に 1 回, 「1」を標準出力へ出力し続けるプログラムです。 そして rc.ssh は「1」が送られてくるかを監視します。 もし 60 秒待っても「1」が送られてこなければ ssh 接続が切れてしまったものと見なし, ssh クライアントに HUP シグナルを送った後, 再接続を行います。


#!/bin/sh
while :
do   sleep 30
    echo 1
done
図 13 リモート・ホスト上で実行するコマンド

なお, この rc.ssh は複数のリモート・ホストに対して ssh 接続を維持できるように書いてあります。 すなわちリモート・ホスト名をキーとし, そのリモート・ホストに ssh 接続する際のオプションを値とする opt 連想配列に, リモート・ホストの数だけ要素を追加すれば OK です。 ssh のオプションは何でも指定可能なので, ローカルからリモートへのポート・フォワードを行うことも可能です。

さて, これでポート・フォワードを常に維持し続けることが可能になったわけですが, 一点注意すべき点があります。 それはパスフレーズの問題です。 本連載第 9 回「ssh(前編)」で説明したように, ssh のかぎを作成する際は必ずパスフレーズを設定すべきです。 ところがパスフレーズを設定すると, ssh 接続する際にパスフレーズを入力する必要があり, rc.ssh スクリプトのように自動的に接続を行いたい場合に困ってしまいます。

ssh-agent を走らせておけば, 毎回パスフレーズを入力する必要は無くなりますが, リブートすると ssh-agent を立ち上げ直してパスフレーズを入力する必要があり, rc.ssh のようにブート時に実行されるスクリプトの場合は ssh-agent は使えません。

従って常時ポート・フォワードを行う場合は, ポート・フォワード専用の ssh かぎをパスフレーズ無しで作ることになります。 万一かぎファイルを盗まれても影響を最小限に抑えるために, 極力権限の無いユーザーのかぎを作るべきです。 そして次の 2 つの制約を設定して, かぎが使える状況を限定します。

(1)ssh 接続元ホストを限定する

常時ポート・フォワードの場合, ssh クライアントを実行するホストは常に同じですから, ssh サーバーは特定のホストからの接続のみを受け付ければ十分です。 ただし図 10 に示すように, ssh クライアントとサーバーとの間でアドレス変換などを行うので, 接続元アドレスは ssh クライアントを実行するホストにはなりません。 アドレス変換を行うホストのアドレスになるでしょう。 もちろん, 接続元アドレスは IP スプーフィングによって偽造可能ですから, この対策は万全ではないのですが, 安全性を高める方法としては有効でしょう。

(2)実行するコマンドを限定する

rc.ssh スクリプトの場合, ssh サーバーで実行すべきコマンドは図 13 の内容に限られます。 通常の ssh 接続のように任意のコマンドを実行する必要はありません。 図 13 は, 30 秒に 1 回, 「1」を標準出力へ出力し続けるだけのプログラムですから, このプログラムのみ実行できるように設定しておけば, 万一かぎファイルを盗まれて, 実行されてもさほど困ることはありません。

もちろん, 特定のコマンドしか実行できなくても, ポート・フォワードを任意に設定されてしまうと安全がおびやかされますので, かぎは厳重に管理すべきです。

(1),(2)共に ~/.ssh/authorized_keys ファイルにおいて, クライアントのかぎに対応する公開かぎの前にオプションを付けることにより, 設定可能です。 オプションは「,」で区切ることで複数指定できます。 オプションに続く公開かぎを認証に用いた接続時のみ, そのオプションが適用されます。 オプションとして以下の項目が指定できます。

● from="パターン"

接続を許可する接続元ホスト名のパターンを指定します。 「*」と「?」をワイルド・カードとして使うことができ, 「,」で区切ることにより複数のパターンを指定できます。 パターンの前に「!」を付けると, パターンにマッチしないホスト名からの接続のみを受け付けます。

● command="コマンド"

ssh 接続を受け付けたとき実行するコマンドを指定します。 ssh クライアントから送られてきたコマンドは無視します。

● environment="環境変数名=値"

ssh クライアントから送られてきたコマンドを実行する前に, 環境変数を設定します。

● no-port-forwarding

ポート・フォワードを禁止します。

● no-X11-forwarding

X プロトコルの転送*9 を禁止します。

● no-agent-forwarding

ssh-agent プロトコルの転送*10 を禁止します。

従って, rc.ssh を受け付けるサーバー asao.gcd.org の ~nobody/.ssh/authorized_keys ファイルの設定は, 「from="パターン"」と「command="コマンド"」のオプションを使って 図 14 のように記述すると良いでしょう。 図 14 中の napt.klab.org は, アドレス変換を行うホスト名*11 です。


from="napt.klab.org",command="sh -c 'while :;do sleep 30;echo 1;done'" 1024 35 23...2334 nobody@kamiya.klab.org
図 14 ~/.ssh/authorized_keys
公開かぎ本体は長いので,途中を省略しています。
*4
ssh では, 直接TCP/IP 接続できないホストに接続するために, ProxyCommand を perl スクリプトで作成できます。 ファイアウオール越えを行う ProxyCommand を作成すれば, ファイアウオールの向こう側にあるホストを, 同じ LAN 上にあるホストと同様に扱うことができます。 連載第10 回「ssh(中編)」参照。
*5
proxy.klab.org は, 内部LAN のみで有効なホスト名です。 ssh サーバーから proxy.klab.org が到達可能であれば, 転送可能です。
*6
stone は TCP や UDP のパケットを中継する アプリケーション・レベルのパケット・リピータです。 簡易 http プロキシ機能については, 連載第 5 回「stone(前編)」を参照してください。
*7
記述方法の詳細については, http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/proxy-live.html を参照してください。
【プライベート・アドレス】
インターネットから直接アクセスする必要が無いサイト内部のネットワークで 自由に利用できる IP アドレス。 RFC1918 で規定されている。 インターネットから直接アクセスできる, インターネット上でユニークな IP アドレスは, グローバル・アドレスと呼ばれる。
【アドレス・ポート変換】
Linux の IP マスカレード機能など。 TCP/IP パケットの送信元アドレスをグローバル・アドレスへ変換することにより, プライベート・アドレスを割り当てられたホストから インターネットへの TCP/IP 接続を可能にする仕掛け。
*8
内部LAN 上に電話回線経由でアクセスできるサーバーを用意しておいて, 切れたらダイヤルアップ接続して図 8 の手順を実行し直すようにすれば, 出向く必要は無いかも知れません。 しかしインターネットにはアクセスできるけれど モデムは無い場所にいるときとか, 海外にいるときは困ってしまいます。
*9
リモート・ホストのポートをローカル側へ転送する機能のX 版です。 リモートで実行したX クライアントをローカル側のX サーバーに表示できます。
*10
リモート・ホストのポートをローカル側へ転送する機能の ssh-agent 版です。 リモートで,ローカル・ホストの ssh-agent を利用できます。
*11
実在はしません。

(他のコマンドとssh の組み合わせ)


本稿は日経Linux 2001 年 2 月号に掲載された、 実践で学ぶ、一歩進んだサーバ構築・運用術, 第 11 回「ssh (後編)」を日経BP 社の許可を得て転載したものです。

Copyright(C)2001 by 仙石浩明 <sengoku@gcd.org>
無断転載を禁じます

| home | up |

sengoku@gcd.org