freeradiusを触った時に経路の暗号化をしないと潜在的なリスクがあると思ったので、IPsecをtransport modeで試してみる事にしました。
今回の鍵作成の作業はApache2用にSSLキーを作成した時と同じで、Ubuntu 8.04 LTSで行なっています。作成する鍵ファイルは次の3種類です。
- newreq.pem - リクエストファイル。秘密鍵ファイルを作成するために使用
- newkey.pem - 秘密鍵ファイル。newreq.pemを元にCAが署名し、生成される
- newcert.pem - 公開鍵ファイル。newkey.pemと対になる
opensslやCA.plを使った鍵作成の手順を説明したサイトはいくつかありますが、独自のファイル名を使うため鍵ファイルの用途がわかりずらいと感じました。 この記事ではCA.plが作成するファイル名を踏襲しました。
今回の作業全体の流れは7.3.3.2 公開鍵を使った方法にある流れに従っています。 ただ、これだけでは不十分だったので、より正確な情報をIPsec HowtoとThe PKIX certificate with racoon、それにipsec-tools.sourceforge.netからのリンク先にあるAutomatic keyingから補完しました。
なおIPsecが稼働するサーバはAlixとWRAPで稼働するDebian lennyです。
鍵ファイルの作成
CAとしてはApacheサーバ用にSSL用鍵ファイルを作成した時に使ったdemoCAをずっと使っていますが、手を広げ過ぎたので鍵ファイルの管理が煩雑になってきました。
そこで今回はIPsec用に中間CAを作成するところから始めました。 必要がなければ飛して既にあるdemoCAディレクトリを使えば良いと思います。
中間CA用demoCAの作成
まずは/usr/lib/ssl/misc/CA.plを使って、土台となるdemoCAディレクトリを準備します。 いま親CAのdemoCAディレクトリがみえるディレクトリにいるとして、中間CA用にサブディレクトリを作成する事にします。
$ mkdir demoCA.ipseq_ca $ cd demoCA.ipseq_ca $ /usr/lib/ssl/misc/CA.pl -newca
入力項目は任意なのでパスワードだけ忘れないようにしておきます。 この作業が終るとカレントディレクトリにdemoCAディレクトリが作成されています。
親CAによる中間CA用reqファイルの署名
いまはdemoCA.ipseq_caディレクトリ直下にいて、そこには"demoCA"ディレクトリだけが存在している状態を仮定します。
このディレクトリの中で単独のCAとして必要なものは揃っていますが、中間CAとなるために親CAに署名をしてもらう必要があります。 通常CAを作成するときに使うCA.pl -newcaコマンドでは、リクエストファイルを作成して、リクエストファイルへの署名をdemoCA内にある自身の秘密鍵で行なっています。
今回はその署名作業を親CAに行なってもらう必要があるので、いちど作成したcacert.pemを捨てて、改めてcareq.pemファイルを親CAに署名してもらいます。
$ cp demoCA/careq.pem ../newreq.pem $ cd .. $ /usr/lib/ssl/misc/CA.pl -signCA
ここまでの作業で"newreq.pem"ファイルが新たに作成されました。 これを中間CAのdemoCAディレクトリにコピーします。
$ cd demoCA.ipseq_ca $ rm demoCA/cacert.pem $ mv ../newcert.pem demoCA/cacert.pem
ここでcacert.pemを削除せずに上書きしても良いのですが、作業内容をはっきりとさせるために削除するようにしています。
作業が終ったらnewreq.pemファイルは削除しておきます。
$ rm ../newreq.pem
これで中間CAを作成する作業は完了です。
IPsec用の鍵ファイルの作成
中間CAはできたので、IPsecで2点間の通信を暗号化するための鍵ファイルを作成します。 ここでは最終的に"newkey.pem", "newcert.pem"の2つのファイルを1セットとして作成します。 サーバ2台でテストをするために、合計で4ファイル、2セットを作成する事になります。
$ /usr/lib/ssl/misc/CA.pl -newreq $ /usr/lib/ssl/misc/CA.pl -sign $ mkdir server1 $ mv new* server1/
作業ではCommon Name(CN)をサーバのFQDNにしておきます。 作業の結果作成された"newkey.pem", "newcert.pem"ファイルをサーバ毎のディレクトリに分けておき、必要に応じて使う事にします。
続けて"server1"を"server2"と読み替えて、もう一度、この手順を繰り返します。
最後にserver1ディレクトリにserver2のnewcert.pemファイルをコピー(server2.newcert.pem)し、server2ディレクトリにも同様にserver1のnewcert.pemファイルをコピー(server1.newcert.pem)します。
ここまででserver1, server2ディレクトリにそれぞれ"newkey.pem"と"newcert.pem"ファイルと相手のnewcert.pemファイルが存在しているはずです。 treeコマンドを使うと、次のようなディレクトリ構造になっているはずです。
|-- demoCA ## 親CA
| |-- cacert.pem
. .
. .
. `-- careq.pem
.
`-- demoCA.ipsec_ca ## 中間CA
|-- demoCA
.
.
|-- server1/
| |-- newcert.pem
| |-- server2.newcert.pem
| `-- newkey.pem
`-- server2/
|-- newcert.pem
|-- server1.newcert.pem
`-- newkey.pem
実際のところはnewcert.pemとnewkey.pemが2組作れていれば良いので、この構造は参考に留めておいてください。 いろいろな鍵ファイルを作る手順をみていると、どこまでがMUSTで、どこからがSHOULDなのか判らないですよね…。
公開鍵方式を使ったIPsec接続
今回はracoonを使った鍵交換を行なうため、双方のシステムにracoonをインストールしておきます。
$ sudo apt-get install racoon
鍵ファイルの配置
サーバにserver1, server2ディレクトリをそれぞれコピーし、作成した鍵ファイルを/etc/racoon/certsディレクトリにコピーしておきます。 なお本当の環境では、サーバの秘密鍵"newkey.pem"はネットワークを経由する事はありません。相手側に渡さないように注意しましょう。
$ sudo mkdir /etc/racoon/certs $ sudo cd server1 $ sudo cp newkey.pem newcert.pem server2.newcert.pem /etc/racoon/certs/
他方のserver2サーバにログインして、同様にファイルを配置します。
racoon.confファイルの作成
ファイルの設定方法は本家サイトのドキュメントが信頼性が高そうです。 この他のドキュメントはKAMEの流れを組む*BSD用だったり、少し古いとそのまま使えないものもありました。
exchange_mode main;を使う場合には、IPアドレスでの指定だけが使えるため、IPv6とIPv4の設定を分けていますが、内容は同じです。
path certificate "/etc/racoon/certs";
remote 192.168.1.xx {
exchange_mode main;
my_identifier asn1dn;
peers_identifier asn1dn;
certificate_type x509 "newcert.pem" "newkey.pem";
peers_certfile x509 "server1.newcert.pem";
verify_cert on;
proposal {
encryption_algorithm aes;
hash_algorithm sha1;
authentication_method rsasig;
dh_group modp1024;
}
generate_policy on;
passive off;
}
remote 2001:03xx:xxxx:1::xxxx {
exchange_mode main;
my_identifier asn1dn;
peers_identifier asn1dn;
certificate_type x509 "newcert.pem" "newkey.pem";
peers_certfile x509 "server1.newcert.pem";
verify_cert on;
proposal {
encryption_algorithm aes;
hash_algorithm sha1;
authentication_method rsasig;
dh_group modp1024;
}
generate_policy on;
passive off;
}
sainfo anonymous {
pfs_group modp768;
encryption_algorithm rijndael, 3des;
authentication_algorithm hmac_sha1, hmac_md5;
compression_algorithm deflate;
}
CRLファイルの取り扱いについて
いまのところは、CRLやcacert.pemファイルの取り扱いについて、ちゃんとしたドキュメントをみつけられていません。 racoon.confのオプションには関連しそうなca_typeパラメータがありますが、使い方について明確な指示はみつけられませんでした。verify_certは相手方から送られてくるDNと手元のcertファイルのDNを比較するもののようで関係なさそうでした。
コードを少し追った限りではCRLのチェックはしているようにみえました。 しかしrevokeしたnewcert.pemファイルはpeers_certfileに指定した状態で、CRLファイルを配置してもhashファイルへのリンクを作成しても通信が拒否される事ありませんでした。
ハッシュファイルへのシンボリックリンクはc_rehashコマンドで行なうことができます。
$ sudo c_rehash /etc/racoon/certs
ここら辺の動きについてはもう少し確認が必要そうです。
/etc/racconディレクトリの内容
treeコマンドを使うと、現在の/etc/racoonディレクトリの構造は次のようになっています。
/etc/racoon/
|-- certs
| |-- server2.newcert.pem
| |-- newcert.pem
| `-- newkey.pem
`-- racoon.conf
ipsec-tools.confファイルの編集
少し古いドキュメントには/etc/setkey.confを編集する手順が載っていますが、Debian Lennyでは/etc/ipsec-tools.confファイルを編集します。 起動時に/etc/init.d/setkeyスクリプトが、/etc/rc2.d/S??setkey経由で実行されて、ipsec-tools.confの内容が反映されることになります。
#!/usr/sbin/setkey -f
#
flush;
spdflush;
## out rule (local -> remote)
## IPv4
spdadd 192.168.10.xx 192.168.1.yy any -P out ipsec
esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
esp/transport//require;
## in rule (remote -> local)
## IPv4
spdadd 192.168.1.yy 192.168.10.xx any -P in ipsec
esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:1::yyyy 2001:03xx:xxxx:10::xxxx any -P in ipsec
esp/transport//require;
稼働確認
両方のサーバで次のようにプロセスを再起動する事で設定が完了します。
$ /etc/init.d/setkey restart $ /etc/init.d/racoon restart
さいごに
IPsecを使ってみて気になったことをまとめておきます。
煩雑な設定
相手方を指定するところでは個別にpeers_certfileなどを指定する必要があるため、サイト全体に拡張しようとするとかなり煩雑になりそうです。 ファイルを互いに交換する仕組みだと鍵交換がn*(n-1)回必要になるので公開鍵方式の利点を殺したような状態になっています。
まぁネットワーク層レベルの仕組みですから、何を信頼するかという議論もあると思うんですけどね。
とはいえracoon.confファイルはincludeで、外部ファイルを読み込む事ができるので、他で検証した設定ファイルとpemファイル一式を展開してあげれば、だいたい安全に自動化ができそうな気配です。
しかしpeers_certfileはdnssecを引数に取るので、これが使えるかもしれないと思っています。 時間をみつけて検証していこうと思います。
おかしな挙動
試したdebian lenny同士の接続では、ipsec-tools.confでAHを有効(ah/transport//require;付き)にした状態で、しばらく使っていると反応がなくなりハングアップしてしまいました。
...
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
esp/transport//require
ah/transport//require;
...
これはpmtu関連のメッセージが大量にでていて、どうやら途中の経路にあるルータのMTUが1500よりも低いことが原因のようでした。 このルータをはさまないようにスイッチングハブのみを経由して互いを接続したところ、この問題は発生しませんでした。
ただIPv4は無事に接続できたのですが、IPv6だけはエラーになってしまいました。
...
Mar 1 08:59:26 localhost racoon: ERROR: phase1 negotiation failed due to time up. ac92e25b5eb92084:0000000000000000
Mar 1 08:59:37 localhost racoon: ERROR: phase2 negotiation failed due to time up waiting for phase1. ESP 2001:3e0:a31:10:20d:b9ff:fe12:f284[500]->2001:3e0:a31:10:20d:b9ff:fe03:4154[500]
...
これはracoonが正常に動いていないようにみえるのですが、ともかく設定を再び行ないます。
$ sudo /etc/init.d/racoon stop $ sudo /usr/sbin/setkey -F $ sudo /usr/sbin/setkey -FP
2,3分ほどしたら再び起動します。
$ sudo /etc/init.d/racoon start
両方のサーバでracoonが起動した事を確認します。 次にsetkeyでSPDを更新します。
$ sudo /etc/init.d/setkey restart
これで無事にIPv4, IPv6ともにtransport modeでIPsecを動かす事ができました。 たぶん/etc/ipsec-tools.confでAHを有効したり、無効にしたり編集している間におかしくなったんだと思います。
それにしても元々の問題はpmtuが正常に動けば問題ないはずなのですが、この直結に近い状態でもmtuの高い側から低い側へ接続すると比較的データ量の多そうな通信の応答がなくなってしまいます。 これはVMWare上でも同様の動きをしています。 tcpdumpでみるとARPやらICMPv6 Neighbor Discoveryには正常に反応しているので、接続は切れずにこのまま画面が固まり続けるんでしょうね。
試しにこの状態で$ sudo setkey -F、$ sudo setkey -FPを実行すると、反応は戻ってきました。 /etc/init.d/setkeyをリスタートしてみると、やはりまた元に戻ってしまいます…。 う〜ん、困ったなぁ…。
racoonプロセスの稼働状況
sshで相手方に接続している最中にどちらかのracoonプロセスを再起動すると、次のログのようにSADが再構成されるため、その間は通信ができなくなります。 切断はしませんでしたが、入力した文字はバッファに入ったまま応答がなくなってしまうので、使い方によってはクリティカルかもしれません。
...
Feb 28 13:38:16 localhost racoon: INFO: respond new phase 2 negotiation: 192.168.10.yy[500]<=>192.168.1.xx[500]
Feb 28 13:38:16 localhost racoon: INFO: Update the generated policy : 192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.1.xx[0]->192.168.10.yy[0] spi=250681865(0xef11a09)
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.10.yy[500]->192.168.1.xx[500] spi=221824184(0xd38c4b8)
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in"
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.10.yy/32[500] 192.168.1.xx/32[500] proto=any dir=out"
...