AWS IoT フリートプロビジョニング（Fleet Provisioning）


# プロビジョニング方式の選定

以下表を目安に登録方法を選定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/53be0232-5093-822d-5834-1cefa76db4e2.png)

- Amazonが管理するCAで証明書を発行する（独自CAを運用しない）
- 事前に秘密鍵、証明書を発行&登録しない

これらを要件とする場合、フリートプロビジョニング（Fleet Provisioning）を選択することになります。
また、今回は(*1)CSRを用いた場合には触れません。

# 用語

- プロビジョニングテンプレート

    デバイスがAWS IoTと通信するために必要なリソース（モノ/証明書/ポリシー等）情報と、
    それらリソースにアクセスするためのパラメータ（証明書ID等）を記述したJSONドキュメント
- クレーム証明書（ブートストラップ証明書）

    初回接続時に使用するデバイス共通の証明書
    個別証明書発行後は使用しない

# フリートプロビジョニングの種類
### 1. 認証ユーザー

本記事では触れません。
スマホ連携などができる場合に有効な手段
以下、大まかな手順。

1. AWS IoTにプロビジョニングテンプレートを登録
1. スマホ連携などで認証を行い、クレーム証明書/秘密鍵を発行
1. 発行したクレーム証明書/秘密鍵をデバイスに仮置き
1. デバイスからクレーム証明書/秘密鍵を使用してAWS IoTに個別証明書/秘密鍵/トークン発行をリクエスト
1. デバイスからクレーム証明書/秘密鍵/トークンを使用してAWS IoTにプロビジョニングをリクエスト
1. プロビジョニング後は、個別証明書/秘密鍵を使用して接続

### 2. クレーム証明書

スマホ連携できなくても認証情報を配布する手段
事前に埋め込んだ共通証明書を用いてプロビジョニングする
以下、大まかな手順。

1. AWS IoTにプロビジョニングテンプレートを登録
1. クレーム証明書/秘密鍵をIoT Coreで発行
1. 発行したクレーム証明書/秘密鍵をデバイスに格納
1. デバイスからクレーム証明書/秘密鍵を使用してAWS IoTに個別証明書/秘密鍵/トークン発行をリクエスト
1. デバイスからクレーム証明書/秘密鍵/トークンを使用してAWS IoTにプロビジョニングをリクエスト
1. プロビジョニング後は、個別証明書/秘密鍵を使用して接続
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/fae943ba-df79-b7c1-7a82-f64f159b02c4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/9332bbfd-ebfc-a049-3b07-f85451290266.png)

# 実際の手順
## 1. AWS側

AWS IoT Coreのフリートプロビジョニングテンプレートを選択

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/f9a343b0-c480-385b-46dd-f21431647f00.png)
1. テンプレート名、説明を任意で入力  
2. 「プロビジョニングロール」は、個別証明書/秘密鍵、モノの発行を行う権限を持ったロールになります。
ロールの作成から任意の名前で作成します。必要なポリシーが自動でアタッチされます。  
3. 「プロビジョニング前のフック」は、プロビジョニング時にデバイスから送信したパラメータ内容をもとにプロビジョニング許可/拒否の判定を行うLambda関数の設定になります。  

以下、「プロビジョニング前のフック」として設定するLambdaの例（公式から参照）
https://aws.amazon.com/jp/blogs/news/how-to-automate-onboarding-of-iot-devices-to-aws-iot-core-at-scale-with-fleet-provisioning/

```python:Python
import json

provision_response = {'allowProvisioning': False}

def isBlacklisted(serial_number):
    #check serial against database of blacklisted serials
    ...
    
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
```

4．「AWS IoT レジストリを使用してデバイスフリートを管理する」は、モノの名前にプレフィックスを付与したり、モノのタイプを決めたりする場合に使用します。
以降の画面で設定します。  
5．「デバイスのキーと値のペアを選択する」は、プロビジョニング時にデバイスに対して{キー：値}を送信する場合に使用します。
以降の画面で設定します。  
6．「次へ」をクリック

次にプロビジョニングで登録されるモノのポリシー（アクセス許可）を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/1e4f5559-e4ce-8876-cf19-617f45ae0500.png)

1. 「アクション」でiotの全権限をワイルドカードで指定しています。
2. 「リソースARN」はワイルドカードで全リソースを指定しています。  
    モノにアタッチするポリシーに関しては以下参照  
    https://docs.aws.amazon.com/iot/latest/developerguide/example-iot-policies.html
3. 「効果」は許可にチェックを入れます。
4. 「次へ」をクリック

次にプロビジョニングで登録されるモノの名前やタイプを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/8206ab4a-5fab-b9f7-9320-87e6adf6d8d7.png)

1. 「モノの名前プレフィックス」は、プロビジョニングで登録されるモノの名前に付くプレフィックスを設定します。
2. 「モノのタイプ」はモノを管理しやすくするために設定します。
3. 「グループ」もモノを監視しやすくするために設定します。
4. 「次へ」をクリック

次にプロビジョニングしたデバイスに対して送信する情報を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/8e170240-2589-a1af-a941-e06e4bf5e563.png)

1．上記設定でキーが'fleet-prov'、値が'test'のデータ（`{'fleet-prov': 'test'}`）が送信されることになります。
2．「テンプレート作成」をクリック

次にクレーム証明書の設定を行います。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/f56ddad9-d5c6-a6eb-df07-d5b303fb6c97.png)

1. 新規で作成する場合は、「証明書を生成します。」のリンクから証明書を発行し、ダウンロードします。ルートCAを取得していない場合は同時にAmazon Root CA 1の内容をファイル名「root.pem」としてダウンロードしておきます。
2. クレーム証明書となる証明書を選択し、「ポリシーをアタッチ」します。
3. アタッチが完了したら「テンプレートを有効化する」で完了になります。

## デバイス側

https://github.com/aws/aws-iot-device-sdk-python-v2/tree/master/samples
AWS IoT Device SDK v2 for Pythonで用意されているサンプル（fleetprovisioning.py）を使用します。
AWS IoT Device SDK v2 for Pythonは以下でインストールできます。

```
$ python3 -m pip install awsiotsdk
```

サンプルコードの実行方法は以下になります。

```
python3 fleetprovisioning.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file> --templateName <name> --templateParameters <parameters>
```

以下パラメータ説明

- endpoint ：エンドポイント（IoT Coreの設定から確認できます）
- root-ca ：ダウンロードしたルートCA（root.pem）のパス
- cert ：ダウンロードしたクレーム証明書（xxxxxxxxxx-certificate.pem.crt）のパス
- key ：ダウンロードした秘密鍵（xxxxxxxxxx-private.pem.key）のパス
- templateName ：作成したフリートプロビジョニングテンプレートの名前（fleet-prov-test-template）
- templateParameters ：テンプレートパラメータ（IoT Coreのフリートのプロビジョニングテンプレートから確認できます）

[endpoint]
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/6ff8ab3a-0e4b-c12b-d1e0-a463d8c44923.png)

[templateParameters]
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/ac60257d-d0b5-7231-9d39-a0c58f4ed508.png)

templateParametersには"SerialNumber"と"AWS::IoT::Certificate::Id"を指定する必要があることがわかります。
"SerialNumber"の値は任意ですが、"AWS::IoT::Certificate::Id"の値はクレーム証明書のIDになります。
証明書IDは以下の部分になります。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/9f8f0c89-d4ad-d1b7-ee5d-ebf8667fa499.png)
templateParametersで指定する値は以下のようになります。

```
--templateParameters "{\"SerialNumber\": \"001\" , \"AWS::IoT::Certificate::Id\": \"b1xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}"
```

実行結果は以下のようになります。

```
Connecting to None with client ID 'test-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'...
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='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', certificate_ownership_token='xxxxx', certificate_pem='-----BEGIN CERTIFICATE-----xxxxxx-----END RSA PRIVATE KEY-----\n')
Publishing to RegisterThing topic...
Waiting... RegisterThingResponse: null
Published RegisterThing request..
Received a new message awsiot.iotidentity.RegisterThingResponse(device_configuration={'fleet-prov': 'test'}, thing_name='fleet-test-001') 
Exiting Sample: success
Disconnecting...
Disconnected.
```

`Received a new message awsiot.iotidentity.CreateKeysAndCertificateResponse(...`で個別証明書/秘密鍵/トークンを受信しています。
`Received a new message awsiot.iotidentity.RegisterThingResponse(...`で登録完了応答を受信しています。
`device_configuration={'fleet-prov': 'test'}`：プロビジョニングしたデバイスに対して送信する情報として設定した値を受信しています。
`thing_name='fleet-test-001'`：モノの名前のプレフィックスとして設定した値にデバイスから送信した"SerialNumber"を付与した値を受信しています。

以下のようにIoT Coreにモノが追加されていれば完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/eb40d5bc-ac35-ae4a-b9c0-b03778e1f8b8.png)

# トラブルシューティング

「プロビジョニング前のフック」として設定するLambdaを設定するとプロビジョニングに失敗しました。
CloudWatchでログを確認したところ、Lambdaで参照している'device_serial'がKeyErrorとなっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/2c154562-0a15-30a0-2624-336594e093c1.png)

「プロビジョニング前のフック」として設定するLambdaの例で`["parameters"]["device_serial"]`を参照していますが、
プロビジョニングテンプレートのJSONでは、`"parameters":{"SerialNumber"...`と定義されており不一致が発生していました。
Lamdaの`["parameters"]["device_serial"]`を`["parameters"]["SerialNumber"]`に修正して解決。

# 参考情報

- AWS IoTにおけるデバイスへの認証情報のプロビジョニング  
    - 動画：https://www.youtube.com/watch?v=N8SaXPGMzjg  
    - スライド：https://pages.awscloud.com/rs/112-TZM-766/images/EV_iot-deepdive-aws2_Sep-2020.pdf

- AWS IoT のフリートプロビジョニングを試す
    - https://qiita.com/tatsuhiroiida/items/46ef5035f99b304d3d6f

- フリートプロビジョニングを使用したデバイス証明書がないデバイスのプロビジョニング 
     - https://aws.amazon.com/jp/blogs/news/how-to-automate-onboarding-of-iot-devices-to-aws-iot-core-at-scale-with-fleet-provisioning/