エラーメッセージ
Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
別の表記
- requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=8443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
ホスト、ポート、URL のパスはリクエストから取られます。最も内側の理由は状況によって異なり(「self-signed certificate in certificate chain」「Hostname mismatch…」「certificate has expired」)、行は「(_ssl.c:1082)')))」のようなソースの位置で終わります。
requests / urllib3 (Python) — requests.exceptions.SSLError wrapping urllib3 MaxRetryError
このエラーの意味
requests が urllib3 に接続を要求したところ、TLS ハンドシェイクで証明書の検証に失敗し、urllib3 は処理を断念しました(requests のデフォルトはリトライなしなので、最初の試行の後で断念します)。役に立つのは括弧の中の部分です。ここでは、requests が使う CA バンドルの中に、OpenSSL が発行者を見つけられませんでした。このバンドルはデフォルトでは certifi が持つ Mozilla のルートのコピーであり、OS のストアではありません。
発生する理由
requests はシステムのストアではなく certifi で検証している
よくあるupdate-ca-certificates でインストールした会社やプライベートの CA、Windows のストアや macOS のキーチェーンにある CA は無視されます。requests.certs.where() は certifi の cacert.pem を指しているからです。再現例が示しているのは、まさにこの状況です。
TLS を検査するプロキシが通信を再署名している
よくあるプロキシのルートが certifi に含まれていないため、公開 API へのリクエストが社内ネットワークでだけ失敗します。
サーバーが中間証明書を省略している
ときどきAPI のサーバーがリーフ証明書しか送信しない場合、OpenSSL はどのバンドルを使ってもルートまでたどり着けません。ブラウザーは中間証明書を取得することで、この問題を隠してしまいます。
REQUESTS_CA_BUNDLE または CURL_CA_BUNDLE が不完全なバンドルを指している
ときどき会社のルートだけを含むバンドルを指定すると、すべての公開ホストへの接続が失敗します(逆の場合も同様です)。これらの変数は certifi に追加されるのではなく、certifi を置き換えます。
修正方法
- 1.
verify= で正しいバンドルを渡す
verify には、CA バンドルファイルのパスか、ハッシュ化された CA ディレクトリのパスを指定できます。ホスト名と日付のチェックは有効なままです。
python import requests # A CA file (or a directory prepared with c_rehash) for this call requests.get("https://api.example.com/", verify="/etc/ssl/certs/ca-certificates.crt") - 2.
プロセスに REQUESTS_CA_BUNDLE を設定する
これをシステムのバンドル(自社の CA をそこにインストールした後)に向けると、そのプロセス内のすべての requests の呼び出しが、curl や OS のツールと同じルートを信頼するようになります。
bash # For every requests call in the process (also honoured: CURL_CA_BUNDLE) export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt # Debian/Ubuntu system bundle python3 app.py - 3.
OS のトラストストアを使う
起動時に truststore.inject_into_ssl() を呼び出すと、requests が OS を通じて検証するようになるため、バンドルを管理しなくても、IT 部門が配布したルートが機能します。
python # pip install truststore==0.10.5 — requests then verifies with the OS trust store import truststore truststore.inject_into_ssl() import requests requests.get("https://api.example.com/") - 4.
Session を一度だけ設定する
アプリケーションでは、公開ルートと会社のルートを含むバンドルを Session.verify に設定し、そのセッションを再利用してください。
python import requests session = requests.Session() session.verify = "/etc/ssl/private-ca/corp-ca-bundle.pem" # public roots + company root session.get("https://api.example.com/")