第6章 すすんだ使い方

Last Modified: Sun Mar 13 00:10:34 UTC 2011

OpenSSH はそれ自体でも多くの機能をもっていますが、 同時にこれは UNIX の「ツールボックス・アプローチ」にのっとって設計されており、 他のソフトウェアと柔軟に組み合わせることができます。 本章では OpenSSH とそれ以外のソフトウェアを使った応用や、 OpenSSH 自体のちょっと変わった使い方を紹介します。

6.1. OpenSSH と組みあわせて使うソフトウェア

CVSやSubversion などのバージョン管理ソフトウェアは、 あらかじめ OpenSSH と組み合わせて使用できるように設計されています。 本節ではこれらの簡単な使い方と 4.3.3. rsync をつかったファイル転送 でも紹介した rsync の応用を紹介します。

6.1.1. CVS によるバージョン管理

CVS は Unix で伝統的に使われてきたバージョン管理システムのひとつで、 複数のユーザがひとつのリポジトリのソースファイルを協調して編集できるようになっています。 CVS 自身はネットワーク経由で複数のユーザがリポジトリにアクセスするための pserver という独自の認証システムをもっていますが、pserver の通信は暗号化されておらず、 したがって盗聴やなりすましなどの攻撃を受ける危険性があります。 また pserver を使うには TCP 2401番ポートで待ち受ける専用のデーモンを 立ち上げる必要があり、その分だけシステム管理の手間も増えることになります。 いっぽう OpenSSH と CVS を組み合わせて使う場合、 ユーザはネットワーク経由でリポジトリを安全に編集することができ、 しかもユーザの認証には通常の OpenSSH によるログインと同じ方法が使えます。

CVS を OpenSSH と組み合わせるのは簡単です。 まずクライアントとサーバの両方に cvs コマンドをインストールしておき、 CVS を使うユーザはクライアント上であらかじめ環境変数 CVS_RSHssh という値を 指定しておきます。ユーザはクライアント上で cvs コマンドを実行するさい、 リポジトリの位置としてローカルなパス名のかわりに 「:ext:ユーザ名@ホスト名:サーバ上のリポジトリのパス名」という 文字列を指定します。すると cvs コマンドは CVS_RSH の値に応じて 内部で自動的に ssh コマンドを呼び出し、OpenSSH 経由で CVS サーバにログインして リモートコマンドを実行します (図 cvs-client-server)。 なお、このときサーバ上ではつねに "cvs server" というコマンドが 実行されます。このようにすると cvs コマンドはサーバモードで動き、 標準入出力を介してクライアントと通信します。


図 cvs-client-server. CVS のクライアントとサーバ
OpenSSH を経由して CVS サーバにアクセスする
$ cvs -d :ext:[ユーザ名@]ホスト名:リモートパス名 コマンド 引数1 引数2 ...

あるいは

(環境変数 CVSROOT に ":ext:[ユーザ名@]ホスト名:リモートパス名" を指定する)
$ cvs コマンド 引数1 引数2 ...

実行例:

client$ cvs -d :ext:yusuke@cvs.example.com:/cvs checkout docs
    (リモートのリポジトリから docs モジュールをチェックアウトする)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':        (秘密鍵のパスフレーズを入力する)
cvs checkout: Updating docs
U docs/Makefile
U docs/man2html.prl
U docs/README
...

なお、cvs コマンドから呼び出される sshコマンドの接続ポート番号や秘密鍵ファイルなどのオプションを 渡したい場合は、個人設定ファイルをつくり、 サーバのホスト名ごとに異なるオプションを指定してください (4.7. 個人用の設定ファイルでさらに快適に 参照)。

CVS専用のユーザアカウントをつくるには

複数のユーザにネットワーク経由で CVS を使わせたいが、 サーバ上でシェルを実行してほしくない場合には、 強制コマンド実行オプション (5.4.2. 一般ユーザに特定のコマンドだけを実行させる 参照) を使うと CVS 専用のユーザアカウントを作成できます。 ただし現行の CVS バージョン 1.11 では、クライアント側のユーザがリポジトリ名を :ext:yusuke@cvs.example.com:/etc などと指定することによって、 ユーザがサーバ上の任意のディレクトリにアクセスできてしまうという欠点があります。 したがってこの cvs server コマンドで --allow-user オプションを 使えるようにするパッチをあてる必要があります。 [脚注: CVS バージョン 1.11 用のパッチは http://ioctl.org/unix/cvs/cvs.diff ]
注意
現時点の cvs server コマンド (バージョン 1.11) をそのまま使うと、 意図しないユーザがサーバ上の任意のディレクトリにアクセスしてしまう危険性があります。

この場合も 5.4.2. 一般ユーザに特定のコマンドだけを実行させる と同様に、 まず環境変数 SSH_ORIGINAL_COMMAND の値を調べ、 ユーザが通常のシェルを使おうとした場合にエラーメッセージを表示する シェルスクリプト force-cvs-only.sh を作成しておきます。 ここでは --allow-user オプションを使うことによって、 ユーザの指定できるリポジトリ用ディレクトリを /cvs に制限しています。 あとはこのスクリプトを各ユーザが送ってきた公開鍵の command="..." オプションに指定すれば完了です。
CVS のラッパプログラム force-cvs-only.sh
#!/bin/sh
# (環境変数 SSH_ORIGINAL_COMMAND の値が cvs server と等しいかどうか検査する)
if [ "cvs server" != "$SSH_ORIGINAL_COMMAND" ]; then
  # (それ以外ならメッセージを表示し終了)
  echo "Sorry, only cvs use is permitted."
  exit 1
fi

# (--allow-root オプションを追加して cvs server プログラムを実行する)
exec cvs --allow-root=/cvs server

authorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/usr/libexec/force-cvs-only.sh",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client

6.1.2. Subversion によるバージョン管理

Subversion は最近普及してきた、CVS に代わるバージョン管理システムです。 基本的な使い方は CVS と似ていますが、よりネットワーク上の共同作業やセキュリティに 配慮して設計されており、OpenSSH との連携もはじめから考慮されています。

Subversion ではリポジトリをふくむモジュールの名前を URL で指定します。 Subversion はリポジトリとしてローカルなファイルシステムだけでなく、http 経由や 独自の svn プロトコルを経由したアクセスもサポートしていますが、 OpenSSH を利用する場合は URL スキーマとして svn+ssh を利用します (SSH トンネルモード)。 この場合、リポジトリの URL は 「svn+ssh://ユーザ名@ホスト名/パス名」という 形になります。この形式のリポジトリを指定すると svn コマンドは 内部で ssh を呼び出し、サーバ上で svnserve -t という リモートコマンドを実行します (図 svn-client-server)。


図 svn-client-server. Subversion の svn コマンドと svnserve
OpenSSH を経由して Subversion サーバにアクセスする
$ svn コマンド svn+ssh://[ユーザ名@]ホスト名/パス名 引数1 引数2 ...

実行例:

client$ svn checkout svn+ssh://yusuke@svn.example.com/svn/pyvnc2swf/
    (リモートのリポジトリから pyvnc2swf ディレクトリをチェックアウトする)
A    pyvnc2swf/vnc2swf.py
A    pyvnc2swf/LICENCE.TXT
A    pyvnc2swf/crippled_des.py
A    pyvnc2swf/mp3.py
...

Subversion 専用のユーザアカウントをつくるには

CVS と同様に、Subversion でも 強制コマンド実行オプション (5.4.2. 一般ユーザに特定のコマンドだけを実行させる 参照) を使うことで専用のユーザアカウントを作成できます。 Subversion では、サーバ上で svnserve コマンドを実行する際に リポジトリの仮想的なルートディレクトリを限定する -r オプションがサポートされています。 このオプションを使うと、クライアントがリポジトリ以外のディレクトリにアクセスするのを防ぐことができます。 たとえば -r /svn を指定すると、ユーザは svn+ssh://yusuke@svn.example.com/svn/pyvnc2swf/ にアクセスするかわりに svn+ssh://yusuke@svn.example.com/pyvnc2swf/ でアクセスでき、 親ディレクトリ /svn にはアクセスできなくなります。 以下に示すシェルスクリプト force-svn-only.sh では svnserve を実行する段階で -r オプションを追加することにより、 予期しないディレクトリへのアクセスを防いでいます。
Subversion のラッパプログラム force-svn-only.sh
#!/bin/sh
# (環境変数 SSH_ORIGINAL_COMMAND の値が svnserve -t と等しいかどうか検査する)
if [ "svnserve -t" != "$SSH_ORIGINAL_COMMAND" ]; then
  # (それ以外ならメッセージを表示し終了)
  echo "Sorry, only svn use is permitted."
  exit 1
fi

# (-r オプションを加えて svnserve プログラムを実行する)
exec svnserve -t -r /svn

authorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/usr/libexec/force-svn-only.sh",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client

6.1.3. rsync を使ってネットワーク経由でバックアップする

ここでは 4.3.3. rsync をつかったファイル転送 で紹介した rsync コマンドを使って、 サーバ上のディレクトリをネットワーク経由で安全にバックアップする例を紹介します。 4.3.3. rsync をつかったファイル転送 ではユーザが手動で rsync コマンドを実行してファイルを転送する方法を 紹介しましたが、ここでは定期的に実行されるバックアップを人手を介さずに、かつ安全に おこなう方法を説明します。 クライアントとサーバの両方に同じバージョンの rsync コマンドが インストールされていると仮定しています。

通常、ネットワーク経由で定期的にバックアップをおこなうさいには、 以下のような条件を満たす必要があります:

4章では詳しく説明しませんでしたが、実は OpenSSH では秘密鍵・公開鍵ペア作成時に 空のパスフレーズを設定することで、パスフレーズの入力を必要としない秘密鍵ファイルを 作成することができます。このような秘密鍵ファイルを使うと、 パスフレーズをいっさい入力せずに公開鍵認証でサーバにログインできます。 当然ながら、通常のユーザがこのような秘密鍵を使うべきではありませんが、 デーモンなどから人手を介さず自動的にリモートホストにログインする場合は、 このようなパスフレーズなしの秘密鍵ファイルに頼る必要があります。 しかし、この秘密鍵ファイルがうっかり第三者の手に渡ってしまうと 攻撃者はサーバへ root として簡単にログインできてしまうので、 このような秘密鍵の利用には細心の注意が必要です。

パスフレーズなしの秘密鍵ファイルを作成するには、ssh-keygenコマンドで パスフレーズを入力するさいに、ただ Enter を入力します。

実行例:

client# ssh-keygen -t rsa                                  (秘密鍵・公開鍵ペアを生成する)
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): id_backup  (鍵の名前を指定)
Enter passphrase (empty for no passphrase):                 (ただ Enter を入力する)
Enter same passphrase again:                                (もう一度 Enter を入力する)
Your identification has been saved in id_backup.
Your public key has been saved in id_backup.pub.
The key fingerprint is:
69:c3:00:fb:70:1e:21:09:09:00:0d:44:b4:d3:05:27 root@server

この後、生成された公開鍵 id_backup.pub を サーバ上の /root/.ssh/authorized_keys ファイルに 登録して強制コマンド実行オプションを追加します。 基本的な方法は 5.4.2. 一般ユーザに特定のコマンドだけを実行させる で紹介した、シェルスクリプトと 環境変数 SSH_ORIGINAL_COMMAND を使う方法と同じです。 まず環境変数 SSH_ORIGINAL_COMMAND の値を 調べて rsync だけを実行するシェルスクリプトを作成し、 これを command="..." オプションで指定してやります。 ただし rsync コマンドは CVS や Subversion とは違い、 サーバ上で実行される rsync のコマンドライン引数が クライアント側の rsync に与えるパラメータによって 違ってきますので、あらかじめ rsync のデバッグ出力を使って、 サーバ側で実行されるコマンドライン文字列を調べておく必要があります。
サーバ側で実行される rsync の引数を調べる
$ rsync -vv [オプション] 存在しないホスト名:サーバ側パス名 クライアント側パス名

たとえば、クライアント側が rsync -az --delete root@server.example.com:/home /backup を実行して、サーバ側の /homeディレクトリの複製を、 クライアント側の /backup ディレクトリにコピーする (同時に、すでに存在しなくなったファイルを削除する) ものとしましょう。 この場合、サーバ側で実行されるコマンドを確かめるには以下のように実行します。

client$ rsync -vv -az --delete nonexistent.dom:/home /backup
opening connection using ssh nonexistent.dom rsync --server --sender -vvlogDtprz . /home
    (サーバ側のコマンドラインが表示される)
ssh: nonexistent.dom: hostname nor servname provided, or not known
rsync: connection unexpectedly closed (0 bytes received so far) [receiver]
rsync error: error in rsync protocol data stream (code 12) at io.c(443)

ここでは本来のオプション -az に加えて、 詳細なデバッグ情報を出力するオプションである -vv を指定しています。 さらにリモートホスト名として実際には存在しないホスト名 nonexistent.dom を 指定することで意図的にエラーを発生させ、 実際のネットワーク通信はいっさい行わずに、サーバ上で実行されるであろうコマンド文字列を 「リハーサル」することができます。この例では、 下線部で示されている部分がサーバ上で実行される文字列であることがわかりました。 ただしオプション -vv は実際にバックアップするときには 使用しないため、ここに表示されているオプション文字列「-vvlogDtprz」から vv を除く必要があります。したがって、実際にサーバ側で実行されるべき コマンド文字列は「rsync --server --sender -logDtprz . /home」になります。

ここまできたら、あとは環境変数 SSH_ORIGINAL_COMMAND を検査して、 この特定のコマンド文字列が与えられたときのみ rsync を実行するようなシェルスクリプト rsync-server.sh を作成し、/root/.ssh/authorized_keys の 公開鍵に command="..." オプションの引数として指定します。
サーバ側の rsync-server.sh スクリプト
#!/bin/sh
# (SSH_ORIGINAL_COMMAND の値を調べる)
if [ "rsync --server --sender -logDtprz . /home" != "$SSH_ORIGINAL_COMMAND" ]; then
    # (不正なコマンドを実行しようとした場合はログに記録して終了)
    logger "Illegal command execution: $SSH_ORIGINAL_COMMAND from $SSH_CONNECTION"
    exit 1;
fi

# (rsyncコマンドを実行する)
exec $SSH_ORIGINAL_COMMAND

/root/.ssh/authorized_keys の設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/root/rsync-server.sh",from="client.example.com",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== root@server

ここでは秘密鍵が第三者に漏れたときの被害をなるべく小さくするため、 鍵の不正な利用に対してより厳しい措置をとっています。 まず from="..." オプションを指定して、特定のクライアント (この場合は client.example.com) からしかこの公開鍵を使用できないように設定しています。 また、環境変数 SSH_ORIGINAL_COMMAND に予期しない文字列が格納されていたときは、 何者かがこの鍵を不正に利用した可能性が高いと判断して、loggerコマンドを 使って syslog にログを残します。ログには環境変数 SSH_ORIGINAL_COMMAND の値と、 接続元ホストの IPアドレスとポート番号が格納されている環境変数 SSH_CONNECTION の値を 記録しています。また、このような環境で利用する場合、サーバ上の sshd_config 設定ファイルの PermitRootLogin 設定項目は、 root がログインできる環境としてはもっとも厳しい forced-commands-only に 設定しておいたほうがよいでしょう (5.2.3. Root のログインを禁止する 参照)。

クライアント側であるバックアップデータ格納用のマシンでは、cron デーモンなどで秘密鍵ファイル id_backup を使用することによって、定期的に (人手の介入なしで) サーバの /home ディレクトリを バックアップできます。
クライアント側の daily-backup.sh スクリプト
#!/bin/sh
exec rsync -e 'ssh -i id_backup' -az --delete root@server.example.com:/home /backup

6.2. ポート転送

OpenSSH には通常の文字ベース (端末ベース) の通信や X11 転送の機能に加えて、 クライアントとサーバ間における任意の TCP 接続を暗号化する機能があります。 この機能は TCP ポート転送 (TCP port forwarding)、 あるいはただ単にポート転送 (port forwarding) と呼ばれています。 これはクライアントまたはサーバ上で接続を待ち受けるサーバソケット (ポート) を 文字どおり相手のホストに“転送”する機能です。 ポート転送を使うとクライアントとサーバで動いているさまざまな プロセスを (盗聴やなりすましの被害を受けることなく) 互いに接続したり、 特定の用途に特化した簡易 VPN のような機能を実現できたりします。 なお、このような機能は暗号化された通信路の中にもうひとつ別の通信路を確立するため、 一般にトンネリング (tunneling) とも呼ばれています。

ポート転送は、基本的にその TCP 接続の向き (ローカル→リモートか、あるいはリモート→ローカルか) によって 2種類に分けられます。本節ではまずこれら ローカル→リモート (L) と リモート→ローカル (R) の各ポート転送の基本的な 使い方を説明し、その後ポート転送を使ったいくつかの応用例を紹介します。 [脚注: この 2つのポート転送は混乱しやすいので、本書では ローカル→リモート (L) と リモート→ローカル (R) のように、 必ずアルファベットをつけて呼ぶことにします。 ここで、矢印 (→) は TCP 接続の行われる方向をあらわし、 L と R はそれぞれ ssh コマンドのオプションをあらわしています。]

6.2.1. ローカル→リモート (L) のポート転送

ローカル→リモート (L) のポート転送は、サーバ (リモート) 側で TCP 接続を待ち受けている プロセスと、クライアント (ローカル) 側で実行されるプロセスが安全に通信するための仕組みです。

sshd が動いているサーバマシンの周辺 (あるいは、サーバマシン自身) で なんらかの TCP 接続を待ち受けているプロセス S があるとしましょう。このデーモンと サーバマシンとの間のネットワークは信頼できるため、通常の TCP 接続が可能なものとします。 そして、クライアントマシンの周辺 (あるいは、クライアントマシン自身) で動いている プロセス C から、このプロセス S に接続したいとします。 しかしサーバ-クライアント間のネットワークは信頼できないため、 この 2つのプロセスが直接 TCP で通信することはできません。

OpenSSH のローカル→リモート (L) のポート転送を使うと、 プロセス CS は以下のようにして通信できます (図 port-forwarding-l)。 [脚注: なお、ここで示されている矢印は TCP 接続のサーバ-クライアント間の関係を表したもので、 実際にはプロセス CS の間で情報は双方向でやりとりされます。]

  1. クライアント側のプロセス C が、ssh に対して TCP 接続をおこなう。(接続 1)
  2. サーバ側の sshd が、プロセス S に TCP 接続をおこなう。(接続 2)

図 port-forwarding-l. ローカル→リモート (L) のポート転送

ここで sshsshd はそれぞれ プロセス SC の「代理」として動いていることに注目してください。 ややこしいですが、クライアント側の ssh プロセスは ここでは C に対する (TCP 上の) 架空のサーバ S として機能し、 サーバ側の sshd プロセスは S に対する (TCP 上の) 架空のクライアント C として機能します。 C がクライアント側の ssh に TCP接続を行うと、 ssh はサーバ側の sshd と連携して、 あたかも C が直接 S に TCP 接続をしているかのように ふるまいます。こうして C は暗号化についてまったく意識せず、 普通に TCP 接続をおこなうだけで安全に S と通信できます。

ローカル→リモート (L) のポート転送は ssh コマンドに -L ポートX:ホストY:ポートZ という オプションを与えることで開始できます。 ポート転送はログイン時に明示的に指定する必要があります。 [脚注: 現在の OpenSSH ではログイン後にポート転送を追加することもできますが、本書では扱いません。] X, Y, Z の値については 図 port-forwarding-l を参照してください。 ssh がサーバにログインした時点からログアウトするまで、sshポートX で接続を待ち受け、ここにおこなわれた接続をサーバ側の ホストY で動いている ポートZ に中継します。なお、この ホストY はサーバが動いているホスト自身 (localhost) でもかまいませんし、 サーバの周辺にある (信頼できるネットワーク内の) ホストでもかまいません。 また、ポート転送は個人設定ファイル ~/.ssh/config を使うことによっても開始できます。 追加のオプション -N を指定すると、ログイン後にシェルを起動せず、ポート転送のみを行います。
ローカル→リモート (L) のポート転送を使用する
$ ssh [-N] -L ポートX:ホストY:ポートZ [ユーザ名@]ホスト名
あるいは
  • ~/.ssh/config 設定ファイルで以下のように指定:
    Host ホスト名
        LocalForward ポートX ホストY:ポートZ
    
$ ssh [-N] [ユーザ名@]ホスト名

実行例:

client$ ssh -L 8000:localhost:4000 yusuke@server.example.com
    (ローカル→リモート (L) のポート転送を使用する)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (秘密鍵のパスフレーズを入力する)
Last login: Mon Mar 20 17:34:56 2006 from xx.xx.xx.xx
server$

この後クライアント上で netstat -an を実行すると、 TCP 8000番ポートが新たに開かれているのがわかります。

client$ netstat -an | grep 8000
tcp     0     0 127.0.0.1:8000          0.0.0.0:*            LISTEN

クライアントが自分自身の TCP 8000番ポートに接続すると、 サーバ側の TCP 4000番ポートで動いているプロセスへと中継されます。 なお、ポート転送の実行中は、 たとえサーバからログアウトしても中継している TCP 接続がすべて完了するまで ssh は終了しません。 オプション -N をつけた場合 ssh はポート転送のみをおこなうため、 端末から Control-C を入力するか、あるいは ssh プロセスが kill されるまで終了しません。

実行例:

client$ ssh -N -L 8000:localhost:4000 yusuke@server.example.com
    (シェルを使用せずにポート転送のみをおこなう)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':
(終了したい場合には Control-C を入力する)
^C

6.2.2. リモート→ローカル (R) のポート転送

リモート→ローカル (R) のポート転送は、クライアント (ローカル) 側で TCP 接続を待ち受けている プロセスと、サーバ (リモート) 側のプロセスが安全に通信するための仕組みです。 これは TCP 接続の方向が逆であることを除けば、 6.2.1. ローカル→リモート (L) のポート転送 で説明したローカル→リモート (L) のポート転送と同じです (図 port-forwarding-r)。サーバ側の sshd プロセスは C に対する (TCP 上の) 架空のサーバ S として機能し、クライアント側の ssh プロセスが S に対する (TCP 上の) 架空のクライアント C として機能します。 プロセス CS は、以下のようにして通信します。

  1. サーバ側のプロセス C が、sshd に対して TCP 接続をおこなう。(接続 1)
  2. クライアント側の ssh が、プロセス S に TCP 接続をおこなう。(接続 2)

図 port-forwarding-r. リモート→ローカル (R) のポート転送

リモート→ローカル (R) のポート転送は、 ssh コマンドに -R ポートX:ホストY:ポートZ という オプションを与えることで開始できます。X, Y, Z の値については 図 port-forwarding-r を参照してください。 ssh がサーバにログインすると、サーバ側の sshdポートX で接続を待ち受け、ここにおこなわれた接続をクライアント側の ホストY で動いている ポートZ に中継します。ローカル→リモート (L) のポート転送と同様、 ホストY はクライアント自身 (localhost) でもかまいませんし、 クライアントの周辺にある (信頼できるネットワーク内の) ホストでもかまいません。 ポート転送は個人設定ファイル ~/.ssh/config を使うことによっても開始できます。 追加のオプション -N を指定すると、ログイン後にシェルを起動せずポート転送のみを行います。
リモート→ローカル (R) のポート転送を使用する
$ ssh [-N] -R ポートX:ホストY:ポートZ [ユーザ名@]ホスト名
あるいは
  • ~/.ssh/config 設定ファイルで以下のように指定:
    Host ホスト名
        RemoteForward ポートX ホストY:ポートZ
    
$ ssh [-N] [ユーザ名@]ホスト名

実行例:

client$ ssh -R 3000:localhost:6000 yusuke@server.example.com
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (秘密鍵のパスフレーズを入力する)
Last login: Mon Mar 20 17:34:56 2006 from xx.xx.xx.xx
server$

この後サーバ上からサーバマシンの TCP 3000番ポートに接続すると、 クライアント側の TCP 6000番ポートで動いているプロセスに中継されます。 サーバ上で netstat -an を実行すると、 TCP 3000番ポートが新たに開かれているのがわかります。

server$ netstat -an | grep 3000
tcp     0     0 127.0.0.1:3000          0.0.0.0:*            LISTEN

6.2.3. 応用1 - HTTP のみに対応した簡易 VPN を構築する

本項ではローカル→リモート (L) のポート転送を応用して、 HTTP のみに対応した簡易 VPN を構築する方法を紹介します。

事業所 A にいるユーザが、離れた事業所 B のイントラネット web サーバ (www.intra.example.com) に アクセスしたいとします。このとき、事業所間のネットワークは信頼できないため、 秘密の漏曳を防ぐためになんらかの暗号化を使うものとします。 OpenSSH によるローカル→リモート (L) のポート転送を使えば、 事業所 A にあるクライアントマシンが仮想的な web サーバとして機能します。 事業所 A のユーザがこのクライアントマシンの TCP 80番ポートに接続すると、 この接続はサーバを経由して事業所 B 内の web サーバに中継されます。 こうしてユーザは離れた場所にあるイントラネットへ安全にアクセスできます (図 http-pseudo-vpn)。


図 http-pseudo-vpn. ポート転送でイントラネットの web サーバにアクセスする

この例は通常のポート転送と 2つの点で異なっています。 まず、クライアントは TCP 80番ポートを listen しなければならないので、 ssh コマンドを root 権限で走らせる必要があります。 またクライアントはこのポートに対して、クライアントマシン以外のマシンからも接続を 受けつける必要があります。 ローカル→リモート (L) のポート転送において、OpenSSH のデフォルトの設定では、 クライアント側で接続を待ち受けているポートにはクライアントマシン自身 (localhost) しか接続できません。これは ssh の listen するソケットが通常は IP アドレス 127.0.0.1 に bind されているためです。 ポート転送を開始する際に、コマンドラインから -g オプションを指定するか、 ~/.ssh/config 設定項目の GatewayPortsyes を指定すると、この IP アドレス は 0.0.0.0となり、 どのマシンからでもこのポートに接続できるようになります。 任意のホストに対して TCP ポートを開くことは一般的に危険ですが、 ここではファイヤーウォール内の信頼されたネットワークでクライアントを運用しているため 問題ないと考えます。
転送したポートに localhost 以外からも接続を許可する (ローカル→リモート (L) のポート転送)
$ ssh -g [-N] -L ポートX:ホストY:ポートZ [ユーザ名@]ホスト名
あるいは
  • ~/.ssh/config 設定ファイルで以下のように指定:
    Host ホスト名
        LocalForward ポートX ホストY:ポートZ
        GatewayPorts yes
    
$ ssh [-N] [ユーザ名@]ホスト名

実行例:

client# ssh -g -N -L 80:www.intra.example.com:80 proxy@server.example.com
client# netstat -an | grep 80
    (TCP 80番ポートが開いていることを確認する)
tcp     0     0 0.0.0.0:80            0.0.0.0:*            LISTEN

なお、この例で ssh を root権限で実行する必要があるのは クライアントが TCP 80番ポートを listen するためです。 リモートホストに root としてログインする必要はありません。 このような場合はサーバ上に proxy というポート転送専用の ユーザアカウントをつくり、強制コマンド実行オプションを設定するのがよいでしょう。 ポート転送では -N オプションを指定した場合、 ssh コマンドは強制コマンドの実行が完了しても ログアウトせずにポート転送を続けるため、command="..." 内の 文字列として /bin/true などの意味のないコマンドを指定しておきます。 また、以下の例では公開鍵の permitopen を使って、 サーバ側で中継できる TCP 接続を特定のホストの特定のポートのみに制限しておくことをおすすめします。 これによって、たとえこの公開鍵 (に対応する秘密鍵) が悪用されたとしても、 事業所 B 内の別のマシンや別のポート番号に TCP 接続が行われるのを防ぐことができます。

ユーザ proxyauthorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/bin/true",no-pty,no-x11-forwarding,no-agent-forwarding,permitopen="www.intra.example.com:80"
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== proxy@server

また、上の例では ssh コマンドを直接コマンドラインから実行していますが、 秘密鍵にパスフレーズをつけなければ起動スクリプトを用いて 自動的に ssh を実行させることも可能でしょう。

6.2.4. 応用2 - VNC で遠隔地にあるマシンを安全に制御する

VNC は広く普及している遠隔制御用のプロトコルで、TCP/IP を経由して X Window System だけでなく Windows や Mac OS X のデスクトップを操作することも可能です。 ユーザはまず遠隔操作の対象となるコンピュータ上で「VNC サーバ」を起動しておき、 そこにリモートから「VNC クライアント」をつかって接続します。 ユーザが VNC クライアント上でマウスやキーボードを操作すると、 その情報は VNC サーバに伝えられ、VNC サーバの動いているマシンに 同じマウスやキーボード操作が反映されます (図 vnc)。

[脚注: RealVNC: http://www.realvnc.com/ (Unix, Windows用)
VNC はもともと AT&T ケンブリッジ研究所で GPL ソフトウェアとして 開発されたもので、RealVNC のほかにもいくつかの実装があります。
TightVNC: http://www.tightvnc.com/ (Unix, Windows用)
UltraVNC: http://ultravnc.sourceforge.net/ (Windows用)
OSXVNC: http://www.redstonesoftware.com/vnc.html (Mac OS X用)


図 vnc. VNC のしくみ

通常 VNC は暗号化されていない TCP 接続を使っているため、 信頼できないネットワークを経由して使うことはできません。 [脚注: 商用の RealVNC 製品と UltraVNC は、SSH ではないものの暗号化に対応しています。] ここでは OpenSSH のポート転送を使って、VNC を安全に使う方法を紹介します。

遠隔地にあるマシンを VNC で制御する場合

VNC はデフォルトで TCP 5900番ポートを使用します。 あるファイヤーウォールで保護されたネットワークの中に VNC サーバの動いているマシン desktop1.example.com がある場合、 OpenSSH のローカル→リモート (L) のポート転送を使って クライアント側の TCP 5900番ポートからサーバ側 desktop1.example.com の TCP 5900番ポートへ中継すれば安全に VNC サーバへ接続できます (図 vnc-forwarding-l)。 ポート転送が開始されたら、ユーザは vncviewer などの VNC クライアントを使って クライアント側の TCP 5900番ポートに接続すればよいのです。


図 vnc-forwarding-l. VNC で遠隔地にあるマシンを制御する

以下の例では ssh コマンドのポート転送に使う -Nオプションと -L オプションに加えて、 ログイン後に自動的に ssh をバックグラウンドで走らせる -f オプションを指定しています。 ポート転送のみを使いたい場合や X11アプリケーションをバックグラウンドで走らせたい場合、 このオプションを使うと ssh 実行後にすぐさま次のコマンドを 入力することができます。

実行例:

client$ ssh -f -N -L 5900:desktop1.example.com:5900 yusuke@server.example.com &
    (ポート転送開始後、ssh をバックグラウンドに移行させる)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa': (秘密鍵のパスフレーズを入力する)
    (ssh がバックグラウンドに移行する)
client$ vncviewer localhost:5900 &

ローカルなマシンを遠隔地から VNC で操作してもらう場合

一方、VNC には別の利用法もあります。 ローカルで動いている Windows マシンなどに異常が発生したとき、 ここで VNC サーバを走らせておき、リモート側のサポートセンターなどから VNC クライアントで接続して操作してもらうのです。 [脚注: このアイデアは「PC説教講座」 http://pctraining.s21.xrea.com/networking/vnc-via-rev-ssh.html に書かれているものを参考にさせていただきました。] この場合は上の例とは逆に、ユーザはサポートセンターのサーバにログインし、 リモート→ローカル (R) のポート転送を使って サーバ側で動いている VNC クライアントの TCP 接続をローカルな マシン mydesktop の TCP 5900番ポートに中継します (図 vnc-forwarding-r)。


図 vnc-forwarding-r. 遠隔地からマシンを操作してもらう

実行例:

client$ ssh -N -R 5900:mymachine:5900 yusuke@server.example.com

サーバ側で使う VNC クライアントが sshd の動いている サーバマシンとは異なるマシンで実行されている場合、サーバマシンは 転送するポートに対して、自分自身以外のマシンから接続を受けつけるよう 設定を変更する必要があります。
転送したポートに localhost 以外からも接続を許可する (リモート→ローカル (R) のポート転送)

サーバ側の sshd_config 設定ファイルで以下のように指定:

GatewayPorts yes

6.2.5. Windows や Mac OS X 上からポート転送をおこなう

Windows 用の SSH クライアントである PuTTY (4.6.1. Windows 上で PuTTY を使用する 参照) や Mac OS X 用の Fugu (4.6.3. Mac OS X 上で Fugu を使用する 参照) からもポート転送機能を利用できます。

PuTTY のポート転送機能

PuTTY でポート転送機能を使うには、ログインの前に、 左側の Category: から「Connection - SSH - Tunnels」という項目を選びます (図 putty-forwarding.)。 最初にローカル→リモート (L) のポート転送を使う場合には、下のボタンから 「Local」を、リモート→ローカル (R) のポート転送を使う場合には 「Remote」を選択します。つぎに "Source port" の欄に ポートX の値を、 "Destination" の欄には ホストY:ポートZ の値をそれぞれ入力し、 「Add」ボタンを押すと転送するポートが 上の "Forwarded ports" 欄に追加されます。 この後サーバにログインすればポート転送が開始されます。 なお、PuTTY ではログイン中にウィンドウのタイトルバーを右クリックして設定を変更することで、 ログインしたあとでポート転送を追加することも可能です。


図 putty-forwarding. PuTTY のポート転送機能

Fugu のポート転送機能

Mac OS X の Fugu でローカル→リモート (L) のポート転送を使うには、 メニューバーの「SSH」から「New SSH Tunnel」項目を選びます。 すると「Create SSH Tunnel」というダイアログウィンドウが現れるので、 以下の項目を入力します。

この後、「Start Tunnel」ボタンを押せばポート転送が開始されます。


図 fugu-forwarding. Fugu のポート転送機能

6.3. VPN機能を使う

TCP ポート転送 (6.2. ポート転送 参照) は、あらかじめ通信するクライアント側の プロセスとサーバ側のプロセスがわかっているときにのみしか使うことができませんでした。 また、これは TCP 接続のみに限られており、UDP パケットを転送することはできません。 クライアント側とサーバ側に複数のホストやプログラムが接続されており、 それらがお互いに多対多で通信するような場合には TCP ポート転送は使えません。 これを実現するのが IPパケットレベルの転送を実現する VPN 機能です。

OpenSSH バージョン 4.3p1 からは 標準でトンネリングデバイス (tun/tap) を扱う機能が含まれるようになりました。 この機能を使うと、TCP だけでなく UDP パケットなど IPレベルの通信を OpenSSH の暗号化を介して転送することができ、真の VPN を構築することができます。 また、tap デバイスを使うと Ethernet レベルの通信も扱うことができます。

これまで VPN は専用の機器やソフトウェアを使うことになっていましたが、 その設定は煩雑でした。OpenSSH による VPN では OpenSSH と同じ認証方式が使えるうえに、 ほぼ標準の環境で VPN を構築できます。ただしVPN の確立が双方の root 権限を必要とするため、 クライアントとサーバの両方でroot 権限をもてる人しか使えないことや、 VPN の IPアドレスとネットワークインターフェイスを サーバ/クライアントの両方であらかじめ決めておく必要があること、 また TCP 上に実装しているので、IPSec のような機構と比べると効率が悪いことなどの欠点があります。
注意
2006年 4月現在ではこの機能はまだ実験段階のため、 安定して動作することは保証されていません。 本書では、Linux と FreeBSD でテストしましたが、 大量のデータを送受信すると不具合が生じることがありますので、 この機能に本格的に依存することはまだ現時点ではおすすめしません。

6.3.1. OpenSSH をつかった VPN のしくみ

OpenSSH をつかった VPN は基本的にはポート転送の 仕組みと変わるところはありません。ただしポート転送が TCP 接続による ストリーム通信を暗号化していたのに対して、VPN では 各 IPパケット (あるいは Ethernet フレーム) を個別に暗号化します (図 real-vpn)。


図 real-vpn. OpenSSH をつかった VPN

OpenSSH の VPN 機能は、 Linux や FreeBSD に内蔵されている tun/tap デバイスを利用しています。 tun/tap デバイスはユーザ空間のプログラムが利用できる仮想ネットワークデバイスで、 この tun/tap デバイスをサーバとクライアントの両方で開き、 その間の通信を OpenSSH が暗号化すると VPN ができあがります。

tun/tap を使ったトンネリングは 2種類に分けられます。 ひとつは tun デバイスを使う方法で、これは Layer 3 (Point-to-Point) を エミュレートし、IPフレームを転送します。 これは PPP プロトコルを使ってサーバと 1対1 で通信するのと等価です。 以下は Linux 上で IPアドレス 192.168.3.1 と 192.168.3.2 の間に tun デバイスを使った VPN が作られている場合の動作例です。

Linux 上における tun デバイスを使った VPN の動作例:

# ifconfig tun0
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet addr:192.168.3.1  P-t-P:192.168.3.2  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
# ping 192.168.3.2
PING 192.168.3.2 (192.168.3.2) from 192.168.3.1 : 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=1 ttl=64 time=16.8 ms
64 bytes from 192.168.3.2: icmp_seq=2 ttl=64 time=16.1 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=64 time=18.3 ms
...

OpenSSH の VPN 機能を使うには、バージョン 4.3p1 以上の OpenSSH と、 tun/tap デバイスの使えるカーネルをクライアントとサーバの両方で利用する必要があります。 現在ほとんどの Linux ディストリビューションでは、 tun/tap デバイスを標準でサポートしているため、 特になにも設定する必要はありません [脚注: tun/tap デバイスについての詳細は、Linux カーネルのソースコード中にある Documentation/networking/tuntap.txt を参照してください。] FreeBSD の場合は、カーネルを構築するさいに conf ファイル中で オプション "device tun" を有効にしておきます。

6.3.2. VPN を設定・テストする

サーバを設定する

OpenSSH で VPN を使う際に注意しなければならないのは、 クライアント側の root プロセスが、サーバに ssh で root としてログインする 必要があるということです。 これは tun/tap デバイスを扱えるのが通常は双方の root だけであるためです。 [脚注: 一般ユーザに tun/tap を許可することは、セキュリティ上問題があるとされています。] また、OpenSSH を使った VPN ではあらかじめクライアントとサーバの間でいくつかの事柄を 決めておく必要があります:

まずサーバ側の sshd_config設定ファイル中の PermitRootLogin 設定項目で root のログインを許可しておく必要があります (5.2.3. Root のログインを禁止する 参照)。 root ログインは危険なため、VPN を本格的に運用する際にはこの値を forced-commands-only に設定し、 強制コマンド実行オプション (5.4.4. 特定のユーザの権限を部分的に使用させる 参照) をつけることをおすすめしますが、 とりあえずテスト時にはシェルが利用できたほうが便利なため、 without-password にしておき公開鍵認証を使うのがよいでしょう。 また、トンネリングを許可するために PermitTunnel 設定項目を yes または point-to-point に設定する必要があります。
トンネリングを許可する
sshd_config設定ファイル:
PermitTunnel {yes | no | ethernet | point-to-point}
  • point-to-point … Layer 3 のトンネリングのみを許可 (tunデバイスを使用)
  • ethernet … Layer 2 のトンネリングのみを許可 (tapデバイスを使用)
  • yes … 両方のタイプのトンネリングを許可 (クライアントがトンネリングのタイプを選べる)
  • no … トンネリングを許可しない

sshd_config ファイル設定例:

PermitTunnel point-to-point

VPN をテストする

VPN はさまざまな層がからみあった複雑なシステムです。 そのため、OpenSSH で VPN を設定する際には、まずできるだけ単純な設定でテストしたほうがよいでしょう。 本来 tun/tap デバイスを使って VPN を確立するには、 これらのインターフェイスを初期化したあと ifconfig コマンドを実行して IPアドレスを設定する必要があるのですが、ssh コマンドの 役割はただ単に tun/tap を初期化して暗号化トンネルを提供するだけです。 つまり、実際のインターフェイスの設定は外部のコマンドに任されているのです。 この部分は 6.3.3. VPN を実際に運用する で自動化しますが、とりあえず最初はこれらを手動でおこなうことにして、 まずトンネルが確立できるかどうかだけをテストしてみることにします。

ssh コマンドでVPN を開始するには、-w オプションを使います。 また、テスト時にはデバッグ出力を表示する -v オプションもつけておくことをおすすめします。
VPN (トンネリング) を開始する
# ssh -wクライアント側インターフェイス番号:サーバ側インターフェイス番号 root@サーバ名

client# ssh -v -w0:0 root@server.example.com
    (デバッグモードで server.example.com にログインし、トンネリングを開始する)

-w オプションにはクライアント側のネットワークインターフェイス番号と、 サーバ側のネットワークインターフェイス番号を指定します。 デフォルトではトンネリング用のデバイスとして tun が使われます。 つまり、-w0:0 はクライアント・サーバともにインターフェイス番号 0 の tun デバイスを 使ってトンネリングを確立するということを意味しています。 うまくいけば次のようなメッセージ (Linux の場合) が出て、通常のシェルが開始されます。 うまくトンネリングができている場合は、サーバとクライアントの両方で tun デバイスが初期化されているはずです。 これは ifconfig tun0 (あるいは ifconfig -a) を実行してみればわかります:

実行例 (Linux の場合):

...
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: Requesting tun.
debug1: sys_tun_open: tun0 mode 1 fd 7
debug1: channel 1: new [tun]
Last login: Mon Feb 13 10:30:42 2006 from xxx.xxx.xxx.xxx
server# ifconfig tun0                   (tun0 インターフェイスが起動しているかどうか確認する)
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

実行例 (FreeBSD の場合):

# ifconfig tun0
    (tun0 インターフェイスが起動しているかどうか確認する)
tun0: flags=8011<UP,POINTOPOINT,MULTICAST> mtu 1500
        Opened by PID 24015

このように表示されている場合はトンネリングが確立しています。 トンネリングが確立していない状態で ifconfig tun0 を実行すると、 Linux ではインターフェイス tun0 そのものが存在せずエラーとなり、 FreeBSD では "Opened by PID XXXX" の部分が表示されません。

ただし、この状態ではサーバとクライアントの tun デバイス間にトンネリングが確立されただけで、 まだ VPN としての設定は完了していません。これらのデバイスが通信を行うためには、 サーバとクライアント両方の tun インターフェイスに IP アドレスを設定する必要があります。 ここでは別の端末を使って、クライアントとサーバ上の両方で tun デバイスを設定します。

tun デバイスは IP 層をエミュレートする (つまり、IP パケットを直接送受信する) ので、 Ethernet のインターフェイスとは違って、 相手方の IP アドレスも指定してやる必要があります。
tun デバイスに IP アドレスを設定する
(Linux の場合)
# ifconfig tun0 自分のIPアドレス pointopoint 相手のIPアドレス (point to pointではないので注意)

(FreeBSD の場合)
# ifconfig tun0 自分のIPアドレス 相手のIPアドレス

これをサーバとクライアントの両方で実行します。 たとえばサーバ側の仮想 IP アドレスを 192.168.3.1、 クライアント側の仮想 IP アドレスを 192.168.3.2 に決めたとすると、 以下のようになります:

(サーバ側 - Linux の場合)
server# ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2
(クライアント側 - Linux の場合)
client# ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1

これが成功したら、いまや IP レベルでの 通信ができるようになっているはずですので、 お互いに ping を送って確かめてみましょう。

(サーバ側)
server# ping 192.168.3.2        (サーバからクライアントに ping)
(クライアント側)
client# ping 192.168.3.1        (クライアントからサーバに ping)

この段階ではまだクライアントとサーバ双方が通信できているだけで、 その他のマシンは VPN に参加していないことに注意してください。 ローカルネットワーク内にある他のマシンを VPN に参加させるには iptables (Linux) や pf (FreeBSD) などのコマンドを用いて正しいルーティングを設定する必要があります。

VPN を終了する

VPN セッションはユーザがログアウトしたあとも続きます。 先ほどのデバッグモードで root のシェルから exit すると、 ssh はシェルから抜けたあとも依然として VPN のセッションを続けます:

server# exit
logout
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: channel 0: free: client-session, nchannels 2
(ここで Control-C を入力)
debug1: channel 1: free: tun, nchannels 1
Killed by signal 2.

現在のところ、 VPN セッションを終了するには端末上で Control-C を押すか、 ssh プロセスを killする以外に方法はないようです。

Ethernet レベルでのトンネリングをおこなう

OpenSSH で VPN を実現するもうひとつの方法は tap デバイスを使って、 Ethernet レベルのトンネリングを確立することです。 tap デバイスと tun デバイスは互いによく似ていますが、tun デバイスが Layer 3 (Point-to-Point) をエミュレートして IPパケットを直接生成、転送するのに対し、 tap デバイスは Layer 2 (Ethernet) をエミュレートし、 Ethernet フレームを転送します。 以下は Linux 上において IPアドレス 192.168.3.1 と 192.168.3.2 との間に tap デバイスを使った VPN が作られている場合の動作例です。

Linux 上で tap デバイスを使った VPN の動作例:

# ifconfig tap0
tap0      Link encap:Ethernet  HWaddr 00:FF:7F:02:53:D7
          inet addr:192.168.3.1  Bcast:192.168.3.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
# ping 192.168.3.2
PING 192.168.3.2 (192.168.3.2) from 192.168.3.1 : 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=1 ttl=64 time=37.6 ms
64 bytes from 192.168.3.2: icmp_seq=2 ttl=64 time=16.1 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=64 time=16.6 ms
...

トンネリングのさいに tun を使うか tap を使うかは、OpenSSH の サーバとクライアント両方の設定に依存しています。サーバは許可するトンネリングの種類を sshd_config 設定ファイルの PermitTunnel 項目で指定し、 クライアントは要求するトンネリングの種類を /root/.ssh/config 個人設定ファイル (あるいは ssh のコマンドラインオプション) の Tunnel 項目で指定します。 これらの項目とトンネリングの種類との関係を、表 tunneling-type にまとめます。
表 tunneling-type. PermitTunnel (サーバ) と Tunnel (クライアント) の値
PermitTunnel (サーバ)Tunnel (クライアント) の値 効果
両方が yes PPP (tun) が使われる
どちらか一方が yes (または point-to-point) で、 もう一方が point-to-point PPP (tun) が使われる
どちらか一方が yes (または ethernet) で、 もう一方が ethernet Ethernet (tap) が使われる
どちらか一方が point-to-point で、 もう一方が ethernet トンネリングは拒否される
どちらか一方が no トンネリングは拒否される

実行例:

client# ssh -v -oTunnel=ethernet -w0:0 server
Enter passphrase for key '/root/.ssh/id_rsa': (秘密鍵のパスフレーズを入力する)
Last login: Mon Feb 13 08:22:39 2006 from xxx.xxx.xxx.xxx
server#

ここで -w オプションに与えている 数値 0 は今度は tun ではなく tap のインターフェイス番号です。

無事サーバにログインできたら、ifconfig tap0 してみてください:

(Linux の場合)
# ifconfig tap0
tap0      Link encap:Ethernet  HWaddr 00:FF:4A:21:8F:3D
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

(FreeBSD の場合)
# ifconfig tap0
tap0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        ether 00:bd:73:36:60:c8
        Opened by PID 93688

デバイスができているのを確認したら、IP アドレスを設定します。 tap デバイスの場合は Ethernet をエミュレートするので、 通常のネットワークカードにアドレスを指定するのと同じ方法が使えます。

tapデバイスに IP アドレスを設定する:

(サーバ側)
server# ifconfig tap0 192.168.3.1 netmask 255.255.255.0
(クライアント側)
client# ifconfig tap0 192.168.3.2 netmask 255.255.255.0

6.3.3. VPN を実際に運用する

さて、VPN を本格的に使うとなると、いちいちサーバとクライアントの両方で 毎回 ifconfig を実行していては非効率的です。 また、リモートからの root のシェル使用も禁止したいところです。 強制コマンド実行オプション (5.4.4. 特定のユーザの権限を部分的に使用させる 参照) と 個人設定ファイル (4.7. 個人用の設定ファイルでさらに快適に 参照) を使えば、 これらの作業を安全に自動化できます。 [脚注: といっても、OpenSSH の VPN 機能は現時点では完璧ではありません。 現在の実装では、IP アドレスとネットワークインターフェイス番号を、 サーバとクライアントの両方で前もって取り決めておかないといけません。そのため、 2台以上のクライアントが同時に VPN を使う場合は少々注意が必要です。]

クライアント側で VPN 用の個人設定ファイル /root/.ssh/config を書く

最初に、クライアント側の設定を自動化してみましょう。 root のホームディレクトリに、以下のような設定ファイルを置きます:

クライアント側の /root/.ssh/config 個人設定ファイル例 (Linux の場合):

Host server-vpn
    Hostname            server.example.com      (サーバホスト名)
    Tunnel              point-to-point          (tun デバイスを使ったトンネリングを指定する)
    TunnelDevice        0:0                     (トンネリングに使うネットワークインターフェイス番号)
    PermitLocalCommand  yes                     (サーバにログインしたあと、クライアントで以下のコマンドを実行する)
    LocalCommand        ( sleep 3; ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1 ) &

最初の設定項目 Hostname には、サーバのホスト名を指定します (4.7. 個人用の設定ファイルでさらに快適に 参照)。 これ以外の 4つの設定項目 (Tunnel, TunnelDevice, PermitLocalCommand, LocalCommand) が VPN を使用するためのオプションです。これらのうち Tunnel については前項で説明しました。 また、TunnelDevice は、コマンドラインの -w オプションと同じ働きをします。 新しく登場する2つの設定項目 PermitLocalCommandLocalCommand を使うと、 サーバへのログインが成功したあとで、ローカルのクライアント上で 自動的に特定のコマンドを実行させることができます。ここでは、この機能を使って クライアント側の ifconfig コマンドを実行させ、IP アドレスを設定します。 なお、実際には RedHat 系の Linux であれば ifconfig を直接実行するよりも、 /etc/sysconfig/network-scripts/ifcfg-tun0 などの設定ファイルを作っておき、 直接 ifconfig を実行するかわりに /sbin/ifup tun0 とするほうが 望ましいでしょう。
Linux 上で LocalCommand 実行の際に IP アドレスを設定するさいの注意
上の例で、設定項目 LocalCommand の値に注目してください。 ifconfig コマンドの前に sleep が入っていますが、 これは ssh コマンドの現バージョン (4.3p2) における 仕様に対する応急処置です。現在の ssh は、LocalCommand の実行が完了したあとに tun/tap デバイスを開きます。ところが Linux では、tun/tap デバイスが使われていない (どのプロセスも open していない) 状態では、ifconfig コマンドで IPアドレスを設定することはできません。 LocalCommand のコマンドを 実行する時点ではまだ tun/tap は開かれていないため、sleep コマンドを使わないと 以下のようなエラーが表示されます:
SIOCSIFDSTADDR: No such device
tun0: unknown interface: No such device

さいわい LocalCommand は値として指定されたコマンド文字列をそのままシェルに渡すので、 いったん LocalCommand のシェルを終了させてから、 バックグラウンドで ifconfig を実行させることができます。 これは sh -c "( sleep 3; ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1 ) &" と同義です。 最初に sleep 3 と指定しているのは、「LocalCommand が実行されてから 3秒後にはおそらく tun0 インターフェイスが使用できる状態になっているだろう」と期待しているためです。 このトリックによって、ssh コマンドは tun/tap インターフェイスを開く前に LocalCommand プロセスの終了を待つようになります。あとは この行全体を ( ) でくくってバックグラウンドで実行させます。 このようにすれば一応、ローカルの IP アドレスが自動的に設定されるようにはなりますが、 現時点では付け焼刃的な感じは否めません。

いっぽう FreeBSD の場合は、tun を開く前でも ifconfig が使えるため、 とくに sleep などの技を使わなくても、ふつうに

LocalCommand  ifconfig tun0 192.168.3.2 192.168.3.1
と書いておけば成功します。

うまくいけば、以下のように入力するだけで (クライアント側の) VPN は準備完了になるはずです:

client# ssh server-vpn
Enter passphrase for key '/root/.ssh/id_rsa':         (秘密鍵のパスフレーズを入力する)
Last login: Mon Feb 13 10:30:42 2006 from xxx.xxx.xxx.xxx
server#

サーバ側の authorized_keys ファイルを変更する

いっぽうサーバ側の IPアドレスの設定は、 authorized_keys ファイルの強制コマンド実行オプション command="..." を使うことで自動化できます。 一般的に、強制コマンド実行では指定されたコマンドが終了すると自動的にログアウトしてしまいますが、 トンネリングを使っている場合は、コマンドが終了したあともサーバは実行を続けます。 なお、sshd の場合はこのコマンドを実行する前に tun/tap デバイスを開くので、 クライアント側の解説で紹介した「sleep トリック」は不要です。 また、公開鍵の tunnel="..." オプションを使えば、 サーバ側が割り当てる tun または tap のインターフェイス番号も強制的に指定することができます:

サーバ側の /root/.ssh/authorized_keys の設定例 (Linux の場合): [脚注: 実際にはこれはぜんぶつながった 1行です。]

tunnel="0",command="ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== root@server

以上の例のように設定すると、クライアントからこの公開鍵を使ってログインしたときは必ず サーバ側の tun0 デバイスが開かれ ifconfig が自動的に実行される (しかも、対話的シェルは実行されない) ことになります。

6.4. プライベートネットワーク内のサーバにログインする

現在ではセキュリティ上の理由などから、プライベートネットワークを利用している組織も多いでしょう。 この場合、外部からアクセスできる IP アドレスは限られた数しか存在しないため、 プライベートネットワーク内に複数のサーバがある場合は、それらのサーバに対して外部から個別の IP アドレスでアクセスすることはできません。 本節ではこのような状況でプライベートネットワーク内の異なるサーバにログインする方法を紹介します。

6.4.1. 複数回に分けてログインする

複数の異なるサーバにログインするひとつの方法は、ログインを 2回に分けておこなうことです。 これはゲートウェイとなるホストで sshd サーバデーモンが走っている場合に有効です (図 ssh-over-ssh)。 ユーザは最初に ssh でサーバ gateway にログインしてから、 ふたたびそのサーバ上で ssh を実行して server1 にログインします。 ssh のリモートコマンド機能を使えば、クライアント上から直接 (1回のコマンド入力で) server1 にログインできます。


図 ssh-over-ssh. プライベートネットワークの外から複数回に分けてログインする

この場合、ssh コマンドの -tオプションを使って、 最初のサーバ (gateway) にログインし、 強制的に仮想端末を割り当てる必要があります。 一般的に ssh は、リモートコマンドを実行する場合にはサーバ側で 仮想端末を割り当てません。しかし、ここではさらに他のマシンに対して ssh を実行する必要があるため、仮想端末が必要になります。 [脚注: ユーザがリモートのサーバ上で対話的にシェルを使うときには、仮想端末は必ず必要です。]
リモートコマンド実行時に強制的に仮想端末を割り当てる
$ ssh -t [ユーザ名@]ホスト名 コマンド 引数1 引数2 ...

実行例:

client$ ssh -t yusuke@gateway.example.com ssh yusuke@server1.intra.example.com  (多段ログイン)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (gateway.example.com の認証)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (server1.intra.example.com の認証)
Last login: Mon Mar 29 02:45:09 2006 from xx.xx.xx.xx
server1$

上の例では、ユーザは 2回パスフレーズを入力していますが、実際にはここで使用している 秘密鍵ファイルは異なるものであることに注意してください。 最初のパスフレーズ入力 (gateway.example.com の認証) では、 ユーザはクライアント上にある秘密鍵を使って認証します。 ところが 2度目のパスフレーズ入力 (server1.intra.example.com の認証) は 実際には gateway上で行われているので、 ユーザは server1 のログインに使う秘密鍵を ゲートウェイ上に置く必要があります。 しかしこの方法ではユーザはパスフレーズを 2度入力しなければならないうえ、 異なる秘密鍵・秘密鍵ペアを 2つ使用する必要があり面倒です。 [脚注: gateway上にクライアントと同じ秘密鍵を置くこともできますが、 これはクライアントから秘密鍵を持ち出すことになるため、おすすめできません。] したがって、このような場合は認証エージェントを使うことをおすすめします (4.4. 認証エージェントを使う 参照)。

認証エージェントを使った場合:

client$ ssh-agent bash                      (認証エージェントを起動する)
client$ ssh-add -t 3600                     (認証エージェントに秘密鍵を追加する)
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':
Identity added: /home3/yusuke/.ssh/id_rsa (/home3/yusuke/.ssh/id_rsa)
Lifetime set to 3600 seconds
client$ ssh -A -t yusuke@gateway.example.com ssh yusuke@server1.intra.example.com
    (エージェント転送を許可して多段ログイン)
Last login: Mon Mar 29 03:00:55 2006 from xx.xx.xx.xx
server1$

この例では認証エージェントの転送を許可しているため、 ゲートウェイ上からもクライアント上の秘密鍵が使われます。 この場合は gateway に秘密鍵ファイルを置く必要はなく、 gatewayserver1authorized_keys ファイルには それぞれ同じ公開鍵を登録しておけばよいことになります。

ゲートウェイにログインしたユーザを自動的に別のサーバに転送したい場合は、 ゲートウェイ上のユーザの authorized_keys ファイルに 強制コマンド実行オプションをつけることもできます。

authorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="ssh yusuke@server1.intra.example.com",no-x11-forwarding,no-port-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client

なお、これら複数のサーバがすべて一元的に管理されている場合は、ユーザが gateway に ログインしたあとに、Hostbased 認証を使うこともできます (6.6. Hostbased 認証 を使う 参照)。 この場合、ユーザは gateway へのログインに成功した時点ですでに信頼されているとみなされ、 server1 からパスワード認証や公開鍵認証を要求されることはありません。

6.4.2. ポートを変えて複数のホストにログインする

外部から複数のサーバにログインするもうひとつの方法は、ゲートウェイをルータとして使い、 異なるポート番号におこなわれた TCP 接続を異なるホストに中継するよう設定しておくことです。 [脚注: 最近ではこのような機能をもった家庭用ルータもあります。] たとえばユーザが、 gateway.example.com の TCP 10000番ポートに接続すると server1 に、 TCP 20000番ポートに接続すると server2 にログインできるように設定するのです。 (図 sshd-different-port)。


図 sshd-different-port. 接続ポート番号でホストを変える

しかしこの方法はこのままで使うと、ホスト認証で問題をひきおこします。

client$ ssh -p10000 gateway.example.com        (server1 にログインする)
...
server1$ exit        (ログアウトする)
logout
client$ ssh -p20000 gateway.example.com        (server2 にログインする)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @  (ホスト鍵が変わっているという警告)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
...

これは server1 と server2 がそれぞれ別々のホスト鍵をもっているのに対して、 sshknown_hosts ファイルに、 どちらも gateway.example.com という名前でホスト公開鍵を登録してしまうためです。 この問題は ~/.ssh/config 個人設定ファイルを用いて解決します。具体的には、HostKeyAlias 設定項目を使って ログインするときに known_hosts 中のホスト公開鍵をそれぞれ server1.intra.example.com と server2.intra.example.com という架空のホスト名に対応づけるのです。
ホスト公開鍵を架空のホスト名に関連づける (IP アドレスの関連はチェックしない)
$ ssh -oHostKeyAlias=架空のホスト名 -oCheckHostIP=no [ユーザ@]実際のホスト名
あるいは
HostKeyAlias 架空のホスト名
CheckHostIP no

実行例:

client$ ssh -oHostKeyAlias=server1.intra.example.com -oCheckHostIP=no -p10000 gateway.example.com
...

.ssh/config 設定ファイルの例:

Host server1
    HostName gateway.example.com
    Port 10000
    HostKeyAlias server1.intra.example.com
    CheckHostIP no

Host server2
    HostName gateway.example.com
    Port 20000
    HostKeyAlias server2.intra.example.com
    CheckHostIP no

実行例:

client$ ssh server1
...
server1$ exit
logout
client$ ssh server2
server2$

.ssh/~configで指定している CheckHostIP no は ホスト公開鍵と IP アドレスとの関連をチェックしないよう指示するものです。 ssh クライアントは、通常ユーザが指定したホスト名に加え、 その IP アドレスについてもホスト公開鍵との対応をチェックしています。 そのため、known_hosts ファイルにホスト公開鍵を登録するときに、 同一の IP アドレスに対して異なるホスト公開鍵を登録してしまうと警告が表示されて しまうのです。

CheckHostIP no を指定しない場合:

client$ ssh server1
...
server1$ exit
logout
client$ ssh server2
(server1.intra.example.com の IPアドレスが登録されているものと一致しないという警告)
Warning: the RSA host key for 'server1.intra.example.com' differs from the key for the IP address 'xx.xx.xx.xx'
Offending key for IP in /home/yusuke/.ssh/known_hosts:52
Matching host key in /home/yusuke/.ssh/known_hosts:48
Are you sure you want to continue connecting (yes/no)?

6.5. 制限された環境下で sshd を走らせる

本節では制限された環境で sshd サーバデーモンを走らせる方法を紹介します。 一般的には特定のユーザ権限や chroot 環境下でデーモンを起動することは セキュリティの向上につながるとされていますが、 OpenSSH はリモートから ログインしたユーザがマシン全体を制御できるように設計されているため、 もともと制限された環境で走らせることをあまり想定していません。 使い勝手を下げずに、sshd サーバデーモンの環境を 制限するには複雑な設定が必要になります。

6.5.1. 一般ユーザ権限で sshd を走らせる

OpenSSH の sshdサーバデーモンは通常 root 権限で走らせることを前提としていますが、 一般ユーザ権限で走らせることもできます。 この場合はデーモンを走らせているユーザのみがログインできます。 しかし一般ユーザ権限の sshd には以下のような欠点があります。

sshdサーバデーモンを一般ユーザ権限で走らせるには、以下のような手順で設定します。

  1. 適当なディレクトリを作り、その中に sshd_config 設定ファイルを作成する。
  2. 同じディレクトリに ssh-keygen コマンドでホスト秘密鍵とホスト公開鍵のペアを作成する。 この際、パスフレーズはつけない。
  3. sshd_config 設定ファイル中の Port 設定項目と HostKey 設定項目を 変更し、Port には 1024番以上のポートを、HostKey には 2. で作成したホスト秘密鍵のパス名を指定する。
  4. sshd を起動する。このとき、-f オプションで sshd_config 設定ファイルのパス名を指定する。 なお、sshd の実行には絶対パス名を指定する必要があります。

実行例:

server$ mkdir myssh                           (sshd 用のディレクトリを作成する)
server$ cd myssh
server$ vi sshd_config                        (sshd_config 設定ファイルを作成する)
server$ ssh-keygen -P '' -f ssh_host_rsa_key  (パスフレーズなしでホスト鍵を生成する)
Generating public/private rsa key pair.
Your identification has been saved in ssh_host_rsa_key.
Your public key has been saved in ssh_host_rsa_key.pub.
The key fingerprint is:
22:96:cb:91:38:a4:88:78:54:7f:24:e8:14:64:02:fe yusuke@server
server$ /usr/sbin/sshd -f sshd_config         (設定ファイルを指定して sshd を起動する)

6.5.2. chroot 環境で sshd を走らせる

chroot は Unix のセキュリティ機能のひとつで、あるプロセス以下のすべての 子プロセスからのアクセスを、ファイルシステム中の一部のディレクトリ (jail) 内に制限します。 ここでは sshd サーバデーモンを chroot 環境下で走らせる方法を簡単に紹介します。 chroot 環境で動かすプロセスは、万が一 root 権限が乗っ取られた場合でも 侵害の影響範囲をある程度制限できるため、一般にセキュリティを大きく改善させると考えられています。しかし、 その設定方法は非常に煩雑なため、つねにおすすめできるわけではありません。 [脚注: また chroot も完璧ではありません。chroot 環境下でも root 権限と 適切なプログラミング環境があれば、chroot したディレクトリ以外のファイルにアクセスできることが知られています。 http://www.bpfh.net/simes/computing/chroot-break.html ] また、当然ながら chroot 環境下で走っている sshd デーモンで 一般的なサーバ管理を行うこともできません。chroot 環境下で sshdサーバデーモンを 利用するのは、OpenSSH を使用する目的が非常に限られているときのみに有効です。

chroot 環境の構築方法はオペレーティングシステムによって大きく差があるため、 ここではごく簡単な代表例を示すのみにとどめます。 以下の例は Red Hat で、ユーザがログインできる chroot 環境下において sshd サーバデーモンを動かす最低限の手順を示したものです。 [脚注: ログインのあと、ユーザのホームディレクトリに chroot するパッチもあります。 http://chrootssh.sourceforge.net/ ]

もっとも単純な chroot 環境を設定する (Red Hat):

# mkdir /chroot                                                (chroot用のディレクトリを作成する)
# cd /chroot
# mkdir bin dev etc home lib sbin var var/empty var/empty/sshd (必要なディレクトリを作成する)
# cp /etc/passwd etc/                                          (/etc/passwd ファイルをコピーする)
# cp /bin/sh bin/                                              (シェルをコピーする)
# ldd /bin/sh                                                  (シェルに必要なライブラリをコピーする)
        libtermcap.so.2 => /lib/libtermcap.so.2 (0x40020000)
        libdl.so.2 => /lib/libdl.so.2 (0x40025000)
        libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# cp /lib/lib{termcap,dl,c}.so* lib/
# cp /lib/ld-linux.so.2 /lib/libnss*.so* lib/
# cp /usr/sbin/sshd sbin/                                     (sshd をコピーする)
# ldd /usr/sbin/sshd                                          (sshd に必要なライブラリをコピーする)
        libpam.so.0 => /lib/libpam.so.0 (0x40020000)
        libdl.so.2 => /lib/libdl.so.2 (0x40029000)
        libresolv.so.2 => /lib/libresolv.so.2 (0x4002c000)
        libcrypto.so.2 => /lib/libcrypto.so.2 (0x4003d000)
        libutil.so.1 => /lib/libutil.so.1 (0x40101000)
        libnsl.so.1 => /lib/libnsl.so.1 (0x40104000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x40119000)
        libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# cp /lib/lib{pam,resolv,crypto,util,nsl,crypt}.so* lib/
# mknod dev/null c 1 3                                        (必要なデバイスノードを作成する)
# mknod dev/zero c 1 5
# mknod dev/urandom c 1 9
# /usr/sbin/chroot . /sbin/sshd -d                            (chroot 環境で sshd を走らせる)
debug1: sshd version OpenSSH_4.3p2
debug1: read PEM private key done: type RSA
debug1: private host key: #0 type 1 RSA
debug1: read PEM private key done: type DSA
debug1: private host key: #1 type 2 DSA
debug1: rexec_argv[0]='/sbin/sshd'
debug1: rexec_argv[1]='-d'
...

まず chroot 環境に必要なディレクトリやファイルをコピーして 必要なデバイスノードを作成し、その後テストを行います。 次に sshdが実行時に必要とする動的リンクライブラリ (.soファイル) を特定するために ldd コマンドを使っています。 なお、sshd が走るためには、/dev/null, /dev/zero および /dev/urandom の 3つのファイルが必要ですので注意してください。 また、この例のように設定すると sshd サーバデーモンのログを syslog 経由で記録できなくなるため、実際には syslogd デーモンのオプションを 変更して chroot 環境下に /dev/log ソケットを作成させる必要があります。 なお FreeBSD では、chroot 環境を構築する手助けをおこなう jailtools [脚注: http://www.the-labs.com/FreeBSD/JailTools/ ] などのツールが用意されています。 Red Hat Linux にも同様のツール [脚注: http://www.jmcresearch.com/projects/jail/ ] が存在します。

6.6. Hostbased 認証 を使う

OpenSSH でサポートされている Hostbased 認証は、 クライアントマシンとサーバマシンどうしがユーザを介さずに直接認証をおこなうことによって、 ユーザレベルの認証を省略する認証方式です。 本書ではほとんどの場合、ユーザの使うクライアントは システム管理者によって管理されているわけではないという状況を想定してきました。 しかし、もしシステム管理者がクライアントも含む複数のマシンを一元的に管理できているなら、 Hostbased 認証を使うことで、ユーザがパスワードやパスフレーズの入力なしに それらのマシンのあいだを自由にログインできるようになります。

Hostbased 認証の長所:

Hostbased 認証の短所:

6.6.1. Hostbased 認証 のしくみ

公開鍵認証では、クライアント上のユーザが サーバに対して、そのユーザ自身の秘密鍵やパスフレーズを使って、 そのユーザの身分をサーバに対し証明しました (3.3.2. 秘密鍵・公開鍵ペアを使ったユーザ認証 参照)。 いっぽう Hostbased 認証では、クライアントマシンとサーバマシンどうしが直接認証します。 通常の公開鍵認証では、クライアント側は最初のホスト認証によりサーバが 本物であることを確認していましたが、サーバ側はそのユーザの秘密鍵については信頼しても、 クライアントマシン全体についてはなにも知らされていません。 しかし、もしサーバがクライアントマシン全体を信用できれば、 そこに正しくログインできているユーザは信用してよいということになります。 そのため、クライアントとサーバの認証がうまくいけば、 ユーザ自身の秘密鍵やパスワードを使う必要はなくなります。

Hostbased 認証では、最初にクライアント側がサーバの正当性を確かめる ホスト認証を実施し、次にサーバ側がクライアントの正当性を確かめます。 これはクライアントがサーバ側に登録されたクライアントのホスト公開鍵に対応する ホスト秘密鍵の所有をサーバに対して証明することによっておこないます。


図 hostbased-authentication. Hostbased 認証

6.6.2. Hostbased 認証を使う準備

Hostbased 認証では、認証するのはマシン同士であってユーザではありません。そのため認証に 必要な各種ファイルはユーザのホームディレクトリではなく、 クライアントマシンの OpenSSH 設定ファイル用ディレクトリ (/etc/ssh など) に置きます。

まず、ホスト認証のためにクライアント側にホスト秘密鍵が必要です。 これは OpenSSH をインストールしたときに、ホスト公開鍵と一緒に自動的に 生成されているはずです。 また Hostbased 認証ではサーバ側に ssh_known_hosts ファイルと、 shosts.equiv ファイルが必要です。ssh_known_hosts ファイルには、 クライアントのホスト公開鍵を登録します。いっぽう shosts.equiv ファイルには、 どのクライアントからの、どのユーザにログインを許可するかを記します。
クライアント側の設定ファイル用ディレクトリ サーバ側の設定ファイル用ディレクトリ
クライアント認証に使うホスト秘密鍵ファイルが必要。
  • ssh_host_rsa_key または ssh_host_dsa_key
どのクライアントのどのユーザにログインを許可するかを登録したファイルが必要。
  • ssh_known_hosts (クライアントのホスト公開鍵を登録する)
  • shosts.equiv (信用するクライアントとユーザを登録する)

サーバ側の ssh_known_hosts ファイルは ユーザの ~/.ssh/known_hosts ファイルと似ていますが、 ここではホスト名のかわりに IP アドレスを登録します。

client$ cat /etc/ssh/ssh_known_hosts  (ssh_known_hosts ファイルの内容を確認する)
11.22.33.44 ssh-rsa AAAAAsuDBHzaC...(中略)...xtjuDRmSp3XqcT=  (11.22.33.44 のホスト公開鍵が登録されている)
...
たとえばクライアント側の IP アドレスが 55.66.77.88 であり、 そのホスト公開鍵ファイルが ssh_host_rsa_key.pub.client だとすると、 ホスト公開鍵は以下のようにして登録できます。

ホスト公開鍵を登録する:

server# ( echo -n '55.66.77.88 '; cat ssh_host_rsa_key.pub.client ) >> /etc/ssh/ssh_known_hosts
サーバ側の shosts.equiv は どのクライアントから、どのユーザのログインを許可するかを記述するファイルですが、 その文法は .rhosts ファイルや .shosts ファイルと同じです。
shosts.equiv ファイルの文法
[+-]IPアドレス [[+-]ユーザ名]

設定例:

11.22.33.44                  # (11.22.33.44 からログインするユーザはすべて許可する)
55.66.77.88 yusuke           # (55.66.77.88 からのユーザ yusuke のログインを許可する)

また、クライアント側とサーバ側の設定ファイルをどちらも書き換える必要があります。

Hostbased 認証では、クライアント側がサーバに自分の権限を証明するために クライアントマシン上のホスト秘密鍵を使います。しかしクライアントマシンの ホスト秘密鍵はそのマシンの正当性を示すために使われるため、 root権限がなければアクセスできません。 OpenSSH では、一般ユーザ権限で実行される ssh コマンドは、 ssh-keysign というroot権限で走る 補助プログラムを呼び出すことによってホスト秘密鍵を使用します。 [脚注: 以前のバージョンの OpenSSH では、ssh コマンドに setuid root ビットを指定することでホスト秘密鍵にアクセスしていました。]

デフォルトではクライアントとサーバともに Hostbased 認証の使用は禁止されています。 Hostbased 認証を行うためには、クライアント側では root 権限で ssh_config 設定ファイルを書き換えて、Hostbased 認証と ssh-keysign の使用を許可しておくことが必要です。
Hostbased 認証を許可する (クライアント側)
クライアント側の ssh_config設定ファイル:
EnableSSHKeysign yes
HostbasedAuthentication yes

同様に、サーバ側でも sshd_config 設定ファイルの変更が必要です。 ここでは管理者があらかじめ設定した shosts.equiv ファイルと ssh_known_hosts ファイルだけを参照するよう、 IgnoreRhosts 設定項目と IgnoreUserKnownHosts 設定項目に yes を設定します。 また、IP アドレスを使ってすべてのクライアントの認証をおこなえるようにするため、 UseDNS 設定項目に no を指定しています。 [脚注: UseDNS 設定項目に yes を指定した場合、 shosts.equiv ファイルには IP アドレスではなく ホスト名を書く必要があります。しかしこの場合は DNS サーバの逆引きを 正しく設定する必要があるため、一般に手続きはより煩雑になります。]
Hostbased 認証を許可する (サーバ側)
サーバ側の sshd_config設定ファイル:
HostbasedAuthentication yes
IgnoreRhosts yes
IgnoreUserKnownHosts yes
UseDNS no
注意
IgnoreRhosts に no を指定すると、 sshdサーバデーモンは Hostbased認証の許可確認時に shosts.equiv だけでなく、ログインしようとしているユーザの ホームディレクトリ上にある ~/.shosts ファイルも使用します。 また、IgnoreUserKnownHosts に no を指定すると、 sshdサーバデーモンは信頼できるクライアントのホスト鍵として ssh_known_hosts だけでなく、ログインしようとしているユーザの ホームディレクトリ上にある ~/.ssh/known_hosts ファイルも使用します。 どちらのオプションも予期しないユーザをログインさせてしまう危険性があります。 安全性を考えると、これらのオプションは yes にしておくことをおすすめします。

6.6.3. Hostbased 認証でログインする

前項で説明した設定が正しく行われていれば、ユーザはパスワードやパスフレーズの 入力なしでサーバにログインできるはずです。

client$ ssh yusuke@server.example.com  (Hostbased 認証でログインする)
Last login: Fri Apr  7 00:56:17 2006 from xx.xx.xx.xx
server$
注意
ユーザが公開鍵認証と Hostbased 認証のどちらでもログインできる場合、 サーバは公開鍵認証のほうを優先して使用します。 公会議認証を利用する場合、ユーザは通常パスフレーズを尋ねられます。ところが、 認証エージェントを使っている状態では、ユーザはそれと気づかずに 公開鍵認証でログインしてしまう可能性が生じます。したがって、 Hostbased 認証がうまくいくかどうかをテストする場合は まちがって公開鍵認証でログインしてしまわないよう注意してください。 このような勘違いを防ぐため、Hostbased 認証のテスト時には ssh -oPubkeyAuthentication=no などのオプションを与えて 一時的に公開鍵認証を禁止するとよいでしょう。

6.7. 他の SSH ソフトウェアとの相互運用

6.7.1. SSH1 プロトコルを使う

本書では主に比較的新しいバージョンの OpenSSH について、その使い方を説明しています。ところが、SSH の通信方式には現在広く用いられている SSH2 プロトコルとは別に、 SSH1 プロトコルと呼ばれている方式が存在します。 OpenSSH では SSH1 プロトコルと SSH2 プロトコルのどちらも使用でき、 これらはユーザから見た分には機能的にほとんど差がありません。しかし、 内部的にはこの 2つのプロトコルはまったく互換性がないのです。 また、SSH1 プロトコルは SSH2 プロトコルと比較してセキュリティが弱く、SSH2 プロトコルが 普及してきた現在では利用をおすすめしません。 [脚注: SSH1 は暗号化に DH 鍵交換を使っていません。 また使用できる暗号化アルゴリズムも限られており、 なりすましの検出機構にも弱いものを使っています。] しかしサーバやクライアントが古く、SSH1 プロトコルしかサポートしていない場合は、 たとえ自分が最新バージョンの OpenSSH を使っていても、SSH1 プロトコルでログインせざるを得なくなります。 OpenSSH プロジェクトの統計によれば、このような古いサーバはまだ世界に 6% ほど存在しています。 [脚注: http://www.openssh.com/usage/ja/ssh-stats.html ] 本項では現在の OpenSSH で SSH1 プロトコルを使用する方法を紹介します。

OpenSSH では、SSH1 プロトコルを使っていても 本書で説明したほとんどの機能が SSH2 プロトコルと同様に使えます。 ただし SSH1 プロトコルでは秘密鍵ファイルと公開鍵ファイルの形式が SSH2 プロトコルとは異なります。そこで本項では、これらの鍵ファイルの扱い方にしぼって、 SSH1 プロトコルの使い方を説明します。

秘密鍵・公開鍵ペアを生成する

SSH1 プロトコルを使っているサーバに公開鍵認証でログインする場合、 クライアント上ではまず ssh-keygen コマンドを使って 秘密鍵・公開鍵ペアを生成する必要があります。 これは 4.1.1. クライアント上で秘密鍵と公開鍵ペアを生成する で説明した方法と同じですが、 SSH1 プロトコルの秘密鍵・公開鍵ペアを生成する場合は ssh-keygen コマンドに -t rsa1 というオプションを与えてやります。 SSH2 プロトコルのときと同じように秘密鍵と公開鍵ファイルが ~/.ssh/ ディレクトリ内に作成され、 公開鍵の指紋が表示されます。
SSH1 用の秘密鍵と公開鍵ペアを生成する
$ ssh-keygen -t rsa1

実行例:

client$ ssh-keygen -t rsa1                             (SSH1 の秘密鍵・公開鍵ペアを生成する)
Generating public/private rsa1 key pair.
Enter file in which to save the key (/home/yusuke/.ssh/identity):  (パス名を入力、あるいは Enter を押す)
Enter passphrase (empty for no passphrase):                       (秘密鍵につけるパスフレーズを入力する)
Enter same passphrase again:                                      (同じパスフレーズをもう一度入力する)
Your identification has been saved in identity.                   (鍵が生成された)
Your public key has been saved in identity.pub.
The key fingerprint is:
81:f9:19:2b:d3:61:f8:60:7f:67:ee:8f:96:0c:2d:1a yusuke@client     (鍵の指紋が表示される)

SSH1 プロトコル用の秘密鍵ファイルと公開鍵ファイルは、それぞれデフォルトで identityidentity.pub という名前が付けられます。 秘密鍵を記録した identityファイルの パーミッションがそれぞれ 600 (-rw-------、所有者のみが読み書き実行可能) になっていることに注意してください。 秘密鍵ファイルが他人から読める状態になっていると、OpenSSH クライアントはその使用を拒否します。 なお、SSH2 プロトコルで使っている秘密鍵ファイル id_rsa はテキストファイルでしたが、 SSH1 プロトコルで使う identity ファイルはバイナリ形式です。 この時点では、identity.pub ファイルも 1行のみからなるファイルですが、 1行が非常に長いため、実際の端末上では何行にも分かれて表示されます。

id_rsa ファイル:

client$ cat identity.pub
2048 35 24980870124867471...(中略)...1274497676742299294801 yusuke@client

サーバ側に公開鍵を登録する

SSH1 プロトコルを使って公開鍵認証でログインする場合も、 SSH2 プロトコルの場合と同様に、 サーバ上で cat コマンドを使って ~/.ssh/authorized_keys ファイルに公開鍵を登録します (4.1.2. 公開鍵をサーバに登録する 参照)。 OpenSSH は各行の格納されている公開鍵の形式を自動的に判定するため、 ひとつのファイル中に SSH1 と SSH2 の公開鍵をまぜて登録できます。

server$ ssh-keygen -l -f identity.pub
    (送られてきた公開鍵の指紋を確認する)
2048 81:f9:19:2b:d3:61:f8:60:7f:67:ee:8f:96:0c:2d:1a yusuke@client
server$ mkdir ~/.ssh/
    (.ssh ディレクトリを作成する)
server$ cat identity.pub >> ~/.ssh/authorized_keys
    (identity.pub の内容を authorized_keys に追加する)
クライアント側の ~/.ssh/ サーバ側の ~/.ssh/
ログインに使う秘密鍵ファイル identity が必要。
client$ ls -la ~/.ssh/
drwxr-xr-x 60 yusuke 4048 Mar 9 07:13 ../
drwx------  2 yusuke  104 Apr 4 04:09 ./
-rw-------  1 yusuke  974 Apr 4 04:09 identity
-rw-r--r--  1 yusuke  638 Apr 4 04:09 identity.pub
ログインに使う秘密鍵に対応する公開鍵 (identity.pub) を登録した authorized_keys ファイルが必要。
server$ ls -la ~/.ssh/
drwx------  2 yusuke  104 Mar 9 12:11 ./
drwxr-xr-x 59 yusuke 4136 Mar 9 14:02 ../
-rw-------  1 yusuke 2230 Apr 4 04:11 authorized_keys

※ これはサーバ上のユーザ yusuke が直接コマンドを実行した場合の出力例です。

ログインする

OpenSSH の ssh クライアントは相手の sshd サーバが 使用できるプロトコルを自動的に判定する機能をもっています。 ログイン時にサーバが SSH1 プロトコルしかサポートしていない場合、 ssh クライアントは自動的に SSH1 プロトコルを使用します。 SSH2 プロトコルもサポートしているサーバに対して 強制的に SSH1 プロトコルでログインする場合は、sshコマンドの実行時に -1 オプションを 指定してください。SSH1 プロトコルでは、デフォルトの秘密鍵ファイルとして ~/.ssh/identity が使われます。

SSH1 プロトコルでも、SSH2 プロトコルと同じようにホスト認証 (3.2. ホスト認証のしくみ 参照) が行われます。 そのホストに初めてログインするユーザは、必ずシステム管理者に ホスト公開鍵の指紋が正しいことを確認してから yes と答えるようにしてください。 なお、SSH1 プロトコルで使われるホスト秘密鍵とホスト公開鍵は、 それぞれサーバの sshd 設定用ディレクトリの ssh_host_keyファイル (秘密鍵) と ssh_host_key.pubファイル (公開鍵) に格納されています。

SSH1 プロトコルでログインする:

client$ ssh -1 oldserver.example.com  
    (強制的に SSH1プロトコルでログインする)
The authenticity of host 'server.example.com (11.22.33.44)' can't be established.
RSA1 key fingerprint is 89:e8:8f:f7:1b:6f:86:01:34:72:5d:b1:96:cd:0b:a3.  (ホスト公開鍵の指紋が正しいかどうか確認する)
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'server.example.com,11.22.33.44' (RSA1) to the list of known hosts.
Enter passphrase for RSA key '/home/yusuke/.ssh/identity':  (秘密鍵のパスフレーズを入力する)
Last login: Tue Apr  4 04:16:21 2006 from xx.xx.xx.xx
oldserver$

6.7.2. 商用 SSH との相互運用

SSH ソフトウェアはもともとオープンソースソフトウェアとして開発され、 その仕組みは一般に公開されていました。しかし現在、SSH ソフトウェアには商用のものも存在しています。 [脚注: 2006年現在、SSH Communications Security 社から SSH Tectia という商品名で販売されています。 詳細は http://www.ssh.com/ を参照してください。] 商用 SSH ソフトウェアも OpenSSH と同じプロトコルを使っており、 多くのコマンド名やオプションは OpenSSH と似ていますが、 使用している公開鍵ファイルの形式が異なるため、OpenSSH と商用 SSH の間で 相互にログインするためには公開鍵ファイルを変換する必要があります。

OpenSSH クライアントから商用 SSH サーバにログインする

OpenSSH の ssh-keygen コマンドをつかって 秘密鍵・公開鍵ペアを生成し、OpenSSH クライアントから 商用 SSH サーバにログインするには、 OpenSSH 用に生成された公開鍵ファイル (id_rsa.pub または id_dsa.pub ファイル) を 商用 SSH 用の IETF形式 に変換する必要があります。これには ssh-keygen -e (export) コマンドを使います。
OpenSSH 用の公開鍵ファイルを商用 SSH サーバ用に変換する
$ ssh-keygen -e -f OpenSSH公開鍵ファイル > IETF形式の公開鍵ファイル

実行例:

$ cat ~/.ssh/id_rsa.pub                                               (元の OpenSSH 用の公開鍵ファイル)
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAtryZO2p...(中略)...HGMpKKbdzoQ==
$ ssh-keygen -e -f ~/.ssh/id_rsa.pub > id_rsa.pub.ietf             (IETF形式の id_rsa.pub.ietf に変換する)
$ cat id_rsa.pub.ietf                                                 (変換された IETF形式の公開鍵ファイル)
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "2048-bit RSA, converted from OpenSSH by yusuke@grape"
AAAAB3NzaC1yc2EAAAABIwAAAQEAtryZO2p/2o8WAHR5PrUyICMTmjTt8NWR+mcicXkHE+
qv7bEs0Wppa6n2XOWcyv4Q91Y8QiJSspD40pLU+mjPpTIN/RQW12r+H+XCNM4ymaVzgBiQ
...
vmQdDeBY/HGMpKKbdzoQ==
---- END SSH2 PUBLIC KEY ----

なお、商用 SSH ではサーバ側の公開鍵の管理方法も OpenSSH とは異なります。 authorized_keys ファイルに公開鍵をまとめて登録する代わりに、 複数の異なる公開鍵ファイルを ~/.ssh2/ ディレクトリ上に置き、 ~/.ssh2/authorization ファイルで公開鍵認証に使用する ファイル名を指定するという方法を採用しています。 詳しくは商用 SSH のマニュアルを参照してください。

商用 SSH クライアントから OpenSSH サーバにログインする

商用 SSH の ssh-keygen2 コマンドをつかって 秘密鍵・公開鍵ペアを生成し、商用 SSH クライアントから OpenSSH サーバにログインするには、 IETF 形式の公開鍵を OpenSSH 用の形式に変換する必要があります。 これには ssh-keygen -i (import) コマンドを使います。
商用 SSH サーバ用 の公開鍵ファイルを OpenSSH 用に変換する
$ ssh-keygen -i -f IETF形式の公開鍵ファイル > OpenSSH公開鍵ファイル

実行例:

$ cat id_rsa.pub.ietf                                               (元の IETF形式の公開鍵ファイル)
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "2048-bit RSA, converted from OpenSSH by yusuke@grape"
AAAAB3NzaC1yc2EAAAABIwAAAQEAtryZO2p/2o8WAHR5PrUyICMTmjTt8NWR+mcicXkHE+
qv7bEs0Wppa6n2XOWcyv4Q91Y8QiJSspD40pLU+mjPpTIN/RQW12r+H+XCNM4ymaVzgBiQ
...
vmQdDeBY/HGMpKKbdzoQ==
---- END SSH2 PUBLIC KEY ----
$ ssh-keygen -i -f id_rsa.pub.ietf > id_rsa.pub                  (OpenSSH 用の id_rsa.pub に変換する)
$ cat id_rsa.pub                                                    (変換された公開鍵ファイル)
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAtryZO2p...(中略)...HGMpKKbdzoQ==

商用 SSH から OpenSSH に移行する

商用 SSH クライアントを使っていたユーザが OpenSSH クライアントに乗り換えるには、 これまで 商用 SSH クライアントで使っていた IETF 形式の秘密鍵を OpenSSH で使える形式に変換する必要があります。 まず、商用 SSH の ssh-keygen2 コマンドを使って、秘密鍵ファイルに つけられていたパスフレーズを解除してください。これによって秘密鍵ファイルは OpenSSH の ssh-keygen -i (import) コマンドで変換できるようになります。 変換したあとは OpenSSH の ssh-keygen -p コマンドで新たにパスフレーズを つけ直せば完了です。 また、商用 SSH サーバを OpenSSH の sshd サーバに置き換える際も同様です。 サーバ上に登録されているホスト秘密鍵ファイルを IETF 形式から OpenSSH の形式に変換する必要があります。

商用 SSH の秘密鍵を変換するときは、安全のため元の秘密鍵ファイルそのものは 書き換えず、これを一時的な秘密鍵ファイルに複製してから ssh-keygen2 コマンドで パスフレーズを解除します。この後、OpenSSH 用の秘密鍵を生成したあとに 一時的なファイルを削除すれば、2つの異なった形式をもつ秘密鍵ファイルが入手できることになります。
商用 SSH 用の秘密鍵ファイルを OpenSSH 用に変換する
$ cp IETF形式の秘密鍵ファイル 一時的な鍵ファイル
$ ssh-keygen2 -e 一時的な鍵ファイル
$ ssh-keygen -i -f 一時的な鍵ファイル > OpenSSH用の秘密鍵ファイル
$ chmod 0600 OpenSSH用の秘密鍵ファイル
$ ssh-keygen -p -f OpenSSH用の秘密鍵ファイル
$ rm 一時的な鍵ファイル

注意… ssh-keygen2 は商用 SSH、ssh-keygen は OpenSSH のコマンドです。

実行例:

$ cp ~/.ssh2/id_dsa_2048_a tmpkey                 (商用 SSH 用の秘密鍵ファイルを一時的な鍵ファイルに複製)
$ ssh-keygen2 -e tmpkey                           (一時的な鍵ファイルのパスフレーズを解除する)
Cannot read public keyfile tmpkey.pub.
Passphrase needed for key "2048-bit rsa, yusuke@client, Wed Apr 05 2006 01:03:22 -0400".
Passphrase :                                                (元のパスフレーズを入力)
Do you want to edit key "2048-bit rsa, yusuke@client, Wed Apr 05 2006 01:03:22 -0400" (yes or no)? yes
Your key comment is "2048-bit rsa, yusuke@client, Wed Apr 05 2006 01:03:22 -0400". Do you want to edit it (yes or no)? no
Do you want to edit passphrase (yes or no)? yes
New passphrase :                                                (ただ Enter を入力)
Again          :                                                (再び Enter を入力)
Do you want to continue editing key "2048-bit rsa, yusuke@client, Wed Apr 05 2006 01:03:22 -0400" (yes or no)? no
Do you want to save key "2048-bit rsa, yusuke@client, Wed Apr 05 2006 01:03:22 -0400" to file tmpkey (yes or no)? yes
$ ssh-keygen -i -f tmpkey > id_rsa            (商用 SSH 用の秘密鍵ファイルを OpenSSH 用の秘密鍵ファイルに変換)
$ chmod 0600 id_rsa                              (安全のため、パーミッションを設定する)
$ ssh-keygen -p -f id_rsa                        (OpenSSH 用の秘密鍵ファイルにパスフレーズを設定する)
Key has comment 'id_rsa'
Enter new passphrase (empty for no passphrase):             (元のパスフレーズを入力)
Enter same passphrase again:                                (再度パスフレーズを入力)
Your identification has been saved with the new passphrase.
$ rm tmpkey                                      (一時的な鍵ファイルを削除する)
注意
変換したあとのパスフレーズをつけていない一時的な秘密鍵ファイルは必ず削除するようにしてください。

Yusuke Shinyama