SSH について

Last Updated at $Date: 2016/09/06 21:55:08 $.

SSH (Secure SHell) は, rsh/rlogin/rcp などの r 系コマンドを代替するために作られたプログラムです. 広く知られているように, r 系コマンドは認証手段が非常に貧弱なので,クラッカーの格好の標的となってしまいます. 対して,SSH は,公開鍵暗号に基づく強力な認証手段と通信路の暗号化により, 信用できないネットワーク上で安全な通信を行う方法を提供します.


Port Forwarding を利用したネットワーク接続

セキュリティ上の理由から, アクセスを受け付けるホストを制限しているメールサーバーやニュースサーバーが増えています. このようなサーバーに接続する場合,SSH の Port Forwarding の機能を利用することによって, 特定のホストから通信しているように見せかけることができます. また,SSH は Port Forwarding の対象となるネットワーク接続を暗号化しますから, 様々な通信を安全に行うことが出来るようにもなります.

この方法の具体例については, FAQ の『4.8 ftp や POP などのサービスを安全にするために ssh が使えますか?』が大変参考になります.

ただし,SSH によって作成された Port Forwarding 用の通信経路は, クライアント上の全ての local process から接続することができます. したがって,「クライアント上の全ての local user が信用できる」という環境でなければ, Port Forwarding の利用はセキュリティ上の危険を増加させる可能性があります.


SSH を経由したメールの受信

OpenSSH-5.4 より新しい SSH を使っている場合は,netcat mode を使う方法がもっとも簡単です. ~/.fetchmailrc に,以下のように設定してください.

poll pop3-server
  protocol apop
  plugin "ssh -C ssh-server -W pop3-server:%p"
  username "username"
  password "password"

OpenSSH-5.4 より古い SSH を使っている場合,中継サーバで netcat 相当のコマンドが利用できれば,以下のように設定すると netcat mode と同様に動作します.

poll pop3-server
  protocol apop
  plugin "ssh -C ssh-server connect pop3-server %p"
  username "username"
  password "password"

この設定例では,netcat 相当のコマンドとして connect コマンドを使っています. Debian GNU/Linux では connect-proxy という名前のパッケージになっていますから,中継サーバで apt-get install connect-proxy とコマンドを実行してインストールしてください.中継サーバに netcat 相当のコマンドがインストールされていない場合は,個人のホームディレクトリ以下にインストールして適切に環境設定すれば,利用できるようになります.

OpenSSH-5.4 より古い SSH を使っていて,中継サーバで netcat 相当のコマンドも利用できない場合は,以下のように設定して Port Forwarding を使いましょう.

poll pop3-server
  via localhost
  port 8236
  protocol apop
  preconnect 'ssh -f -P -C -L 8236:pop3-server:110 ssh-server sleep 15 </dev/null >/dev/null 2>&1'
  username "username"
  password "password"

しかし,Port Fowarding を使う方法では,指定されたポート 8236 番が何らかの理由で使用中の場合にうまく接続できません. そのため,2012年現在では netcat mode を使う方法が最もおすすめです.


Command not found とエラーが出る

普段使っているコマンドなのに,SSH 経由で実行すると, 以下のようにエラーメッセージが出力されて実行できないことがあります.

[client:~]% ssh server command
Command not found

この問題は,ログイン先のホストでシェルが実行されているかどうかの違いに原因があります. あるホストにログインして対話的に利用する場合には, 通常のシェル初期化ファイル(~/.bashrc~/.cshrc など)が読み込まれるので,環境変数 PATH は普段通りに設定されます. しかし,この例のように単にコマンドを実行するだけの場合は, ログイン先のホストではシェルは呼び出されないため,環境変数 PATH が設定されません. その結果,普段使っているコマンドなのに見つからない,ということが起こります.

SSH はコマンド実行時の環境変数を ~/.ssh/environment というファイルから読み込むようになっています.したがって,以下のコマンドを実行して, ~/.ssh/environment を用意しておくと, 普段通りの環境変数が設定されるようになり,コマンドもきちんと見つかるようになります.

[client:~]% ssh server
[server:~]% printenv | egrep '^PATH=' > ~/.ssh/environment

しかし,NFS などでホームディレクトリを共有している異種混在環境にログインする場合には, 上述のような静的な手法では対処できません.最近の OpenSSH であれば, ~/.ssh/rc というファイルにシェルスクリプトを記述することができ, そのような環境でも対応することができます. 以下は,私の使っている ~/.ssh/rc です.

if [ -d /var/lib/dpkg ]; then
    PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games
else
    PATH=/usr/local/bin/X11:/usr/local/bin:/usr/bin:/bin:/usr/ccs/bin:/usr/ucb
fi

if read proto cookie && [ -n "$DISPLAY" ]; then
    if [ `echo $DISPLAY | cut -c1-10` = 'localhost:' ]; then
        echo add unix:`echo $DISPLAY | cut -c11-` $proto $cookie
    else
        echo add $DISPLAY $proto $cookie
    fi | xauth -q -
fi

なお,上記スクリプトの後半部分は,X11転送を処理するためのスクリプトです. 詳しくは sshd のマニュアルを参照してください.


SOCKS 経由の通信

OpenSSH には,単体で SOCKS 経由の通信を行う機能はありません. その代わりに,ProxyCommand というオプションを利用して, SOCKS サーバーに接続する外部プログラムを呼び出すことによって通信を行います.

ProxyCommand には,指定されたホスト・ポートに対する通信を標準入出力に redirect するコマンドを指定します.そのようなコマンドとして,後藤さんによって作られた connect コマンドを利用することができます.具体的には,以下のように ~/.ssh/config に設定します.

Host *
  ProxyCommand connect -S socks-server %h %p

connect コマンドは,Debian GNU/Linux では connect-proxy という名前のパッケージになっています.SSH を実行する端末で apt-get install connect-proxy してインストールしてください.


防火壁を越えた通信

防火壁の外から防火壁内のマシンにログインする場合,指定された中継サーバにログインしなければならない,という状況は珍しくありません. OpenSSH-5.6 より新しい SSH を使っている場合は,netcat mode と複数セッションの共有を組み合わせて使う方法がもっともお勧めです. 以下のように ~/.ssh/config に設定して下さい.

Host relay-server.example.net
  ProxyCommand none
  ControlMaster auto
  ControlPath ~/.ssh/mux-%r@%h:%p
  ControlPersist 10

Host *.example.net
  ProxyCommand ssh -C relay-server.example.net -W %h:%p

なお,中継サーバ上の SSH が OpenSSH-5.4 より古くても netcat mode は動作しますから,手元の端末で動作する SSH だけを更新すれば netcat mode が使えるようになります.どうしても OpenSSH-5.4 以後に更新できない場合,中継サーバで netcat 相当のコマンドが利用できれば,以下のように設定すると netcat mode と同様に動作します.

Host relay-server.example.net
  ProxyCommand none

Host *.example.net
  ProxyCommand ssh -C relay-server.example.net connect %h %p

この設定例では,netcat 相当のコマンドとして connect コマンドを使っています. Debian GNU/Linux では connect-proxy という名前のパッケージになっていますから,中継サーバで apt-get install connect-proxy とコマンドを実行してインストールしてください.中継サーバに netcat 相当のコマンドがインストールされていない場合は,個人のホームディレクトリ以下にインストールして適切に環境設定すれば,利用できるようになります.

外部ネットワークと DMZ の間に防火壁,さらに DMZ と内部ネットワークの間にも防火壁というように防火壁が多段になっている場合,以下のように設定すると自動的に中継サーバを2つ経由して接続します.

# local-machine --> firset-relay-server --> second-relay-server --> target-machine と接続する

Host first-relay-server.example.net
  ProxyCommand none

Host second-relay-server.example.net
  ProxyCommand ssh -C first-relay-server.example.net -W %h:%p

Host *.example.net
  ProxyCommand ssh -C second-relay-server.example.net -W %h:%p

逆に,防火壁の中から防火壁の外に通信する時に中継サーバを経由する必要がある場合は,以下のように設定してください.

Host *.example.net
  ProxyCommand none

Host *.*
  ProxyCommand ssh -C relay-server.example.net -W %h:%p

防火壁内部との通信経路を作る

防火壁外にいる時に,防火壁内の WWW サーバなどに対する透過的な接続が必要になってしまう場合があります. 防火壁内のマシンで root になることができれば,PPP over SSH などの様々な手法で VPN を構成してしまうことが可能ですが,防火壁内のマシンで root になれない場合には,そうはいきません. そのような場合には,

という構成で PPP over SSH の設定を行うことにより,防火壁内での権限なしに通信経路を設置することができます.

slirp は,モデムや telnet/rsh 経由の端末接続しか提供されていない環境で, PPP 接続を模擬するためのソフトウェアです. かつて,まだ PPP 接続を提供しているプロバイダが一般的ではなく, 大学の情報処理センターの端末接続しか利用できなかった場合にお世話になりましたが, こんなところで再び使うことになるとは….

なお,以下の説明は Debian での pppd の設定ファイルの配置に依存していますので, それ以外の環境では,それぞれ適当に置き換えてください.


(比較的)簡単な方法

  1. 防火壁内のマシンで slirp をコンパイルして,インストールしておきます. root になれない状況なので, ホームディレクトリ以下にインストールして適切に環境設定することになります.
  2. 防火壁外のマシンで pppd を呼び出すユーザ権限(例:vpn)で, 防火壁内のマシン(例:in.example.net)にパスワードを入力せずにログインできるようにしておきます.
    1. VPN 用の公開鍵を作ります.
      % ssh-keygen -t dsa -N '' -f ~/.ssh/vpn_dsa
      
    2. 作成した公開鍵を,防火壁内のマシンの ~/.ssh/authorized_keys に追加します. その上で,その公開鍵の行の先頭に以下のように指定して, slirp 以外は実行できないようにしておきます.
      command="slirp -P",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-dss ...
      
    3. 以下のコマンドを実行して,slirp の開始メッセージが現れることを確認しておきます.
      % ssh -C -x -t -e none -i ~/.ssh/vpn_dsa in.example.net slirp -P
      
  3. 以下のような内容の /etc/ppp/peers/example を用意します.
    hide-password
    noauth
    pty "ssh -C -x -t -e none -i /home/vpn/.ssh/vpn_dsa in.example.net slirp -P"
    10.0.2.15:10.0.2.2
    #debug
    ipparam example
    nodefaultroute
    noipdefault 
    
    防火壁外のマシン用の IP アドレスとして 10.0.2.15, 防火壁内のマシン用の IP アドレスとして 10.0.2.2 を指定していますが, これは slirp のデフォルト設定に合わせています.詳しくは slirp(1) を参照してください.
  4. ここまでで,とりあえず pon example というコマンドで接続できるようになっているはずです. 接続してみて,10.0.2.2 が見えていることを確認してください.
  5. 次に,経路を適切に設定するためのスクリプト /etc/ppp/ip-up.d/example を用意します.
    #!/bin/sh
    PATH=/bin:/usr/bin:/sbin:/usr/sbin
    if [ x"$6" = xexample ]; then
        route add -net `echo "$4"|sed 's,[0-9][0-9]*$,0,'` netmask 255.255.255.240 "$1"
        route add -net x.y.z.0 netmask 255.255.255.0 gw "$5"
    fi
    
    ここまでで,防火壁内のマシンについて,IP アドレスを直接指定した接続ができるようになっているはずです. ただし,slirp の制限から ping は正常に動作しませんから,それ以外の方法で確認してください.
  6. 防火壁内のマシンについての名前解決は,防火壁内の DNS サーバに問い合わせるようにします. slirp は,特定の制御用アドレス(= デフォルトは 10.0.2.3)に対して送られてきた DNS query を, 防火壁内のマシンの /etc/resolv.conf に指定されている DNS サーバに転送してくれます. そのため,2通りの方法があります.
    ・全ての名前解決を,防火壁内の DNS サーバに依頼できる場合

    手元の防火壁外のマシンの /etc/resolv.conf に以下のように指定すれば良いです.

    nameserver 10.0.2.3
    
    ・防火壁外の名前解決は独自に,防火壁内の名前解決は防火壁内の DNS サーバに依頼する場合

    手元の防火壁外のマシンに dnsmasq をインストールしましょう.その上で,以下の指定を /etc/dnsmasq.conf に加えておきます.

    server=/.example.net/10.0.2.3
    

このように設定して,防火壁内部のマシンに対する接続が必要になったら pon example, 不要になったら poff example として使います.


複雑な方法

ここまでの方法は,接続が必要になると手動で接続を作らなければならない,という不便さがありました. これを解決するには,手元の防火壁外のマシンには pppd を常時実行しておき, 通信が発生すると自動的に接続を開始するように設定します.ただ,こちらの方法はかなり複雑になります.

  1. 接続経路の作成・抹消を行うスクリプト /home/vpn/connect を用意します.
    #!/bin/sh
    
    PATH=/bin:/usr/bin:/sbin:/usr/sbin
    
    dir=/home/vpn
    file=${dir}/example-ondemand.pid
    
    case "$1" in
        start)
            echo "$$" > ${file}
            exec ssh -C -x -t -e none -i ${dir}/.ssh/vpn_dsa in.example.net slirp -P
            ;;
        stop)
            if [ -r ${file} ]; then
                pid=`cat ${file}`
                if [ ! -z ${pid} ]; then
                    kill ${pid}
                fi
                rm ${file}
            fi
            ;;
    esac
    
  2. 以下のような内容の /etc/ppp/peers/example-ondemand を用意します.
    hide-password
    noauth
    pty "su vpn /home/vpn/connect start"
    10.0.2.15:10.0.2.2
    #debug
    ipparam example-ondemand
    nodefaultroute
    noipdefault 
    demand
    idle 90
    holdoff 60
    
  3. pppd を起動し,経路設定を行うスクリプト /etc/init.d/example-ondemand を用意して,適当なタイミングで実行されるように /etc/rc3.d/ にリンクを作っておきます.
    #!/bin/sh
    
    PATH=/bin:/usr/bin:/sbin:/usr/sbin
    
    PEER=example-ondemand
    INTERFACE=ppp0
    
    start(){
        pon ${PEER}
        for sec in 1 2 3; do
            if ( grep -q ${INTERFACE}: /proc/net/dev ); then
                break
            fi
            sleep ${sec}
        done
        route add -net 10.0.2.0 netmask 255.255.255.240 ${INTERFACE}
        route add -net x.y.z.0 netmask 255.255.255.0 gw 10.0.2.2
    }
    
    stop(){
        poff ${PEER}
    }
    
    case "$1" in
        start)
            start
            ;;
        stop)
            stop
            ;;
        restart|force-reload)
            stop
            start
            ;;
    esac
    
  4. とりあえず,ここまでで普通に接続できるようになっているはずです. 以下のコマンドを実行して試してみてください.
    # /etc/init.d/example-ondemand start
    
  5. しかし,これでしばらく使っていると,防火壁内のマシンに既に不要になったはずの slirp のプロセスが大量に残っている状態になると思います. どうやら,pppd の pty オプションで起動した子プロセスは, pppd 本体が終了するまで残ってしまうようなので, 以下のような /etc/ppp/ip-down.d/example-ondemand を用意して, 明示的に子プロセスを停止するように工夫します.
    #!/bin/sh
    if [ x"$6" = xexample-ondemand ]; then
        su vpn /home/vpn/connect stop
    fi
    

Hostbased 認証

Hostbased 認証とは,登録済みのクライアントから接続があった場合は, そのクライアント上でのユーザー情報を信用して接続を許可する認証方式です. 多くのクライアントとサーバーが稼働しているサイトで適切に利用すると, ユーザーのパスワード入力の手間を減らすことができます. 設定手順は以下の通りです.

  1. クライアントのホスト鍵を作成しておきます.
    [client:~]# ssh-keygen -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key
    
    Hostbased 認証は,クライアントのホスト鍵,特に秘密鍵の秘匿性に基づいて認証を行っています. したがって,秘密鍵のファイル(/etc/ssh/ssh_host_rsa_key)の読み取り許可属性が適切に設定されていることを確認しておく必要があります.
    [client:~]$ ls -l /etc/ssh/ssh_host_rsa_key
    -rw-------    1 root     root          883 2002-01-01 12:00 /etc/ssh/ssh_host_rsa_key
    
  2. クライアント側の /etc/ssh/ssh_config に以下の設定を追加して,server に対して通信を行う時は Hostbased 認証を利用するように指定します.
    Host server
      HostbasedAuthentication yes
    
    OpenSSH-3.8 以後は,以下の指定も加える必要があります.
    EnableSSHKeysign yes
    
    この指定は,対象となるホストが指定されていない部分に書く必要があります.
  3. サーバー側の /etc/ssh/sshd_config に以下の設定を追加して,Hostbased 認証を受け付けるように指定します.
    HostbasedAuthentication yes
    
    以下は Hostbased 認証と直接の関係はありませんが,一般ユーザーによる Hostbased 認証の乱用を禁止するために設定しておくべき項目です.
    IgnoreRhosts yes
    IgnoreUserKnownHosts yes
    RhostsAuthentication no
    RhostsRSAAuthentication no
    
  4. サーバーにクライアントの公開鍵を登録します. なお,DNS spoofing 攻撃を避けるため,クライアントのホスト名は FQDN で指定し, クライアントの IP アドレスも同時に指定しておくと良いでしょう.
    [server:~]# ssh-keyscan -t rsa client,address >> /etc/ssh/ssh_known_hosts
    
  5. クライアントからの接続を受け付けるように指定します.
    [server:~]# echo client >> /etc/ssh/shosts.equiv
    

これで,クライアントからサーバーに対してパスワード入力を省略して ssh 経由でログインすることができるようになっているはず‥‥でしたが, 私は以下のような問題で苦労しました.

OpenSSH-3.4p1 のバグ

OpenSSH-3.4p1 はバグのために,Hostbased 認証が正常に機能しないようです. パッチを適用する必要があります.

ProxyCommand との衝突

ProxyCommand と Hostbased 認証は共存できません. ProxyCommand設定されている状態で Hostbased 認証を行おうとすると,以下のようなエラーメッセージが表示されます.

% ssh server
userauth_hostbased: cannot get local ipaddr/name

OpenSSH-3.6p1 以降は ProxyCommand の設定を無効化することができるらしいので, Hostbased 認証を行うホストを対象として ProxyCommand を無効化するという設定を試してみたのですが,手元では正しく動作しませんでした. したがって,現在のところ簡単な解決策はなく, ProxyCommand を利用するホストを小まめに指定するしかないようです.

補助プログラムの実行許可属性

Hostbased 認証には,クライアントの秘密鍵の情報が必要です. 通常,秘密鍵は root 以外は読み出せないようになっていますから, OpenSSH は ssh-keysign という setuid root された補助プログラムを利用して認証を行っています.

補助プログラムが setuid root されていないと, 以下のようなエラーメッセージが表示されました.

% ssh server
could not open any host key
ssh_keysign: no reply
key_sign failed

Debian の場合は,dpkg-recongfigure ssh というコマンドを実行して, "Do you want /usr/lib/ssh-keysign to be installed SUID root?" という質問に「Yes」と答えると,適切な実行許可属性に設定されます. それ以外の環境では,以下のコマンドで修正できるでしょう.

# chmod u+s /usr/lib/ssh-keysign

商用 SSH との相互運用

久しぶりに,OpenSSH ではなく商用の SSH がインストールされている環境を使うことになったのですが, 公開鍵認証によるログインがうまくいかずに,かなり悩みました.

より詳しい情報は, 「OpenSSH と SSH の相互運用」や 「相互運用のための鍵管理」を参照してください.


設定内容のデバッグ

クライアント側の設定に問題があると考えられる場合は, -v オプションを指定してクライアントを実行した結果を調べると原因が分かることがあります.

% ssh -v server

サーバー側の設定に問題があると考えられる場合は, -d -d -d オプションを指定してサーバーを実行した結果が有用です.

# /etc/init.d/ssh stop
# /usr/sbin/sshd -d -d -d

[Top] / [SSH について]