[AWS IoT]証明書の作成・登録方法 Part2: Fleet Provisioning

# はじめに
AWS IoTにはいくつかの証明書の登録方法があります。[前回の記事](https://qiita.com/tsugunao/items/4f6bfba9be5a7c97c4d5)ではマネジメントコンソールからの証明書の作成について試してみましたが、今回はFleet Provisoningを試してみたいと思います。

# 証明書登録方法の分類
[本家のAWSの資料](https://pages.awscloud.com/rs/112-TZM-766/images/EV_iot-deepdive-aws2_Sep-2020.pdf)に証明書の登録方法が分類されています。以下は資料の抜粋です。
![スクリーンショット 2021-07-22 12.15.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/2719094b-c771-e9ba-f850-ab9857e6d945.png)
今回試してみるFleet Provisoningは、Amazonが管理するCAを使用して、大量のデバイスが初回接続時に証明書を発行しモノを作成、Policyを紐付ける方法となります。事前に工場等で証明書をデバイスに入れておかなくてもよいというメリットがあります。一方で自分でCAを管理したい場合にはこちらの方法は使用できないので、JITRやJITPの方法を使用する必要があります。

# Fleet Provisoningについて
Fleet Provisioningには大きく分けて、1）クレームによるプロビジョニングと2）信頼できるユーザーによるプロビジョニングがあります。どちらも似たプロセスのプロビジョニングになるのですが、1）は事前に証明書の発行等だけを行える権限を持った証明書をデバイスに焼き込んでおいて、初回接続時に本番用の証明書をリクエストして保存するというものです。2）は信頼できるユーザーを作成し、信頼できるユーザーが初回の接続権限をもった証明書をデバイスに提供し、デバイスはその一時的な証明書をもとに本番用の証明書をリクエストするというものです。今回は1）のクレームによるプロビジョニングについて試してみます。基本的な流れは2）の信頼できるユーザーによるプロビジョニングでも同様となります。1）のクレームによるプロビジョニングの流れは以下のようになります。
![スクリーンショット 2021-07-22 12.20.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/a44587d9-3c5b-9351-5f1e-e00e86f58a52.png)

# Fleet Provisioing 試してみた

## IoT Core側の設定

Fleet Provisioningを行うためには、まずはプロビジョニングテンプレートというテンプレートを作成する必要があります。プロビジョニングテンプレートの作成の前に、クレーム証明書を作成します。クレーム証明書の作成の手順は通常の証明書の作成の手順と同じなので、例えば[こちら](https://qiita.com/tsugunao/items/4f6bfba9be5a7c97c4d5)のような手順で証明書を作成し、ダウンロードした証明書とKeyを保存しておきます。

次にプロビジョニングテンプレートを作成します。
AWS IoT Coreのオンボード＞フリートのプロビジョニングテンプレートから作成をクリックします。
![スクリーンショット 2021-07-22 12.36.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/bdc81f99-d469-d175-4a28-770b1a702736.png)

テンプレート名を入力し、ロールを作成し、適宜オプションを選択し、次へをクリックします。ちなみにここでデバイスのキーと値のペアを選択するのオプションにチェックを入れると、プロビジョニング時にデバイスに設定情報などパラメータを渡せるようになります。
![スクリーンショット 2021-07-22 12.38.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/b2494361-8d03-67fc-e358-fbb46336edf0.png)

次に、プロビジョニングされるデバイスに付与するPolicyを設定します。ここで作成しても良いですし、すでに作成したPolicyを選択しても良いです。
![スクリーンショット 2021-07-22 12.43.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/07d93030-0938-6309-4e20-3b47c643f87e.png)

次にモノの名前のPrefixやモノのタイプ、モノの属性などを定義し、次へをクリック。
![スクリーンショット 2021-07-22 12.47.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/194221d7-ca6d-0e5f-98cb-6b53b10d0e86.png)

「デバイスのキーと値のペアを選択する」のオプションを選択した場合、デバイスに渡すカスタム属性を入力できるので、設定情報を入力し、テンプレートを作成をクリック。
![スクリーンショット 2021-07-22 12.49.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/516a36b7-3763-be34-2d58-a92d1e2761e7.png)

テンプレートが作成され、クレーム時に使う証明書を選択する画面になるので、はじめに作成しておいた証明書を選択し、Policyをアタッチし、テンプレートを有効化するのボタンをクリックするとテンプレートが作成されます。
![スクリーンショット 2021-07-22 15.09.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/39b791a9-c004-9dc4-ead1-5093b17a2e84.png)

作成されたプロビジョニングテンプレートは以下のようになります。

```json
{
  "Parameters": {
    "SerialNumber": {
      "Type": "String"
    },
    "AWS::IoT::Certificate::Id": {
      "Type": "String"
    }
  },
  "Resources": {
    "certificate": {
      "Properties": {
        "CertificateId": {
          "Ref": "AWS::IoT::Certificate::Id"
        },
        "Status": "Active"
      },
      "Type": "AWS::IoT::Certificate"
    },
    "policy": {
      "Properties": {
        "PolicyName": "My_IoT_Policy"
      },
      "Type": "AWS::IoT::Policy"
    },
    "thing": {
      "OverrideSettings": {
        "AttributePayload": "MERGE",
        "ThingGroups": "DO_NOTHING",
        "ThingTypeName": "REPLACE"
      },
      "Properties": {
        "AttributePayload": {
          "Make": "Sony"
        },
        "ThingGroups": [],
        "ThingName": {
          "Fn::Join": [
            "",
            [
              "MyThing_",
              {
                "Ref": "SerialNumber"
              }
            ]
          ]
        },
        "ThingTypeName": "Bulb"
      },
      "Type": "AWS::IoT::Thing"
    }
  },
  "DeviceConfiguration": {
    "MyDeviceOption": "Normal"
  }
}
```

## クライアント側の作業
クライアントからは、[こちら](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/fleet-provision-api.html#create-keys-cert)にも説明があるように、AWSによって予約されたMQTT Topicを用いて証明書発行のリクエストを行い、特定のTopicをSubscribeすることで、証明書と秘密鍵を受け取り、それをRegisterThingsのAPIを呼ぶことでAWS側に登録します。これらのAPIはCreateKeysAndCertificateリクエスト/レスポンス、CreateCertificateFromCsrリクエスト/レスポンス、RegisterThingというAPIで定義されており、AWS IoT SDKから使用することができます。（SDKを使用しない場合も、$aws/certificates/create/payload-format などの特定の予約Topicと通信すれば証明書は発行できるはず）

今回は、[こちら](https://github.com/aws/aws-iot-device-sdk-python-v2#aws-iot-device-sdk-v2-for-python)にあるPython用のSDKをつかってやってみます。

```
git clone  https://github.com/aws/aws-iot-device-sdk-python-v2.git
```
としてSDKをCloneします。今回はこのSDKの中でサンプルとして提供されているプログラム```samples/fleetprovisioning.py```を利用します。以下はfleetprovisioing.pyの抜粋ですが、MQTT connectionを張り、CreateKeysAndCertificateリクエストをなげて、レスポンスを受け取り、RegisterThingを行うという流れになっています。

```
            print("Publishing to CreateKeysAndCertificate...")
            publish_future = identity_client.publish_create_keys_and_certificate(
                request=iotidentity.CreateKeysAndCertificateRequest(), qos=mqtt.QoS.AT_LEAST_ONCE)
            publish_future.add_done_callback(on_publish_create_keys_and_certificate)

            waitForCreateKeysAndCertificateResponse()

            if createKeysAndCertificateResponse is None:
                raise Exception('CreateKeysAndCertificate API did not succeed')

            registerThingRequest = iotidentity.RegisterThingRequest(
                template_name=args.templateName,
                certificate_ownership_token=createKeysAndCertificateResponse.certificate_ownership_token,
                parameters=json.loads(args.templateParameters))
```
以下のようにfleetprovisoning.pyを実行します。クレーム用の証明書や秘密鍵を引数で指定し、作成したプロビジョニングテンプレートの名前を指定します。また、テンプレート上でParamaterとなっている変数についてtemplateParametersとして指定します。

```
$ python3 fleetprovisioning.py --endpoint xxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com --cert certs/xxxxxxx-certificate.pem.crt --root-ca certs/Amazon-root-CA-1.pem --key certs/xxxxxxx-private.pem.key --templateName TestTemplate --templateParameters '{"SerialNumber": "0002"}'
```
すると以下のように証明書が作成されたものが返され、証明書とモノが作成されます。コンソール上に証明書や秘密鍵の情報が表示されるので、必要に応じてファイルに保存します。

```
Connecting to xxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com with client ID 'test-e0a08869-dfd8-4603-8fc7-6eb51fae8efd'...
Connected!
Subscribing to CreateKeysAndCertificate Accepted topic...
Subscribing to CreateKeysAndCertificate Rejected topic...
Subscribing to RegisterThing Accepted topic...
Subscribing to RegisterThing Rejected topic...
Publishing to CreateKeysAndCertificate...
Waiting... CreateKeysAndCertificateResponse: null
Published CreateKeysAndCertificate request..
Received a new message awsiot.iotidentity.CreateKeysAndCertificateResponse(certificate_id='154177a894db890c===中略=====3a3b5', certificate_ownership_token='eyJ2ZXJzaW9uIjoi====中略=========3RpTTMxQjh2SlpLN1k5bz0ifQ==', certificate_pem='-----BEGIN CERTIFICATE-----\nMIIDWTCCAkGgAwIBAgIUIFy/6GEMMvAZ8xaDISGKPmuX9xs=====中略=======+cOP6YOkPcxMg0v9klIK\n-----END CERTIFICATE-----\n', private_key='-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA62R========中略========nPQGUuO5J05wAc2nbMS\n-----END RSA PRIVATE KEY-----\n')
Publishing to RegisterThing topic...
Waiting... RegisterThingResponse: null
Published RegisterThing request..
Received a new message awsiot.iotidentity.RegisterThingResponse(device_configuration={'MyDeviceOption': 'Normal'}, thing_name='MyThing_0002') 
Exiting Sample: success
Disconnecting...
Disconnected.
```

マネジメントコンソール上で確認すると、モノが作成され、属性もテンプレートで指定したように設定され、証明書も作成してアタッチされていることが確認できます。このように初回接続時にプロビジョニングを行うことができます。
![スクリーンショット 2021-07-22 15.56.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/3279ba30-9626-c313-d1b6-0c440ec96afc.png)


# まとめ
証明書の作成登録手順の1つであるFleet Provisoningを行いました。デバイスからの初回アクセス時に証明書を作成し、それをAWSに登録できることを確認することができました。