IoT Hub Device Provisioning Service とは？を作りながら理解する

前回は、IoT Hub Device Provisioning Service の超概要ということで、DPSが何をしたい時に使うサービスなのかを書きました。
https://qiita.com/mstakaha1113/items/231c859d7427b466d4ad

今回は、マイクロソフトのドキュメント **"クイックスタート"** を題材に、実際にDPSを作りながら理解していきたいと思います。

# 今回見るドキュメント
１．Azure上でIoT HubとDPSを接続する
[クイック スタート:Azure Portal で IoT Hub Device Provisioning Service を設定する](https://docs.microsoft.com/ja-jp/azure/iot-dps/quick-setup-auto-provision)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/9b8e3ea2-8af1-6096-0ad2-05b0b3c9b9d9.png)

２．X.509証明書を使い、デバイス→DPS→IoT Hubという接続を確認する
- [クイック スタート:IoT Hub Device Provisioning Service 対応の C# デバイス SDK を使用して、シミュレートされた X.509 デバイスを作成してプロビジョニングする](https://docs.microsoft.com/ja-jp/azure/iot-dps/quick-create-simulated-device-x509-csharp)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/2c8a17f7-cca0-a620-f96a-5882440375bb.png)
ドキュメントはC#を参照していますが、ほかの言語もサンプルがありますので、必要に応じてご確認ください。

# Azure上でIoT HubとDPSを接続する

## IoT Hubの作成
最初に接続先となるIoT Hubを作成します。
これはドキュメントそのままですので、詳細は記載しません。ドキュメント記載のまま進めてください。
"価格とスケール"につきましてはFreeでもBasicでもStandardでもかまいません。
またSecurity Centerやタグは無くても問題ありません。

## Device Provisioning Service(DPS)の作成
デバイスから最初の通信を受け取るDPSを作成します。
こちらもドキュメントそのままですので、詳細は記載しません。

## IoT HubとDPSをリンクする
IoT HubとDPSの関連付けを行います。
こちらもドキュメントそのままです。
ドキュメントがよくできていますし、そもそもやることが少ないです。
DPS側から、自分に関連させるIoT Hubを選択するという流れになります。
IoT Hubを"Basic"で作成した方は、下記のような注意事項が出ていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/b125728a-4574-06ce-a466-c17795f7d658.png)
これはIoT Hubのスケールに関する注意事項となります。
詳しくはこちら[ソリューションに適した IoT Hub のレベルを選択する](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-scaling#iot-hub-rest-apis)をご確認ください。

今回は実施しませんが、ここで複数のIoT Hubを接続することができます。
この機能（＋割り当てポリシー）を利用して

- 負荷を分散する
- DR対策を施す
- デバイス仕向け地(日本、欧州、アメリカ等)のIoT Hubを自動的に選択させる

というようなことに対応できるようになります。
もちろん、実際にはDPS単体ではなくIoT Hubから続く後続の処理すべてがこの状況を想定した作りになっている必要があります。

# X.509証明書を使い、デバイス→DPS→IoT Hubという接続を確認する
## X.509証明書の準備
ドキュメントには
>「[Provisioning Device Client Sample - X.509 Attestation](https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/device/X509Sample)」(デバイス クライアントのプロビジョニングの例 - X.509 構成証明) のサンプル コードを使用して、シミュレートされたデバイスの個々の登録エントリで使用する証明書を作成します。

との記載があります。
が、地味にはまりどころでもありますので、今回はAzure Portal内の"Cloud Shell"を使って作っていきます。元ネタは[こちら](https://microsoftlearning.github.io/AZ-220-Microsoft-Azure-IoT-Developer/Instructions/Labs/LAB_AK_06-automatic-enrollment-of-devices-in-dps.html#exercise-2-generate-and-configure-x509-ca-certificates-using-openssl)です。

ドキュメントにも記載がありますが、ここで作成する証明書もお勉強用の証明書です。本番利用はお控えください。（本番じゃなくても、実デバイスを扱う時になったら、本物の証明書を使いましょう）

### Cloud Shell(クラウドシェル)の起動
Cloud Shellは、Azure内に準備されるAzure用のコマンド等々を扱えるCLI環境です。ざっくり言いますと、Azure内にコンテナで環境たててくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/ffc95e7c-889a-da1f-477d-13b4c9c5639f.png)
皆さんのAzure Portalにも、上の円で囲った場所にあるようなマークがありませんか？
こちらをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/2d6cbab3-3f97-3dfa-2e00-3932331a4082.png)
ストレージをマウントするのでコストがかかります旨問われますが"ストレージの作成"をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/014b05b2-1779-4cfe-51f1-2db3118bdd67.png)
しばらく待つと、このような感じでBash画面が立ち上がります。
ちなみに裏でリソースグループとストレージを作成しています。
今回私の環境では
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/25ac2c5e-bf25-0346-8ffc-42ad52e36dad.png)
リソースグループ：cloud-shell-storage-southeastasia
ストレージ：cs11003000095dd496c
として作成されていました。
これを保持しているうちは、Cloud Shellを閉じても前回の操作が残っており、削除すると１から始めることができます。

### 証明書の作成
環境ができましたので、使っていきたいと思います。
何も考えず、以下のコマンドをコピペしたらいけます。

``` shell
mkdir createcert
cd createcert
curl https://raw.githubusercontent.com/Azure/azure-iot-sdk-c/master/tools/CACertificates/certGen.sh --output certGen.sh
curl https://raw.githubusercontent.com/Azure/azure-iot-sdk-c/master/tools/CACertificates/openssl_device_intermediate_ca.cnf --output openssl_device_intermediate_ca.cnf
curl https://raw.githubusercontent.com/Azure/azure-iot-sdk-c/master/tools/CACertificates/openssl_root_ca.cnf --output openssl_root_ca.cnf
chmod 700 certGen.sh
```
ディレクトリの作成と必要な情報のダウンロードを実施します。
続いて証明書を作成します。

``` shell
./certGen.sh create_root_and_intermediate
./certGen.sh create_device_certificate device001
```
１行目でルート証明書と中間証明書を作成します。
２行目でデバイス個別の証明書を作成します。

``` shell
download ~/createcert/certs/new-device.cert.pem
download ~/createcert/certs/new-device.cert.pfx
```
最後にできあがった証明書類をローカルPCにダウンロードします。
(DPSにアップロードするためにローカルにダウンロードします)
ざっくり書くと、pemが錠でpfxが鍵です。
このあとpemをDPSに登録して、デバイス側のアプリでpfxを利用します。

今回は実施しませんが、グループ登録を試す場合は、先に作成したルート証明書が使えます。

## 証明書の登録
証明書は出来上がりましたので、DPSに登録します。
ドキュメントに沿って、今回は個別登録を実施します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/7c1b693b-8240-b420-88de-2e80feae4429.png)
Azure PortalのDPSを選択し、"登録を管理します"をクリックします。
続いて"個別登録の追加"をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/da5390b4-5b1f-8f55-1b8d-f7ea80c841da.png)
"登録の追加"の画面に入ったら、まずは"メカニズム(プロビジョニングのやり方)"に"X.509"が選択されていることを確認します。
その後、"プライマリ証明書の.pemファイルまたは.cerファイル"欄に、先ほどダウンロードしたファイルを登録します。
アップロードが完了したら、"保存"ボタンをクリックし登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/7244cbe4-4ab4-d72b-aceb-c51d860436a9.png)
登録が終わりましたら、"個々の登録"タブをクリックしてください。
登録IDが表示されていると思います。IDの名前そのものは、証明書を作成する際に指定していますので、証明書をアップロードする際には指定する必要はありません。

# 実際に接続してみる
# IoT Hubの現在の状態を確認しておく
これからローカルで１つのアプリケーションを動かします。動くと、IoT Hubにデバイス"device001"が登録されます。
まずは現在IoT Hubに"device001"が存在しないことを確認しておきます。
Azure Portalで、はじめに作成したIoT Hubを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/c16f9c00-5bdb-b3a0-6859-9d7d0beac08f.png)
"IoTデバイス"をクリックします。すると"デバイスが見つかりませんでした"というメッセージが見えます。
これで、"現時点ではIoT Hubにデバイス登録が存在しない。" ということがわかります。

# サンプルプログラムをダウンロードする
ドキュメント冒頭に
>これらの手順では、[C# 用 の Azure IoT サンプル](https://github.com/Azure-Samples/azure-iot-samples-csharp)を使用して、Windows OS を実行中の開発用コンピューター上で X.509 デバイスをシミュレートします。 サンプルでは、Device Provisioning Service を使用して、シミュレートされたデバイスを IoT ハブに接続する操作も行います。

と記載がありますので、リンク先からソースコードをダウンロードします。
私はローカルで以下のコマンドを打ちました。

```
git clone https://github.com/Azure-Samples/azure-iot-samples-csharp.git
```
Gitが使えない場合は、Zipダウンロードとかしてください。
ローカルに展開したら、X509のサンプルが入っているディレクトリまで下ります。

```
cd ./azure-iot-samples-csharp/provisioning/Samples/device/X509Sample
```
↑ご自身の環境にあわせて適宜読み替えてください。

# DPS用のIDを取得する
サンプルプログラムからDPSに接続するにあたり、必要なものがあります。
今回接続するDPSに割り振られたIDです。
Azure Portalから取得しましょう。
Azure PortalでDPSを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/3c546dfd-1001-2aa9-7b32-7675049e9390.png)
概要タブの中、"IDスコープ"という場所に記載されているIDをコピーします。
(図の中の緑色の線の部分に記載されています)

# プログラムを実行する
コマンドプロンプトに戻り

```
dotnet run <ID Scope>
```
と打ちます。
"< ID Scope >"の部分は先ほどコピーした"IDスコープ"に置き換えてください。
実行すると

```
Enter the PFX password for certificate.pfx:
```
と聞かれると思います。
何も考えずに**"1234"**と入力しましょう。
Cloud Shell内で起動したcertGen.shを見るとわかるのですが、パスワードは"1234"とハードコードされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/e216888d-57bc-c7d3-3157-6a6d85a7c8b9.png)
その後、このような形でエラーになると思います。
これは今回のサンプルの仕様の問題なので、サンプルにあわせてあげます。
以下の２点を実施してください。

- pfxファイルの名前を"certificate.pfx"に変更する
- pfxファイルをプログラムが配置された場所に配置する

例えばこんな感じです。

```
> ren new-device.cert.pfx certificate.pfx
> move ./certificate.pfx ./azure-iot-samples-csharp\provisioning\Samples\device\X509Sample
```
再実行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/72fd0005-e864-2d06-3825-d6a12d85063b.png)
どんな動きをしているかが気になる方は、ソースコードを見てください。

## IoT Hubに登録されたか確認する
DPSができることはIoT Hubにデバイスを登録して、接続情報をデバイスに返却することです。
ここでは、IoT Hubに登録されていることを確認します。

Azure Portalで、はじめに作成したIoT Hubを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/1569ca2c-efed-a4c9-4e62-97ca234f91b3.png)
"IoTデバイス"をクリックしてデバイスの登録情報を確認します。
"device001"が登録されていることが確認できると思います。
今回は省略しますが、Device ExplorerやVSCodeのExtention等を使って確認すると、メッセージも送られていることがわかります。

# まとめ
今回はDPSの動作を見るため、チュートリアルに沿ってX.509証明書を用いたデバイスの個別登録を実施してみました。

- DPSとIoT Hubを作成して関連付ける
- 証明書を作ってDPSに登録する
- 証明書を使ってDPSに接続し、IoT Hubへの自動登録
- 登録されたIoT Hubから返却された接続情報を用いてIoT Hubとのやりとり

ができました。
もう少し中身が気になるかたは、サンプルソースコード"X509Sample"フォルダの中の"ProvisioningDeviceClientSample.cs"から読み解いていただけたらと思います。