requestsを使ってhttpsのエンドポイントにAPIコールしたらSSLErrorになった。
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: /exampleapi (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)'),))
あえてHTTPS接続をする必要がないテスト時などは、以下の方法でSSL認証をしないことで回避できる。
requests.get('https://example.com/exampleapi', verify=False)
そうできない場合にどうするか。
原因
今回接続したいAPIは、Root認証局の証明書と中間認証局の証明書が必要だった。
エラーの原因は、システムがRoot認証局の証明書については持っていたが、中間認証局の証明書を持っていなかったことだった。
対策
この場合の対策は、要するに中間認証局の証明書を渡すことだが、2通りある。
1. verifyにRoot認証局の証明書と中間認証局の証明書のあるフォルダパスを渡す
Ubuntu/Debianの場合、Root認証局の証明書は/etc/ssl/certs
以下にある。
ここに中間認証局の証明書を配置する。
中間証明書は、ブラウザのURLバー脇の鍵アイコンをクリックし、証明書を見るとわかる。
今回は認証局としてEntrustを利用しているAPIであったため、Entrustのサイトから中間認証局の証明書を取得した。
その上で、c_rehash
コマンドで中間証明書を処理しておく。
(なお、手元の中間証明書の拡張子は.cerでなく.crtにしないとc_rehash
コマンドで処理されなかった。)
$ c_rehash /etc/ssl/certs
この状態で、コードを以下のように修正する。
requests.get('https://example.com/someapi', verify='/etc/ssl/certs')
これで、中間認証局の証明書も見てくれるのでSSLErrorがなくなる。
2. 中間認証局の証明書を含めた、単一の証明書ファイルを更新する
Ubuntu/Debianの場合、/etc/ssl/certs/ca-certificates.crt
に、設定済みの証明書をすべてまとめたファイルがある。
これに、中間認証局の証明書を含めることでも対応できる。
中間証明書が/tmp/intermediate-ca.cer
というパスにあるとして、以下のコマンドで行える。
$ mkdir /usr/share/ca-certificates/mylocal
$ cp /tmp/intermediate-ca.cer /usr/share/ca-certificates/mylocal
$ echo "mylocal/intermediate-ca.cer" >> /etc/ca-certificates.conf
$ sudo update-ca-certificates
これで/etc/ssl/certs/ca-certificates.crt
に中間認証局の証明書が入るので、1と同様、requestsのverify引数に渡せる。
requests.get('https://example.com/someapi', verify='/etc/ssl/certs/ca-certificates.crt')
また、REQUESTS_CA_BUNDLE
という環境変数にセットすれば、verify引数をセットしなくてもよくなる。
※requests公式docのSSL Cert Verification
$ REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt python
>> requests.get('https://example.com/someapi')
opensslコマンドでの確認方法
SSLErrorが出る原因を調べる際にopensslのコマンドが役に立った。
$ openssl s_client -connect example.com:443 -servername example.com
...
Verify return code: 21 (unable to verify the first certificate)
このように、return codeで何が問題かわかる。(この場合は中間証明書かRoot証明書が悪い)
問題ないときは、以下のようなコードが表示される。
Verify return code: 0 (ok)
参考にしたURL
- requestsのエラーでまず最初に見、ドキュメントの意訳がわかりやすくて助かった。
- .cer, .crt拡張子のファイル種別について
- requestsのssl認証に関する公式ドキュメント
- opensslでのデバッグについて