Featured image of post Azure SDK for PythonでCERTIFICATE_VERIFY_FAILEDを雑に回避する

Azure SDK for PythonでCERTIFICATE_VERIFY_FAILEDを雑に回避する

あくまで検証用途 本番でやっちゃダメよ

まとめ

Azure SDK for Pythonを使っていてサービスへの接続時に証明書検証エラーSSL: CERTIFICATE_VERIFY_FAILEDが出た時、
雑に回避したかったら各クライアントのコンストラクタにconnection_verify=Falseを渡せば良い。
(本番運用するような場合はちゃんとルート証明書を端末のRootCAに追加しよう!怒られても知らないぞ!)

# こんな感じ
blob_service_client = BlobServiceClient.from_connection_string(connect_str, connection_verify=False)

経緯

会社のうんこPC ちょっと特殊な環境で開発しているときにAzure SDK for PythonからAzureのサービスを呼び出したところ証明書エラーが出てしまった。

azure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)

このエラーは実行環境が信頼しているCA証明書でサーバー(Azure)側の証明書検証がしてしまうというもので、
正攻法としては信頼するCA証明書のリストにAzureのルート証明書を追加すれば解決するのだが、
PCがうんこすぎてそこらへん全然自由にいじれない
割と急いでたり動作検証で一回だけ通ればよかったりするときにわざわざ証明書をいじるのはけっこうめんどくさい。
全然汚い方法でいいからサクッと解決したくなった(=証明書検証自体をスキップしたい)。

環境

手順

キーワード引数を指定

とりあえず使うコードはこんな感じ。
対象とするAzureサービスはなんでもいいが今回はBlob Storageのクライアントを使う。

これを特定の環境で実行したとき、次のようなエラーが出るものとする。
(接続文字列はportalから取得できる)

# 依存関係入れる
$ pip install -r requirements.txt
# ...

# 接続文字列を環境変数に設定
$ export AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net

# スクリプト実行、証明書検証エラー発生
$ python main.py
Traceback (most recent call last):
  File "/test/main.py", line 14, in <module>
    for container in container_list:
                     ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/azure/core/paging.py", line 123, in __next__
    return next(self._page_iterator)
  File "/usr/local/lib/python3.13/site-packages/azure/core/paging.py", line 75, in __next__
    self._response = self._get_next(self.continuation_token)
                     ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/azure/storage/blob/_models.py", line 538, in _get_next_cb
    return self._command(
           ~~~~~~~~~~~~~^
        marker=continuation_token or None,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        maxresults=self.results_per_page,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        cls=return_context_and_deserialized,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        use_location=self.location_mode)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/azure/core/tracing/decorator.py", line 105, in wrapper_use_tracer
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.13/site-packages/azure/storage/blob/_generated/operations/_service_operations.py", line 685, in list_containers_segment
    pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        _request, stream=_stream, **kwargs
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 240, in run
    return first_node.send(pipeline_request)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  [Previous line repeated 2 more times]
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/policies/_redirect.py", line 204, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py", line 555, in send
    raise err
  File "/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py", line 527, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  [Previous line repeated 1 more time]
  File "/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py", line 301, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 96, in send
    response = self.next.send(request)
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py", line 128, in send
    self._sender.send(request.http_request, **request.context.options),
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/azure/core/pipeline/transport/_requests_basic.py", line 409, in send
    raise error
azure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)

ここで証明書検証をスキップするようにしたいが、
使ってるクライアントにそれっぽいオプションがなさそう…詰んだ…
と思ったが、ちょっとエラーを調べるとこのクライアントが内部で呼び出してるクラスとそのパラメータみたいなのが出てきた。

(BlobServiceClientがHTTPリクエストを送るメソッドの内部でConnectionConfigurationを呼び出していて、このclassにはconnection_verifyといういかにも証明書検証の有無を司ってそうなパラメータがある)

試しにキーワード引数(kwargs)をクライアントのコンストラクタに渡してみよう…

コードを書き換えて再度実行したところ、
「ちゃんと証明書検証したほうがいいぞ」的な警告は出るものの今度はエラーにならずちゃんと呼び出しが成功した(コンテナ名の一覧が表示できた)。

$ python main.py
/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'stuzimihsrexample.blob.core.windows.net'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
example-container-1
example-container-2

azure.core.configuration.ConnectionConfigurationは他のAzureサービスのクライアントでも通信時に使われているようなので、
同様に各クライアントのコンストラクタ引数にconnection_verify=Falseを渡せば証明書検証を回避できるはず。

やったぜ。

(証明書エラーの再現手順)

普通の環境で実行しても証明書エラーに遭うことはなかなかないので、
今回は問題を再現するため以下の手順で信頼するCA証明書が欠けているような状態を作った。

雑にDockerでPythonコンテナを立ち上げ、RootCAのリストを取得する。

$ # 作業ディレクトリ
$ pwd
/path/to/tempdir
$ ls -1 test
main.py
requirements.txt

$ # testディレクトリをマウントしてPythonコンテナを立て、そのままbashで入る
$ docker container run --rm -it -v /path/to/tempdir/test:/test python:3 /bin/bash
root@f8a544bd9c26:/#

root@f8a544bd9c26:/# # 依存関係入れる
root@f8a544bd9c26:/# cd test
root@f8a544bd9c26:/test# pip install -r requirements.txt
# ...

root@f8a544bd9c26:/test# # RootCAの場所を確認
root@f8a544bd9c26:/test# python
Python 3.13.2 (main, Mar 18 2025, 22:43:33) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import certifi
>>> certifi.where()
'/usr/local/lib/python3.13/site-packages/certifi/cacert.pem'
>>> exit

root@f8a544bd9c26:/test# # RootCAをコピーしてホストOS側で編集する
root@f8a544bd9c26:/test# cp /usr/local/lib/python3.13/site-packages/certifi/cacert.pem ./cacert.pem

拾ってきたRootCAをホストOS側から任意のエディタで開き、
# Issuer: CN=DigiCert Global Root G2の部分のpem(画像では1740~1768行目)をごっそり削除する。

DigiCert Global Root G2を削除

(Blob StorageのRootCAがDigiCert Global Root G2であることはブラウザで確認した。)

Blob Storageの証明書のRootCAはDigiCert Global Root G2

これで信頼されたRootCAのリストからBlob StorageのRootCAが消えたので、
これをPythonが参照するように上書きする。

root@f8a544bd9c26:/test# # 編集後のcacert.pemで上書き
root@f8a544bd9c26:/test# cp ./cacert.pem /usr/local/lib/python3.13/site-packages/certifi/cacert.pem

あとはAzure portalから接続文字列をコピーしてきて環境変数に張り付けて、

root@f8a544bd9c26:/test# # 接続文字列を環境変数に設定
root@f8a544bd9c26:/test# export AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net

コードを実行したらちゃんとエラーになった。

root@f8a544bd9c26:/test# python main.py
Traceback (most recent call last):
  File "/test/main.py", line 14, in <module>
    for container in container_list:
                     ^^^^^^^^^^^^^^
  #...
azure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)

おわり

うんこPCで開発すんのマジしんどい、営業マンと同じ構成のポンコツ制限PCをエンジニアに使わせるんじゃないよ  

超久しぶりにPython触ったら色々わかんなすぎて泣いてしまった。
やっぱりGoがいいなあ…

おまけ

自由を与えすぎると危険になる例