前回までで、stunnelを起動したものの接続テストがまだでした。 元々別件で使用した CouchDB Ruby用ガイドのCouchモジュールをSSLに対応させてみました。
クライアント認証は、サーバ(今回はstunnel)にクライアントの持っているCertificateを認めさせる事なので、クライアント用にCertificateを作成して、そのCA局情報をstunnelに登録しておきます。
ついでにRubyスクリプト側にcacertファイルを持たせて、サーバ認証もするようにしました。
SSLのサーバ認証とクライアント認証で必要なこと
今回の作業で stunnelのFAQやら RubyのNet::Httpsマニュアルやら、その他のドキュメントを参考にしました。
Rubyのマニュアルは素晴しい内容だと思いましたが、大抵のドキュメントはサーバ認証に必要なことと、クライアント認証に必要なことを、あまり分けて書いていないか、説明していない印象を受けました。
前回のStunnel用のPEMファイルを準備したのだって、ほとんどサーバ認証用に必要な作業です。
サーバ認証の流れ
今回はサーバ認証のためにcacert.pemをRubyスクリプトにも配置しますが、その作業を先にします。
- サーバ(stunnel)側にnewcert.pemとnewkey.pemを埋め込む (既にstunnel.pemとして完了済み)
- クライアント側にサーバのnewcert.pemに対応したcacert.pemファイルを配置する
- クライアントが動作し、手元のcacert.pemと送られてきたnewcert.pemの内容を確認した上で、Common Nameが接続に使用したサーバ名と一致するか確認する
変更個所は2点で、sslを使うためにrequireで"net/https"を指定します。
require 'net/https'
次にCouchモジュールのrequestメソッドを書き換えました。 今回はBasic認証周りの変更は外してあります。
def request(req)
client = Net::HTTP.new(@host, @port)
if @options.kind_of?(Hash) and @options.has_key?('cacert')
client.use_ssl = true
client.ca_file = @options['cacert']
client.verify_mode = @options['ssl_verify_mode'] if @options.has_key?('ssl_verify_mode')
client.verify_mode = OpenSSL::SSL::VERIFY_PEER if not @options.has_key?('ssl_verify_mode')
client.verify_depth = @options['ssl_verify_mode'] if @options.has_key?('ssl_verify_depth')
client.verify_depth = 5 if not @options.has_key?('ssl_verify_depth')
end
res = client.start { |http| http.request(req) }
unless res.kind_of?(Net::HTTPSuccess)
handle_error(req, res)
end
res
end
Rubyスクリプトから使う場合には、スクリプトと同じディレクトリに cacert.pemファイルを配置して、インスタンスメソッドの第3引数でHashオブジェクトを指定します。
couch = Couch::Server.new("home.example.org","5984", {
'cacert' => File.expand_path('cacert.pem', File.dirname($0))})
これでサーバ認証用の設定は終りです。cacert.pemをクライアント側のRubyスクリプトに読み込ませたのはサーバ認証のためでクライアント認証には反対にサーバ側にcacert.pemを配置します。
サーバ側に配置するcacert.pemファイルは、クライアント用に作成したnewcert.pemに対応するものであるのが後続作業のポイントになります。
クライアント認証用のPEMファイルの作成と配置
まずは作業内容の説明と準備
今回はサーバ用Certificate(newcert.pemとnewkey.pem)を作成する時に使用したのと、同じdemoCAを使います。
とはいえ、demoCAは同じでなくても構いません。 この先の作業に使うdemoCAは1つですから、クライアント用PEMファイルのdemoCAの事だと思って読んでください。
作業自体は「 Ubuntu 8.04 LTS上のApache2をSSL化」の「Apacheサーバ用のSSL鍵ファイルの準備」とほぼ同じです。
作業を"demoCA"のあるディレクトリで行なっていますが、これはUbuntuデフォルトのopenssl.cnfファイル(/etc/ssl/openssl.cnf)の中に、CA局のdirとして"./demoCA"が登録されていることに起因します。 これを変更すれば必ずしも"demoCA"のあるディレクトリで作業する必要はありません。
こう書いても常識的にはdemoCAディレクトリにcdしたくなるので、 まずは作業ディレクトリから$ ls -ld demoCAでdemoCAディレクトリがみえることを確認します。
$ ls -ld demoCA drwxr-xr-x 6 yasu yasu 4096 2010-11-15 20:13 demoCA
実際の作業はここから
次に、まずは"newreq.pem"ファイルを作成します。 注意点は次の2点です。
- 最初に入力する"PEM passphrase"は、後で作成する"newkey.pem(couchdb_stunnel_client_key.pem)"を使う時に必要になるので覚えておく
- クライアント側のcertファイルに埋め込むCommon Nameは何でも良い (サーバはcacert.pemしかみないため)
/usr/lib/ssl/misc/CA.pl -newreq
いろいろ入力しますが、次のような内容になっています。 入力したところは強調表示にしています。
PEM pass phrase: ijsafuiowe890rwe ... Country Name (2 letter code) []: JP State or Province Name (full name) []: Fukushima Locality Name (eg, city) []: AizuWakamatsu Organization Name (eg, company) []: Yet Another Sundial Org. Organizational Unit Name (eg, section) []: CouchDB Management Team Common Name (eg, YOUR name) []: couchdb@example.org Email Address []: admin@example.org Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: Request is in newreq.pem, private key is in newkey.pem
続けて"newcert.pem"と"newkey.pem"を作成します。 ここでは"demoCA"のdemoCA/private/cakey.pemを開くために必要なパスフレーズを入力します。 あとは'y'を入力して作業を完了するだけです。
/usr/lib/ssl/misc/CA.pl -sign
カレントディレクトリにPEMファイルを放置しておくと、わけがわからなくなるのでディレクトリに移動しておきます。 一緒に作成した時に使ったdemoCAのcacert.pemもコピーしておきます。
$ mkdir demoCA.couchdb_stunnel_client_keys $ cp demoCA/cacert.pem demoCA.couchdb_stunnel_client_keys/ $ mv newcert.pem newkey.pem demoCA.couchdb_stunnel_client_keys/ $ rm newreq.pem
デフォルトのnewcert.pemとnewkey.pemという名前も、配布する時には判りずらいので、それらしい名前にコピーしておきます。
$ cd demoCA.couchdb_stunnel_client_keys/ $ cp newcert.pem stunnel.client.cert.pem $ cp newkey.pem stunnel.client.key.pem
さてnewreq.pemを作成した時のパスフレーズは覚えておかないと使えないので、あまりお勧めしませんが、passphraseなしにアクセスできる"stunnel.client.key.nopass.pem"ファイルを作成する事もできます。
openssl rsa < stunnel.client.key.pem > stunnel.client.key.nopass.pem
ここまでで次のファイルが作成できました。
- cacert.pem
- stunnel.client.cert.pem
- stunnel.client.key..pem
- stunnel.client.key.nopass.pem (これはなくてもいい)
作成したPEMファイルの配置
いよいよ作成したファイルを配置します。
stunnelの動いているサーバ側での作業
ここではstunnel.client.cert.pemと、cacert.pemファイルを配置します。c_rehashは必須です。
$ sudo cp stunnel.client.cert.pem cacert.pem /etc/couchdb/certs $ sudo c_rehash /etc/couchdb/certs
クライアントのPEMファイルだけでは不十分で、そのPEMファイルが正規のものかを確認するために発行したCAのcacerts.pemファイルも必要なところがポイントです。
クライアント側でのファイル配置とスクリプトの変更
まずはRubyスクリプトのあるディレクトリに残りのPEMファイル(stunnel.client.*.pem)をコピーします。
まずはCouchDBモジュールのrequestメソッドを変更します。
def request(req)
client = Net::HTTP.new(@host, @port)
if @options.kind_of?(Hash) and @options.has_key?('cacert')
client.use_ssl = true
client.ca_file = @options['cacert']
client.verify_mode = @options['ssl_verify_mode'] if @options.has_key?('ssl_verify_mode')
client.verify_mode = OpenSSL::SSL::VERIFY_PEER if not @options.has_key?('ssl_verify_mode')
client.verify_depth = @options['ssl_verify_mode'] if @options.has_key?('ssl_verify_depth')
client.verify_depth = 5 if not @options.has_key?('ssl_verify_depth')
client.cert = @options['ssl_client_cert'] if @options.has_key?('ssl_client_cert')
client.key = @options['ssl_client_key'] if @options.has_key?('ssl_client_key')
end
res = client.start { |http| http.request(req) }
unless res.kind_of?(Net::HTTPSuccess)
handle_error(req, res)
end
res
end
次にRubyスクリプト本体を変更します。
ssl_client_cert = OpenSSL::X509::Certificate.new(File.new('stunnel.client.cert.pem'))
ssl_client_key = OpenSSL::PKey::RSA.new(File.new('stunnel.client.key.pem'),'ijsafuiowe890rwe')
couch = Couch::Server.new("home.example.org","5984", {
'cacert' => File.expand_path('cacert.pem', File.dirname($0)),
'ssl_client_cert' => ssl_client_cert,
'ssl_client_key' => ssl_client_key
})
"stunnel.client.key.nopass.pem"を使う場合は、第2引数のパスフレーズは省略します。 これでStunnelを経由してCouchDBに接続する事ができるようになりました。
RubyのEOFError
cacert.pemファイルの読み込みがうまくいかないと、Rubyスクリプト内で例外を保続してprint $!
をしてみてもなかなか不可解なエラーを発生します。
/usr/local/stow/ruby-1.9.2-p0/lib/ruby/1.9.1/net/protocol.rb:135:in `read_nonblock': Connection reset by peer (Errno::ECONNRESET) from /usr/local/stow/ruby-1.9.2-p0/lib/ruby/1.9.1/net/protocol.rb:135:in `rbuf_fill' ...
rbuf_fillとか、read_*メソッド関連のエラーは大抵はstunnelとかは関係なくて、クライアント側のコーディングとファイルの配置が間違っている可能性が高いです。
こういうエラーが出た場合には、curlやwgetでサーバが正しく動いているか確認するのがお勧めです。
$ cat stunnel.client.cert.pem stunnel.client.key.pem > stunnel.client.pem $ curl -X GET -cacert cacert.pem --cert stunnel.client.pem:ijsafuiowe890rwe https://home.example.org:5984/example/
stunnelを起動する時に -D 7デバッグオプションをつけると、facilityがdaemonでsyslogに書き出されます。 Ubuntuであれば/var/log/daemon.logを確認すると解決策が分かるかもしれません。
まとめ
自分でもサーバ認証とクライアント認証はごっちゃになったり、わけもわからずに*cert.pemファイルを配置してc_rehashコマンドを打つ場合があります。
自分でアプリケーションを作れば知識も整理できるんでしょうけれど、stunnelをいきなり使おうとするとPEMファイルの作成が最初のハードルになります。
いろいろなスクリプトやガイドはありますが、opensslを実行するコマンドラインに -config ./openssl.cnfとか足されていると、それだけで、コマンドが実行できずに混乱するかもしれません。
楽をしようとスクリプトを使っても、クライアント認証まで持っていくには、そういう手間を省こうとした事で余計に混乱するかもしれません。
突き詰めればreq.pemを経由したcert.pemとkey.pemの2つのファイルしか扱わないんですけどね。cacert.pemが絡んで、意味もわからずコマンドを打っていくと、うまく動かないなんてことにもなるでしょう。
とりあえずは自分用のdemoCAを作成して繰り返し使うのがお勧めです。
企業内利用を想定したクライアント認証用PEMファイルを生成するスクリプトなどを公開している方はいて、中身を理解して使う分には効率を大きくあげてくれます。
CouchDBはちょうどモチベーションを保つのに良いセキュリティのなさ加減だったので、良い教材になりました。
0 件のコメント:
コメントを投稿