harumaki.net

インフラ屋の覚書や、ラーメン食べある記とか。

[PHP] RedHat/CentOSのcurlで”SSL Connection Errorが発生しちゃった件

   


モバイルサイトのキャリア通信にて、ユーザー認証の部分でcurlを使っているが
アプリサーバーのOSをCentOS5から6へ上げて検証していたところ、開発チームから“SSL Connection Error”のエラーが発生しちゃった~、とのヘルプ依頼が。ということでトラブルシュートのお手伝い!

▼環境

1)もともとのサーバーのcurlバージョン(CentOS5 i386 32bit)

curl-devel-7.15.5-17.el5_9
curl-7.15.5-17.el5_9

2)AWSでセットアップしたCentOS6.6のcurlバージョン(CentOS6 X64_64 64bit)

libcurl-devel-7.19.7-40.el6_6.4.x86_64
python-pycurl-7.19.0-8.el6.x86_64
curl-7.19.7-40.el6_6.4.x86_64
libcurl-7.19.7-40.el6_6.4.x86_64

CentOS5ではlibcurlもcurlに含まれていたが、CentOS6からはパッケージが別れている。が、この部分は特に問題なし。

▼ここで疑問

“php -i”で確認したところ、CentOS5環境ではopensslが使われていたが、CentOS6ではNSSが使われている?これは何?

– Cent5でphpをビルドしたときの環境

cURL Information => libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5

– Cent6でphpをビルドしたときの環境

cURL Information => libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2

参考までにcURLの公式サイトchangelogを見てみたが、過去のアップデートでもopensslからNSSを使うよう変更した、という履歴は見当たらず。うーん。

▼原因・理由が判明~

そのままRPMのほうのアップデートやエラータを見てみたところ、理由が判明しました!
RedHat Enterprise Linux 6のRPM版パッケージのエラータのドキュメントではNSS APIを使うように変更されていた!

参考:https://rhn.redhat.com/errata/RHBA-2012-0430.html
– 抜粋:

==============================================
* Previously, SSL connections could not be established with libcurl if the
selected Network Security Services (NSS) database was broken or invalid. This
update modifies the code of libcurl to initialize NSS without a valid database,
which allows applications to establish SSL connections as expected in this
scenario. (BZ#800903)

* The OpenLDAP suite was recently modified to use NSS instead of OpenSSL as the
SSL back end. This change led to collisions between libcurl and OpenLDAP on NSS
initialization and shutdown. Consequently, applications that were using both,
libcurl and OpenLDAP, failed to establish SSL connections. This update modifies
libcurl to use the same NSS API as OpenLDAP, which prevents collisions from
occurring. Applications using OpenLDAP and libcurl can now connect to the LDAP
server over SSL as expected. (BZ#800904)
======================================================

翻訳版:有難うございます!
http://www.nanajo.com/blog/hogex/?p=260

さらに、specファイルを確認したところ、以下の更新履歴を確認。本家のchangelogには無かったが、RedHatの更新履歴にあった!

– 履歴抜粋

* Thu Apr 07 2011 Kamil Dudka <kdudka@redhat.com> 7.19.7-26
– force NSS to ask for a new client certificate when connecting second time
to the same host (#694294)

なるほど、納得しました。

▼ということで、対応~

OSは引き続きRedHat6/CentOS6を使うことになるが、curlはRPMでインストールしたい…ということなので、対策として考えられるのは、以下の通り。

1.RedHat6/CentOS6のcurl-7.19.7-26以降のをソースRPMを使って、opensslを使うようリビルド

→こちらは、結論としては困難。
タイミング悪く7.19.7-26でクライアントの認証で強制的にNSSを使うようにパッチが当てられているため。

2.cURL公式サイトからコンパイル済みのRPMをダウンロードし、デフォルトのcurl-7.19からアップデートする

→こちらも試したが、依存関係にあるlibssh2などののバージョンが古く、依存関係をそろえるための手間が…たいへん!

3.cURL公式サイトからソースRPMをダウンロードし、RPMパッケージをコンパイルして
デフォルトのcurl-7.19からアップデートする

いろいろ試したところ、これが一番スマート、というかマシといえたので、この方法を本番環境に適用することにしました。
ということで、以下は作業ログ。

1. 作業ディレクトリをrootのホームに指定。

rpmbuildをするので、今回は初めからrootにsu。
デフォルトではディレクトリが存在していなかったので、作成。

# cd /root/
# mkdir -p rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

2. ダウンロードと展開

curlの本家サイトのダウンロードページにて案内されていたミラーサイトから
直接ソースRPMをダウンロード。

※2015.06.19更新、パッケージが更新されていたのでダウンロード元URLを更新しました。ちょくちょくアップデートされますね。
http://mirror.city-fan.org/ftp/contrib/sysutils/Mirroring/libcurl-7.43.0-1.0.cf.rhel6.x86_64.rpm

# cd /root/rpmbuild/SRPMS/
# wget http://mirror.city-fan.org/ftp/contrib/sysutils/Mirroring/curl-7.40.0-1.0.cf.rhel6.src.rpm
# rpm -Uvh curl-7.40.0-1.0.cf.rhel6.src.rpm

3. パッケージの作成

# cd ../SPECS/
# rpmbuild -bb curl.spec
エラー: ビルド依存性の失敗:
libmetalink-devel は curl-7.40.0-1.0.cf.rhel6.x86_64 に必要とされています
c-ares-devel >= 1.6.0 は curl-7.40.0-1.0.cf.rhel6.x86_64 に必要とされています
perl(Time::HiRes) は curl-7.40.0-1.0.cf.rhel6.x86_64 に必要とされています

4. 足りていないパッケージを追加

# yum install libmetalink libmetalink-devel c-ares-devel perl-Time-HiRes

※環境により依存関係は異なるので、自分の環境を確認する。
※libmetalink、libmetalink-develはepelリポジトリなので要注意。epelが入ってない場合はインストールする。

5. 再度rpmbuildを実行。

# rpmbuild -bb curl.spec
--- 中略 ---

6. 終わったら、パッケージの確認

RPMパッケージは、/root/rpmbuild/RPMS/x86_64/に作成される。

# ls -al /root/rpmbuild/RPMS/x86_64/
合計 2904
drwxr-xr-x 2 root root 4096 1月 25 13:44 2015 .
drwxr-xr-x 3 root root 4096 1月 25 13:44 2015 ..
-rw-r--r-- 1 root root 358164 1月 25 13:44 2015 curl-7.40.0-1.0.cf.rhel6.x86_64.rpm
-rw-r--r-- 1 root root 1618000 1月 25 13:44 2015 curl-debuginfo-7.40.0-1.0.cf.rhel6.x86_64.rpm
-rw-r--r-- 1 root root 311712 1月 25 13:44 2015 libcurl-7.40.0-1.0.cf.rhel6.x86_64.rpm
-rw-r--r-- 1 root root 650944 1月 25 13:44 2015 libcurl-devel-7.40.0-1.0.cf.rhel6.x86_64.rpm

7. インストール

– まずテスト

# rpm -Uvh --test curl-7.40.0-1.0.cf.rhel6.x86_64.rpm libcurl-7.40.0-1.0.cf.rhel6.x86_64.rpm libcurl-devel-7.40.0-1.0.cf.rhel6.x86_64.rpm
準備中... ########################################### [100%]

-問題ない様子なので、インストールを実施。

# rpm -Uvh curl-7.40.0-1.0.cf.rhel6.x86_64.rpm libcurl-7.40.0-1.0.cf.rhel6.x86_64.rpm libcurl-devel-7.40.0-1.0.cf.rhel6.x86_64.rpm

インストール後、phpinfoにて確認。

8. バージョン確認

$ php -i

または

$ php -r 'phpinfo();' | less

— 実行結果の抜粋

cURL Information => libcurl/7.40.0 OpenSSL/1.0.1e zlib/1.2.3 c-ares/1.10.0 libidn/1.18 libssh2/1.4.2

→NSSからOpenSSLに代わっていることを確認。以上で対応終了!
curl、libcurl、libcurl-develを(RedHat謹製でないものに)アップデートすれば、php側の再コンパイル/インストールは不要。
ただし、openldapを利用する環境では、当然使えないので要注意。

▼まとめると…

  • redhat6のRPM版curlでは、openldapとの兼ね合いで強制的にNSS APIを使うようパッチ対応していた。
  • この変更は、curlの本家サイトでは適用されていない。
  • このためredhat/CentOSのcurlでは、PHPにてOpenSSLの利用を前提とした環境では、SSLエラーが出る。もちろんNSSが悪いわけではないが、ソースの改修の手間を天秤にかけて、今回はOpenSSLでいくことに。
  • この環境での解決方法としては、curl公式サイトにてソースRPMを落としてきてrpmbuildするのが一番近道だった。
  • libcurl.soが更新できれば、phpの再コンパイルは不要。
  • OpenLDAPを利用する環境だとエラーが起きる可能性が大きそう…今回はLDAPは未導入なので未確認ですが、一つのシステムで同居させない、といった配慮も必要になりますね。
  • そして技術的負債というかバッドノウハウというか、curlに限りアップデートにイレギュラー対応が必要になる…のが心苦しいところ。“運用で何とかする”で片づけないで課題として時間ができたら解決していこう…今は時間が無いのでまた今度(汗)

以上でした~

 - CentOS, php, RedHat, トラブルシュート