Lambdaでboto3を使ってDevice Shadowを取得しようとしたらSSLでエラーが起きたから応急処置をする

2020.12.17

こんにちはCX事業本部の夏目です。

boto3でIoT CoreのDevice ShadowにアクセスしようとしたりするとSSLでエラーが発生する事象が起きています。

とりあえずその応急処置の方法を共有します。

発生しているエラー

{
  "errorMessage": "SSL validation failed for https://data.iot.ap-northeast-1.amazonaws.com/things/test_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 8, in lambda_handler\n    resp = iot.get_thing_shadow(thingName='lip_t9999')\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"
  ]
}

原因

どうも最新のcertifi==2020.12.5だとダメらしい。

応急処置

certifi==2020.11.8だと動くことを確認したので、Lambda Layerに追加して動くようにします。
なお、今回は手動で更新を行うようにします。

layer.zipの準備

$ mkdir -p layer/python
$ cd layler/python
$ pip install certifi==2020.11.8 -t .
$ cd ..
$ zip -r ../layer.zip *
$ cd ..
$ ls -l layer.zip
.rw-r--r-- 158k xxxx 17 Dec 18:46 layer.zip

Layerの作成

左のカラムのレイヤーをクリック。

レイヤーの作成をクリック。

Layerの名前や対象となるランタイムを選択します。 ここでは Python3.6, Python3.7, Python3.8を選択しています。

入力と選択をしたら、作成を押して作成します。

LambdaにLayerを追加する

Layerを追加したいLambda関数の画面に行きます。

デザイナーをクリックし、開きます。

Lambdaのアイコンと関数名の下のLayersをクリックして、レイヤー情報を開きます。
そして、レイヤーの追加をクリックします。

レイヤーを追加の画面に行くので、カスタムレイヤーを選んで先程作成した certifi_layerのバージョン 1を選択します。
選択したら、右下の追加をクリックして追加します。

Layerの追加が行われ、Layersの右に (1)と表示されます。
これで応急処置は完了です。

Lambdaを動かしてみる

import json
import boto3

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

def lambda_handler(event, context):
    # TODO implement
    resp = iot.get_thing_shadow(thingName='lip_t9999')
    return resp['payload'].read().decode()

Layer追加前

Layer追加後

まとめ

応急処置としてLayerを追加することでGetThingShadowできるようになります。

急にSSLエラーが出て困ってる人の役に立てたら幸いです。