AWS IoT のフリートプロビジョニングを試す


Fleet Provisioning

AWS IoTにフリートプロビジョニングの機能が追加されました。
製造工場の作業工程やコストなどの制約で個別の証明書や鍵を出荷時に入れられない(入れたくない)ことはしばしば有り、それに対する施策として、以前から初回接続時にデバイス証明書を配布するJITPやJITRなどの方法がありました。今回その仕組がサービスに取り込まれ、プロビジョニングのテンプレート作成やフックするLambdaなどをまとめて設定することができるようになっています。
JITRについては、こちらの記事が詳しいです。
https://qiita.com/TakashiKOYANAGAWA/items/b3b679e2a7d56f144a8e

今回触るサンプルでは、本番用の証明書に加えて秘密鍵もサーバー側で生成し、デバイス側はそれを保存し、それ以降の通信で利用しています。別の方法として、秘密鍵が通信路を流れるリスクを避けるため、鍵ペアをデバイスや別の場所で作成し、CSRを用いて証明書を発行することが可能です。
以前書いたこちらの記事では、CSRをデバイスで作成し、Lambda経由で証明書を発行していましたが、こういった作業が省略できます。

ここでは、こちらの記事を参考にフリートプロビジョニングを試していきます。
https://aws.amazon.com/jp/blogs/news/how-to-automate-onboarding-of-iot-devices-to-aws-iot-core-at-scale-with-fleet-provisioning/

AWS IoTの設定

オンボードという項目が追加されています。
ここからテンプレートを作成します。

プロビジョニングロールは、AWS IoTがプロビジョニングに関する操作を許可することが記述されています。

プロビジョニング前のフック:JITRの仕組みのときのようにLambdaをフックできます。これは、デバイスを許容するかどうかを判定するロジックをアプリケーション側で追加したい場合に使えそうです。また、判定がOKの場合に後処理(デバイスの初期状態をDynamoDBやShadowに設定するなど)を行うことも必要に応じてできそうです。

今回、Lambdaは、こちらの資料にならって 作成します。
後ほどクライアント側から送るパラメータのKeyと合うようにするために、一部修正し、以下のようにします。

provisioning-test-hook
import json

provision_response = {'allowProvisioning': False}

def isBlacklisted(serial_number):
    return False

def lambda_handler(event, context):

    # DISPLAY ALL ATTRIBUTES SENT FROM DEVICE
    print("Received event: " + json.dumps(event, indent=2))

    # Assume Device has sent a device_serial attribute
    device_serial = event["parameters"]["SerialNumber"]

    # Check serial against an isBlacklisted() function
    if not isBlacklisted(device_serial):
        provision_response["allowProvisioning"] = True

    return provision_response

isBlacklisted では、アプリケーション要件に合わせて、例えばDynamoDBでシリアルナンバーのブラックリストを管理し、その項目と一致する場合にTrueを返すような実装を入れる、などが考えられます。が、一旦ここでは割愛します。

レジストリに登録されたときのモノの名前やType,Groupなどの設定を行います。Groupには動的なグループは指定できません。

今度は逆に、デバイス側に送りたい情報をセットします。

プロビジョニング用の証明書を作ります。今回はAWS のCAに署名された証明書を使います。独自CAも利用可能です。証明書を作成したら、生成される秘密鍵、クライアント証明書、Root CA証明書をダウンロードしておきます。

前の画面に戻り、証明書を選択し、ポリシーをアタッチのボタンを押すと、必要な権限が付与されたポリシーがアタッチされます。
アタッチされたポリシーを確認したところ、以下のようになっていました。Permissionはプロビジョニングに必要なTopicの操作などに限定されています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/certificates/create/*",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/provisioning-templates/test-template/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Subscribe"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/certificates/create/*",
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/provisioning-templates/test-template/provision/*"
      ]
    }
  ]
}

テンプレートの作成が完了したら、ブートストラップ証明書を有効化しておきます。

クライアント側の作業

こちらのサンプルをベースに作成していきます。今回は手元のMacで試しています。
https://github.com/aws-samples/aws-iot-fleet-provisioning/tree/b5080ddadf27a264b3397ebeddd5f393146e98ff

git clone https://github.com/aws-samples/aws-iot-fleet-provisioning.git
cd aws-iot-fleet-provisioning/
pip3 -V
# 3.7であること
pip3 install -r requirements.txt
nano config.ini
# config.iniを修正する。エンドポイントのリージョンも変更が必要な場合があります。
python3 main.py

問題なく動作すると以下のような出力となりました。


##### CONNECTING WITH PROVISIONING CLAIM CERT #####
##### SUCCESS. SAVING KEYS TO DEVICE! #####
##### CREATING THING ACTIVATING CERT #####
##### CERT ACTIVATED AND THING FleetProvTest-1588837679773 CREATED #####
##### CONNECTING WITH OFFICIAL CERT #####
##### ACTIVATED AND TESTED CREDENTIALS (591bbcc6dd-private.pem.key, 591bbcc6dd-certificate.pem.crt). #####
##### FILES SAVED TO /Users/xxx/fleet-provisioning/aws-iot-fleet-provisioning/certs #####
{"service_response": "##### RESPONSE FROM PREVIOUSLY FORBIDDEN TOPIC #####"}

プロビジョニングテンプレート作成において、デバイス側に送りたい情報として、prov-attrの値をセットしました。こちらを取得するために、少しコードを修正します。
provisioning_handler.pyon_message_callback の中で、json_dataを出力するようにします。

provisioning_handler.py
                json_data = json.loads(message.payload)
                print(json_data)  # 追加

これで再度実行すると、IoT Coreから返される値を確認することが出来ます。

{'deviceConfiguration': {'prov-attr': 'abc'}, 'thingName': 'FleetProvTest-1588843382620'}

ここまでで、本番用の証明書および秘密鍵がデバイス側に保存され、疎通が可能になりました。
プロダクションの際には、ダウンロードした秘密鍵をデバイスのセキュアな領域に保存するなどの検討が必要です。