この記事は、ニフティグループ Advent Calendar 2024 20日目の記事です。
はじめに
こんにちは。会員システムグループに所属しています、たかたか と申します。
最近、健康への意識が高まり、運動を始めました。ただし、食事制限はまだ行っていません。無理をせず徐々に健康体へ近づいていければと思っています。
さて、本ブログでは、最近起こしてしまった失敗について書いていこうと思います。エンジニアとしての仕事や日常生活の中で、もしかしたら起こるかもしれないミスや失敗を共有することで、皆さんの参考になればと思います。
起こったこと
とあるシステムのリプレイスのメンテナンスが終了した後、それは起こってしまいました。
なんと、HTTPSでの接続で証明書の検証がうまくいかず、サーバー間の通信ができなくなってしまったのです。
まあ、そんなこともあるよね、と感じていただけている方もいるかと思いますがその諸々について書いていこうと思います。
まずは証明書の検証について確認
まず、証明書の検証について、簡単に説明させていただきます
- まず、クライアントがサーバーにHTTPS接続をお願いします
- サーバーは自分の身分証明書(デジタル証明書)をクライアントに見せます
- クライアントは以下のようなチェックを行います:
- 信頼できる機関が発行した証明書かどうか確認
- 証明書の親子関係(証明書チェーン)が正しいか確認
- 証明書の期限が切れていないか確認
- 証明書が無効になっていないか確認
- 証明書に書かれている名前と接続先が同じか確認
- すべての確認がOKだった場合、安全な通信路を作ります
- 暗号化のための鍵を作って交換
- 安全な通信をスタート
このチェックの中で一つでも問題があると、接続できなくなります。
経緯
AWSのマネジメントコンソールから必要なリソースを作成し、システムの構築を進めていました。その中で、サーバー間の通信にはHTTPSを使用することになっていたのですが、中間証明書とサーバ証明書の設定を間違えてしまいました。
- この設定画面です
本番環境へのデプロイ前の検証では、curlコマンドで動作確認を行ってしまい、問題に気付くことができませんでした。
仕組み的にどうすればよかったのか
証明書の検証について、今回原因となったのは設定を間違えてしまったというところが根本的な原因です。しかし、ちゃんと動いているかどうかの確認で、HTTPのリクエストを送るツールが証明書をどのように、どのような環境で検証しているかというところも大事なポイントだと思っています。
サーバー証明書はPEM形式で
- サーバー自身のサーバー証明書
- 中間証明書
- ルート証明書(通常はサーバーには含めません)
の内容を記載することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# サーバー証明書 -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- # 中間証明書 -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- # ルート証明書(通常はサーバーには含めません) -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- |
webブラウザなどは中間証明書の内容がなくてもサーバー証明書の内容から補完することができたりしますが、スクリプトからhttpリクエストを送信しようとすると失敗することがよくあります。
サーバー証明書の設定は、使用するツールや環境によって検証結果が異なる場合があります。実際の運用環境では、複数のツールで検証を行うことが重要です。それでは、具体的な検証例を見ていきましょう。
検証
試しに、記載されているチェーンが不完全な証明書を用意し、検証してみましょう。
- 設定を間違った証明書
1 2 3 4 5 6 |
# サーバー証明書だけを書く -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- |
以下のツールを使用して、私の端末で検証したいと思います
- curl
何かhttpリクエストを送りたいときよく使いますよね。 今回は動作検証をこれだけで実施してしまったので、失敗に気づけなかったです。 基本的に、ローカルに保存されている証明書ストアの情報と照合してちゃんと検証が行われるはずですが、私の使用している端末ではたまたまラッキーで証明書の検証が通過してしまいました。 - pythonのrequestsモジュール
pythonでhttp通信を行うときの鉄板ですよね。certifiというライブラリに保存されている証明書群に依存しており、ブラウザのように証明書の情報を自動で補完したりすることはないです。誤った証明書が設定されている等の原因で通信ができなくなったりします。 - openssl
証明書の検証といえばこれが使用されます。 証明書のチェーンが間違っていれば自動補完とかはせず、検証に失敗しますので、ここがOKなら大丈夫でしょう。
※入力・出力結果は一部改変しています
- curlの実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ curl -D - <https://example.com> HTTP/2 200 server: Apache/2.4.6 (CentOS) cache-control: no-store, no-cache content-type: text/html; charset=UTF-8 date: Mon, 16 Dec 2024 07:40:40 GMT pragma: no-cache set-cookie: dnzHashcmd=fin; content-length: 6879 コンテンツ ... |
普通に返ってきましたね。
何か異常は見受けられません
- requestsの実行結果
1 2 3 4 5 6 |
Python 3.11.0 (main, Mar 15 2024, 14:30:35) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import requests >>> requests.get("<https://example.com>") requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)'))) |
失敗しておりますね。
エラー内容を見ると、SSLCertVerificationErrorの文字が。
- opensslの実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ openssl s_client -connect example.com:443 -showcerts 0 s:/C=JP/ST=Tokyo/L=Minato-ku/O=Example Corporation/CN=www.example.co.jp i:/C=JP/O=Example CA/CN=Example Intermediate CA -----BEGIN CERTIFICATE----- hoge(サーバー証明書の内容)hoge -----END CERTIFICATE----- Server certificate subject=/C=JP/ST=Tokyo/L=Minato-ku/O=Example Corporation/CN=www.example.co.jp issuer=/C=JP/O=Example CA/CN=Example Intermediate CA --- SSL handshake has read 2482 bytes and written 757 bytes Verification error: unable to verify the first certificate --- New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 Protocol: TLSv1.3 Server public key is 2048 bit This TLS version forbids renegotiation. Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 21 (unable to verify the first certificate) ... |
Verification errorが発生してますね。
このような結果から 次のような教訓を得ることができました。
第一に、サーバー証明書の検証は使用するツールによって結果が異なる可能性があるため、複数のツールで確認することが重要。
第二に、curlコマンドだけでの検証は不十分で、より厳密な検証ツール(opensslなど)を併用する必要があります。
最後に
使用している技術への理解を深め、起こりうるミスのパターンを網羅できれば完璧なのですが、中々そうはいきませんよね。
エンジニアとしての活動がまだまだ短い私ではありますが、これから沢山経験を積んで成長していければと思っています。
明日は、swimland0306 さんの「ニフティにSREとして入社して2年間でやってきたこと」です。
お楽しみに!