6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAI API利用時のSSLErrorに対して証明書をダウンロードして解決する

Last updated at Posted at 2023-07-31

はじめに

本家のOpenAI APIや、Azure OpenAI ServiceのAPIを利用する際に、以下のような
SSL証明書関連のエラーが発生したので、対処法をいろいろ調べた。

APIConnectionError: Error communicating with OpenAI: HTTPSConnectionPool(host='<HOSTNAME>.openai.azure.com', port=443): Max retries exceeded with url: //openai/deployments/<MODEL-NAME>/chat/completions?api-version=<API-VERSION> (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1007)')))

↓※SSL証明書関連でよく出るエラーの例

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)

urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='hogehoge.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:997)')))

requests.exceptions.SSLError: HTTPSConnectionPool(host='hogehoge.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:997)')))

うまくいかない方法

requestsモジュール単体で使用する場合、デバッグを目的として一時的にverify=Falseオプションを指定することは可能ですが、一般的には推奨されません。

requests.get("https://www.google.com", verify=False)

これに似たようなオプションがOpenAIにも用意はされてるものの、

openai.verify_ssl_certs = False

実際openaiモジュールそのもののソースコードそのものを見ると、「もしFalseにしても効きません、証明書は常にverifyされます」と書いてあるので無駄です。

verify_ssl_certs = True # No effect. Certificates are always verified.

解決策

requestsモジュールは、certifiモジュール内のpemファイルを参照して証明書を検証するそうです。したがって、certifiモジュール内のpemファイルを使って検証できないと、当該エラーが発砲されるようです。

つまり、requestsモジュールが適切なpemファイルを参照できるようにすれば解決するようです。その解決策は以下の2パターンが考えられます

  • requestsモジュールが参照するpemファイルを編集する
  • requestsモジュールが参照するpemファイルのファイルパスを変更する

ので、とりあえずそのサイトにアクセスして証明書をダウンロードし、この証明書をテキストエディタで開いて、certifiモジュール内のpemファイルにコピペして上書きするか、もししくはソースコード内で各モジュールが参照するファイルパスを環境変数を上書きして指定すればよさそうです。
インターネットアクセスして証明書を検証するようなモジュールは「requests」「curl」「openssl(またはssl)」がありますが、これらそれぞれを設定します。

証明書のダウンロード方法(Edgeの場合)

OpenAI APIの場合

以下に記載の通り。

  1. https://api.openai.com/v1/engines にアクセスし、認証は無記名でキャンセルして閉じる
    image.png

  2. アドレスバーの近くにあるカギマークをクリックし、さらに右上の証明書っぽいアイコンをクリックする
    image.png
    image.png

  3. 「詳細」タブに切替え、ツリーの中で一番の親要素である「certadmin」を選択しエクスポートする
    image.png

  4. このファイルをテキストエディタで開いて、certifiモジュール内のpemファイルにコピペするか、もしくはソースコード中で、以下のように当該ファイルを指定するように環境変数を上書きする

import os
os.environ["REQUESTS_CA_BUNDLE"] = "./certadmin.crt"  # requestsモジュールが参照するパス
os.environ["SSL_CERT_FILE"] = "./certadmin.crt"  # openssl,sslモジュールが参照するパス
os.environ["CURL_CA_BUNDLE"] = "./certadmin.crt"  # curlモジュールが参照するパス

※ちなみに、openaiモジュールでは、1.x系から、requestsモジュール?ではなく、sslモジュールを使うようになった?っぽいです。いままではREQUESTS_CA_BANDLEを設定しときさえすればよかったのですが、openaiを1.x系にしたら動かなくなったので、記事を更新しました。

Azure OpenAI Serviceの場合

  1. 所望のAzure OpenAI Service(https://oai.azure.com/portal)にアクセスし,上述と同様にダウンロードする

実装例

自分用のメモ。python-dotenvを使った環境変数の外出し化含む実装例

.env
AZURE_OPENAI_ENDPOINT=https://HOSTNAME.openai.azure.com/
AZURE_OPENAI_KEY=YOUR-AZUREOPENAI-KEY
REQUESTS_CA_BUNDLE=./certadmin.crt
SSL_CERT_FILE=./certadmin.crt
load_envs.py
import os
from os.path import dirname, join

from dotenv import load_dotenv

load_dotenv(join(dirname(__file__), ".env"))

chat.py
# %%
# https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/chatgpt?tabs=python&pivots=programming-language-chat-completions
import os

from openai import AzureOpenAI

import load_envs

client = AzureOpenAI(
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    api_key=os.getenv("AZURE_OPENAI_KEY"),
    api_version="2023-05-15",
)

response = client.chat.completions.create(
    model = "deployment_name".
    messages=[
        {"role": "system", "content": "Assistant is a large language model trained by OpenAI."},
        {"role": "user", "content": "Who were the founders of Microsoft?"},
    ],
)

print(response)
print(response.model_dump_json(indent=2))
print(response.choices[0].message.content)

# %%

リスク

ChatGPT曰く以下気を付けろとのこと。

あなたが提案した解決策は一時的な対処としては有効かもしれませんが、以下に示すようにいくつかの重要な懸念事項があります。

  1. セキュリティリスク:自己署名証明書を信頼することは、セキュリティ上のリスクを引き起こす可能性があります。自己署名証明書は、信頼できる認証局から発行されたものではなく、証明書の所有者が誰であるかを検証するための信頼のしくみがありません。そのため、中間者攻撃(MITM攻撃)のリスクがあります。これは、攻撃者が通信の途中でデータを盗むか、改ざんする可能性があるということです。

  2. 永続性の問題:ブラウザで取得した証明書は、その時点での証明書情報を反映したものであり、証明書の更新や変更があった場合には追従できません。その結果、再度証明書エラーが発生する可能性があります。

  3. 環境依存:この方法は、プログラムが実行されるすべての環境に対して同様に証明書を設定する必要があります。これは、デプロイプロセスをより複雑にし、エラーを生じさせる可能性があります。

この問題を解決する最善の方法は、正規の証明書を使用することです。自己署名証明書がサーバにインストールされている理由を調査し、可能であれば信頼できる認証局から発行された証明書に置き換えることを検討してください。問題が解決しない場合は、APIプロバイダに連絡してサポートを求めると良いでしょう。

※会社でプロキシサーバ云々が絡むケース(Netskopeなど)

そもそも会社でプロキシサーバを導入していて、これが影響しているケースがあります。

元々、通信用のモジュールはそれぞれで証明書リストを抱えて使っていたそうですが、個々の証明書リストの更新が大変になるので、外部モジュールに分離して共有するようになったとかなんとか。それこそ上述のように、requestsモジュールの場合は、certifiモジュールの中のpemを見て検証するそうです。

ただし、会社でプロキシサーバが動いているとかなんとかある場合、certifiモジュール内のpemファイルでは足りないことがあり、上述のようなエラーが発生するとのこと。

したがってこの場合、OSのpemファイルを参照するように工夫しておいたほうがよさそうに思います。なので、上述のソースコード上で指定したように、そもそもOSの環境変数として指定のpemファイルを参照するように設定しておいた方がよさそうです。

  • 追加する環境変数
    • REQUESTS_CA_BUNDLE(=requestsモジュール用)
    • SSL_CERT_FILE(=opensslモジュール用)
    • CURL_CA_BUNDLE(=curlモジュール用)

参考

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?