[aws-iot-device-sdk-js-v2] 試して理解する Fleet Provisioning

AWS IoTのFleet Provisioning(フリートプロビジョニング)をaws-iot-device-sdk-js-v2で試してみました
2021.04.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部のうらわです。

AWS IoTにおける認証情報のプロビジョニング方法の1つであるFleet Provisioningについて、aws-iot-device-sdk-js-v2を用いて手を動かして流れを確認してみました。

環境

Macで作業します。パッケージマネージャーはyarnを使用します。途中でopensslも使用します。

aws-iot-device-sdk-js-v2は本記事の執筆時点の最新バージョンであるv1.4.3を利用します。

AWSのリージョンは東京リージョンです(ap-northeast-1)。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H15

$ node -v
v14.15.4

$ yarn -v
1.22.10

$ openssl version
LibreSSL 2.8.3

Fleet Provisioningについて

AWS IoTの認証情報のプロビジョニング方法については以下の資料にわかりやすくまとまっています。

https://hatenablog-parts.com/embed?url=https://pages.awscloud.com/rs/112-TZM-766/images/EV_iot-deepdive-aws2_Sep-2020.pdf

一覧は以下となります(上記の資料より引用)。

Fleet Provisioningは、デバイスを初回接続時にAWS IoTに登録させ、AWS IoTに認証情報を発行させたい場合に有効な方法です。

流れは以下となります(上記の資料より引用)。今回は「0. デバイス側で事前に秘密鍵を持っている場合にはCSRを投げることも可能」と「1. クレーム証明書・秘密鍵を使って証明書・秘密鍵の発行をリクエスト」の両方を試してみます。

sdkをクローンする

まずはsdkを任意のディレクトリにクローンします。Fleet Provisioningのサンプルコードがあるディレクトリに移動しておきます。

git clone https://github.com/aws/aws-iot-device-sdk-js-v2.git
cd samples/node/fleet_provisioning

続いて、package.jsonaws-iot-device-sdk-js-v2のバージョンを../../../から^1.4.3に変更し、package-lock.jsonは削除してyarn installします。ts-nodeも追加でインストールしておきます。

# 以下のディレクトリで作業します
cd samples/node/fleet_provisioning
rm -rf package-lock.json
yarn install
yarn add -D ts-node

また、Fleet Provisioningで入手する証明書を使用してAWS IoTとの疎通確認を行うためにsamples/node/pub_subのサンプルコードも利用します。上記と同様の手順でパッケージをインストールしておきます。

クレーム証明書・秘密鍵を作成する

Fleet Provisioningを実行するために、まずは事前にクレーム証明書と秘密鍵を作成します。これらはデバイスに埋め込まれるもので、全デバイスで共通です。

AWS IoTのメニューにて 安全性 > 証明書 > 証明書を作成する > 1-Click証明書作成(推奨) で証明書を作成し、「このモノの証明書」「公開鍵」「秘密鍵」をダウンロードしておきます。また、AWS IoTのルートCAも入手しておきます。

ダウンロードしたら「有効化」し、ここではポリシーはアタッチせず完了します。

テンプレートを作成する

AWS IoTのメニューから オンボード > フリートのプロビジョニングテンプレート を選択し、新規テンプレートを作成します。

任意のテンプレート名を設定、プロビジョニングロールの作成(名前は任意)、オプション設定の「AWS IoTレジストリを使用してデバイスフリーとを管理する」にチェックを入れます。

ポリシーを作成します。このポリシーは、Fleet Provisioningが成功すると作成された証明書に自動でアタッチされます。今回は検証用のため全てのリソースに対して全てのアクションを許可しておきます。

モノが登録される時の設定をします。今回はモノの名前プレフィックスだけ指定しておきます。Fleet Provisiningによって登録されるモノにはこのプレフィックスが自動でつきます。 それ以外はデフォルトのままとし、「テンプレートを作成」をクリックします。

最後に、先ほど作成したクレーム証明書にFleet Provisioningによって証明書作成・デバイス登録を可能にするポリシーをアタッチします。

以上でテンプレートの準備は完了です。

試す

冒頭のフローの中に記載のあった「0. デバイス側で事前に秘密鍵を持っている場合にはCSRを投げることも可能」と「1. クレーム証明書・秘密鍵を使って証明書・秘密鍵の発行をリクエスト」の2つのFleet Provisioningを、sdkのサンプルコードを用いて試します。

0はデバイスプロビジョニングMQTT APIのCreateCertificateFromCsr、1はCreateKeysAndCertificateが該当します。

また、sdkのサンプルコードではCreateKeysAndCertificateまたはCreateCertificateFromCsrの実行後、続けてRegisterThingを実行してモノの登録も実施してくれます。

各APIの詳細は以下のドキュメントを参照してください。

CreateKeysAndCertificate

このAPIではリクエストペイロードは空です。レスポンスペイロードには新しい証明書と秘密鍵が含まれます。

レスポンスの秘密鍵と証明書がファイルで欲しいため、sdkのサンプルコードにprivateKeycertificatePemをファイルに書き出すコードを追加しておきます。

https://github.com/aws/aws-iot-device-sdk-js-v2/blob/v1.4.3/samples/node/fleet_provisioning/index.ts#L110

index.ts

fs.writeFileSync(
  `${response.certificateId}-certificate.pem.crt`,
  response.certificatePem
);
fs.writeFileSync(
  `${response.certificateId}-private.pem.key`,
  response.privateKey
);

コードを修正したら必要なコマンドオプションを指定してFleet Provisioningを実行します。ダウンロードしておいた証明書ファイル等はindex.tsと同じディレクトリに配置しておきます。

$ npx ts-node index.ts \
--endpoint xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com \
--ca_file AmazonRootCA1.pem \
--cert xxxxxxxxxx-certificate.pem.crt \
--key xxxxxxxxxx-private.pem.key \
--template_name my-template-20210423 \
--template_parameters '{"SerialNumber": "12345"}'

コマンドオプションについては以下の通りです。

オプション 設定内容
--endpoint AWS IoTのエンドポイント
--ca_file ダウンロードしておいたAWS IoTルートCAのファイル名
--cert ダウンロードしておいたクレーム証明書のファイル名
--key ダウンロードしておいた秘密鍵のファイル名
--template_name 先ほど作成したテンプレートの名前
--template_parameters モノの名前のプレフィックス以下につける値をSerialNumberをキーにして指定(json文字列)

実行後、カレントディレクトリに秘密鍵と証明書が作成されています。また、ブラウザで確認すると証明書とともにモノも自動で作られていることがわかります。

この証明書を使用してAWS IoTとの疎通確認を行います。AWS IoTのメニューからテストを開き、「トピックをサブスクライブする」のトピックフィルターの入力フォームにtest/topicと入力してサブスクライブします。

samples/node/pub_subのサンプルコードでMQTT Publishを試します。

$ mv 証明書ファイル名 ../pub_sub
$ mv 秘密鍵ファイル名 ../pub_sub

# ルートCA証明書もコピーしておく
$ cp AmazonRootCA1 ../pub_sub

$ cd ../pub_sub
$ npx ts-node index.ts \
--endpoint xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com \
--ca_file AmazonRootCA1.pem \
--cert 証明書ファイル名 \
--key 秘密鍵ファイル名

Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":1}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":2}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":3}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":4}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":5}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":6}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":7}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":8}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":9}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":10}

AWS IoTのテスト画面を確認すると、同様のメッセージが表示されます。Fleet Provisioningでモノを登録し自動で作成された証明書と秘密鍵を用いてAWS IoTとの疎通が成功したことを確認できました。

CreateCertificateFromCsr

このAPIではCSRをリクエストペイロードに含め、レスポンスペイロードに作成された新しい証明書が含まれます。

秘密鍵は手元で生成したもの(デバイスに保存される想定)を利用するため、CreateKeysAndCertificateと異なり秘密鍵はインターネットを通りません。これは本記事の冒頭で掲載している一覧表にも記載があります。

まずは手元で秘密鍵とCSRの作成を行います。今回はopensslを使用します。

$ openssl genrsa 2048 > my-device-private.pem.key
Generating RSA private key, 2048 bit long modulus

# 検証用なのでCommon Name以外は全て空にする
$ openssl req -new -key my-device-private.pem.key > my-device.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:Test
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

CreateKeysAndCertificateと同様、レスポンスに含まれる証明書をファイルとして保存したいため、サンプルコードを編集しておきます。

https://github.com/aws/aws-iot-device-sdk-js-v2/blob/v1.4.3/samples/node/fleet_provisioning/index.ts#L221

index.ts

fs.writeFileSync(
  `certs/${response.certificateId}-certificate.pem.crt`,
  response.certificatePem
);

Fleet Provisioningを実施する前に、クレーム証明書にアタッチしているポリシーを修正しておく必要があります。メニューの 安全性 > ポリシー から、テンプレート名-timestampという名前で作成されているポリシーのポリシードキュメントを編集します。

以下のハイライトされている行は追加箇所です。

{
  "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:account-id:topic/$aws/certificates/create/*",
        "arn:aws:iot:ap-northeast-1:account-id:topic/$aws/certificates/create-from-csr/*",
        "arn:aws:iot:ap-northeast-1:account-id:topic/$aws/provisioning-templates/my-template-20210423/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Subscribe"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:account-id:topicfilter/$aws/certificates/create/*",
        "arn:aws:iot:ap-northeast-1:account-id:topicfilter/$aws/certificates/create-from-csr/*",
        "arn:aws:iot:ap-northeast-1:account-id:topicfilter/$aws/provisioning-templates/my-template-20210423/provision/*"
      ]
    }
  ]
}

参考: https://docs.aws.amazon.com/iot/latest/developerguide/reserved-topics.html#reserved-topics-fleet

あとはコマンドオプションを指定してFleet Provisioningを実行します。CreateKeysAndCertificateとは異なり、--csr_fileで先ほど作成したCSRのファイル名を指定する必要があります。

$ npx ts-node index.ts \
--endpoint xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com \
--ca_file AmazonRootCA1.pem \
--cert xxxxxxxxxx-certificate.pem.crt \
--key xxxxxxxxxx-private.pem.key \
--template_name my-template-20210423 \
--template_parameters '{"SerialNumber": "22222"}' \
--csr_file my-device.csr

実行後、カレントディレクトリに証明書が作成されています。CreateKeysAndCertificateと同様に疎通確認を行います。コマンドオプションの--keyにopensslで作成した秘密鍵を指定する点が異なります。

# 保存された証明書を移動する
$ mv 証明書ファイル名 ../pub_sub

# opensslで作成した秘密鍵を移動する
$ mv my-device-private.pem.key ../pub_sub

$ cd ../pub_sub
$ npx ts-node index.ts \
--endpoint xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com \
--ca_file AmazonRootCA1.pem \
--cert 証明書ファイル名 \
--key my-device-private.pem.key

Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":1}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":2}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":3}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":4}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":5}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":6}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":7}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":8}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":9}
Publish received. topic:"test/topic" dup:false qos:1 retain:false
{"message":"Hello world!","sequence":10}

おわりに

ドキュメントを読むだけでなく、実際に手を動かすことでFleet Provisioningの流れを把握することができました。sdkのサンプルコードを利用すれば簡単に実行することができるため、ぜひ試してみてください。