boto3でIoT CoreのATSエンドポイントに接続する(SSL validation failedエラー対応)

botoでIoT Coreに接続すると「SSL validation failed」が発生する場合の対応方法です。 非推奨のエンドポイントを止めて、推奨されているATSエンドポイントに接続します。
2020.12.18

最近、boto3でIoT Coreにアクセスすると、SSL validation failedエラーになる事があります。応急処置は下記ブログで紹介されていますが、本記事では根本対応をご紹介します。

環境

ライブラリ バージョン
boto3 1.16.31
botocore 1.19.31
certifi 2020.12.05

SSL validation failedエラーが発生する

たとえば、boto3でIoT Coreにアクセスすると、SSL validation failedが発生します。 下記は、LambdaからIoT CoreのデバイスシャドウをGetする際のエラーメッセージです。

{
  "errorMessage": "SSL validation failed for https://data.iot.ap-northeast-1.amazonaws.com/things/led-device/shadow [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)",
  "errorType": "SSLError",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 18, in lambda_handler\n    res = iot_data.get_thing_shadow(thingName='led-device')\n",
    "  File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n    return self._make_api_call(operation_name, kwargs)\n",
    "  File \"/var/runtime/botocore/client.py\", line 663, in _make_api_call\n    operation_model, request_dict, request_context)\n",
    "  File \"/var/runtime/botocore/client.py\", line 682, in _make_request\n    return self._endpoint.make_request(operation_model, request_dict)\n",
    "  File \"/var/runtime/botocore/endpoint.py\", line 102, in make_request\n    return self._send_request(request_dict, operation_model)\n",
    "  File \"/var/runtime/botocore/endpoint.py\", line 137, in _send_request\n    success_response, exception):\n",
    "  File \"/var/runtime/botocore/endpoint.py\", line 256, in _needs_retry\n    caught_exception=caught_exception, request_dict=request_dict)\n",
    "  File \"/var/runtime/botocore/hooks.py\", line 356, in emit\n    return self._emitter.emit(aliased_event_name, **kwargs)\n",
    "  File \"/var/runtime/botocore/hooks.py\", line 228, in emit\n    return self._emit(event_name, kwargs)\n",
    "  File \"/var/runtime/botocore/hooks.py\", line 211, in _emit\n    response = handler(**kwargs)\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 183, in __call__\n    if self._checker(attempts, response, caught_exception):\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 251, in __call__\n    caught_exception)\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 277, in _should_retry\n    return self._checker(attempt_number, response, caught_exception)\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 317, in __call__\n    caught_exception)\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 223, in __call__\n    attempt_number, caught_exception)\n",
    "  File \"/var/runtime/botocore/retryhandler.py\", line 359, in _check_caught_exception\n    raise caught_exception\n",
    "  File \"/var/runtime/botocore/endpoint.py\", line 200, in _do_get_response\n    http_response = self._send(request)\n",
    "  File \"/var/runtime/botocore/endpoint.py\", line 269, in _send\n    return self.http_session.send(request)\n",
    "  File \"/var/runtime/botocore/httpsession.py\", line 281, in send\n    raise SSLError(endpoint_url=request.url, error=e)\n"
  ]
}

Lambdaコード例

import boto3

iot_data = boto3.client('iot-data')

def lambda_handler(event, context):
    res = iot_data.get_thing_shadow(thingName='led-device')
    print(res)

原因

botocoreが使っているcertifiにおいて、バージョンが2020.11.08から2020.12.05になるとき、Symantec社のルートCA証明書が削除されているためです。 Symantec社の認証局無効化については以前にいろいろ発表されており、certifiが今回その対応をしたことによるものですね。

対応方法:ATSエンドポイントにアクセスする

IoT CoreのATSエンドポイントを使って、アクセスするように修正します。

ATSエンドポイントを取得する

いずれかの方法で取得できます。

  • IoT Coreの設定画面でATSエンドポイントを取得する
  • AWS CLIコマンドでATSエンドポイントを取得する

IoT Coreの設定画面でATSエンドポイントを取得する

ブラウザでIoT Coreにアクセスし、「設定」にある「カスタムエンドポイント」を使います。

IoT CoreのATSエンドポイントを取得する

AWS CLIコマンドでATSエンドポイントを取得する

下記コマンドを実行します。

$ aws iot describe-endpoint --endpoint-type iot:Data-ATS
{
    "endpointAddress": "xxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
}

boto3でIoT CoreのATSエンドポイントに接続する

boto3.clientendpoint_urlでATSエンドポイントを明示すればOKです。https://の付与も忘れないでください。

import boto3

iot_data = boto3.client('iot-data', endpoint_url='https://xxxxxx-ats.iot.ap-northeast-1.amazonaws.com')

def lambda_handler(event, context):
    res = iot_data.get_thing_shadow(thingName='led-device')
    print(res)

さいごに

ATSエンドポイントが推奨されていることは知っていましたが、「boto3の接続時にATSエンドポイントを明示すること」は盲点でした。 困っている方の参考になれば幸いです。

参考