Amazon Web Services ブログ

量産デバイスや大量のデバイスに個別の認証情報を発行する方法について

日々、様々なIoTのアーキテクチャについて相談を受けておりますが、具体的に量産デバイスや大量のデバイスのキッティングで、個別の証明書を格納する方法が課題としてよく上がります。AWS IoT Deep Diveセミナー#1ではAWS IoT Coreが提供する様々なプロビジョニング方法について紹介しています。この様に多くのプロビジョニング方法を提供していますが、 AWS IoT Core が発行する証明書を利用するパターンでは Fleet Provisioning を使った方式を選ばれるお客様が多くなってきました。この記事では、サンプルを交えながらあらためて Fleet Provisioning について詳しく解説していきたいと思います。

また、デバイスの製造と AWS IoTCore での X.509 証明書のプロビジョニングというホワイトペーパーも公開されていますので、こちらも合わせてご覧いただけますと幸いです。

この記事では AWS IoT Device SDK for Python のサンプルを使って試す方法について紹介していますが、組み込みデバイスでは FreeRTOS を利用することも多くなってきております。また、IoTデバイスはさまざまな環境やユーザーの元で利用させることもあり、デバイスに保存されるクレデンシャル(認証情報)はセキュアに保つ必要が出てきており、先日ブログでも紹介したIoT Lens の中でも、耐タンパ性の高いセキュアエレメントを利用して保存することを推奨しています

FreeRTOS でセキュアエレメントを活用した Fleet Provisioning が試せるサンプルは、デバイスカタログに認定デバイスとしてGR-ROSE(ルネサス エレクトロニックス社のRX65Nファミリのマイコンを利用)を掲載している株式会社コアのサイトで公開されています。 以下のようなアーキテクチャを構築する際のサンプルが公開されていますので、FreeRTOS を使用したサンプルに興味がある方はぜひご覧ください。


Fleet Provisioning の紹介

PoC の様に数台の IoT デバイスを使う場合であれば、個別に証明書を発行してそれぞれをデバイスに組み込むのは難しくありませんが、大量生産されるような量産向けのデバイスでは、それぞれのデバイスに個別の証明書を手動で発行して組み込むのは大変困難です。 AWS ではこの様に大量のデバイスをプロビジョニングするための方法がいくつか用意されていますが、自社で証明書を発行せずに AWS IoT Core が発行する証明書を大量にかつ自動的に発行する場合は、 Fleet Provisioning を使うのが有効です。
Fleet Provisioning には2種類のキーペア(秘密鍵、証明書)が登場します。1つ目は Claim 証明書、2つ目はデバイス固有の証明書です。 Claim 証明書は、デバイス固有の証明書を発行するための権限のみを持つ IoT Policy が紐づけられており、通常の通信を行うために接続を確立する証明書として利用することができません。デバイス固有の証明書は Claim 証明書を使って IoT デバイスが AWS IoT Core に対して証明書の発行をリクエストすると発行される証明書です。 IoT デバイスは、このデバイス固有の証明書を使って、センサーデータの送信やクラウドからのコマンドを受け付けることが可能になります。

以下の図でもう少し詳しく処理の順番を見ていきましょう。 IoT デバイスと AWS IoT Core の通信は MQTT プロトコルを利用して行われます。


①の処理では Claim 証明書を利用して AWS IoT Core に MQTT で接続します。この時接続で利用する ClientID は他のデバイスと重複しない様にユニークな ID を使用します。接続ができたら証明書発行結果を受け取る Topic に Subscribe します。そして、証明書の発行 Topic に空のメッセージを Publish することで、 Subscribe してあった Topic に新たに発行された秘密鍵・証明書と、トークンが Publish されてきます。受け取るデータの形式は、 Topic の payload-format に json または cbor を指定されたデータになります(詳しくは秘密鍵と証明書の発行を参照)。この時点では発行された証明書は有効になっていません。

②の処理では ①のMQTTコネクションをそのまま利用し、Thing の登録結果を受け取る Topic に Subscribe し Thing の登録を要求する Topic に①の処理で受け取ったトークンと Thing 登録で使うパラメータを Publish します。 Subscribe してある Topic に Thing 登録の結果が Publish されてきます。デバイス側ではこの結果を見て、正常に登録されたことや Thing 名、その他追加の情報を受け取ることができます(詳しくは Thing の登録を参照)。 Thing の登録を要求する Topic では、プロビジョニングテンプレートを指定して必要な Thing などを作成し、 IoT Policy と証明書を関連づけ、証明書を有効にします。この時に、プロビジョニングフックとして AWS Lambda を実行することができますので、登録しても良いデバイスなのかのチェックや、プロビジョニングテンプレートへ渡すパラメータの作成、そのほかの関連処理を実行してから Thing の作成を実行することができます。

③の処理ではデバイス固有の証明書がデバイス側にありますので、固有の証明書を使って AWS IoT Core との通信を開始します。この通信からは AWS IoT Core に接続するときの ClientID は Thing 名などユニークな値を利用します。次回以降デバイスが起動した後はこの証明書や ClientID を利用して接続する様にします。

これまでの説明で Claim 証明書を使った固有の証明書を発行する仕組みが理解できたかと思います。この Claim 証明書が流出した場合に好き勝手に証明書が発行されてしまうのではと?気になった方もいるかもしれません。その様なリスクを考えた場合、先に説明した様にプロビジョニングフックで、正しいデバイスからのリクエストなのか?のチェックする仕組みを入れておくことを推奨しています。

例)

  • デバイス固有のシリアル ID があるとして、それを事前にデバイス管理用のデータベースに格納しておきます。 Lambda では送られてきたシリアル ID がそのデータベースに存在するかをチェックし、問題がなければ有効にします
  • デバイスのプロビジョニングで人の操作がある場合は、スマートフォン等で One-Time-Password(OTP) を発行し、それをデバイスを管理するデータベースに登録し、デバイスからプロビジョニングする際に OTP も一緒に送って Lambda で検証し、問題がなければ有効にします
  • この後に説明している信頼できるユーザーによるプロビジョニング方式を採用します

上記の例では Claim 証明書は最初からデバイスに埋め込むパターンについての説明でしたが、もう一つ信頼できるユーザーによるプロビジョニング方式と呼ばれる方法もあります。違いは Claim 証明書自体をユーザー認証ができた場合に取得できるもので、取得した後の流れば同じとなります。ユーザーによる認証が必要となるため、スマートホームデバイスの様に、ユーザーによるセットアップが発生する様なユースケースで有効と思います。この方法で発行される Claim 証明書は有効期限が5分に設定されているため Claim 証明書の流出による懸念を低減することができます。
最後にこの例では秘密鍵と証明書が AWS IoT Core で発行されますが、セキュアエレメントの様に、秘密鍵自体をデバイス側で持っている場合には CSR による証明書の発行リクエストを送ることで、秘密鍵をデバイス外に出すことなく、プロビジョニングすることも可能です。この場合は CSR による証明書の発行リクエストで利用する MQTT Topic が  $aws/certificates/create-from-csr/payload-format  と変わってきます。 Thing の登録は同じ Topic を利用します。

以上で Fleet Provisioning の全体像が見えてきたのではと思います。

AWS IoT Device SDK を使って動作を確認

ここまで見てきて、実際の動きを試してみたいと思われた人も多いのではないでしょうか?  AWS IoT Device SDK では Fleet Provisioning 用のサンプルも用意されています。実際に Python SDK のサンプルを使って試してみましょう。
AWS CLI を利用して設定しますので、クレデンシャルを用意してください( AWS Identity and Access Management  、 AWS IoT Core 、 Lambda の操作が可能な権限を持つ物)。

Step1 Fleet Provisioning の準備

環境変数の設定

export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_ACCESS_KEY_ID=権限を持つアクセスキー
export AWS_SECRET_ACCESS_KEY=シークレットアクセスキー

Fleet Provisioning のテンプレートに必要な権限を持つ Role を作成

# Assume Roleのドキュメントを用意
cat << EOF > assume-role-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "iot.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# プロビジョニングで使われるIAM Roleを作成 
aws iam create-role --role-name FPTemplateTestRole \
--assume-role-policy-document file://assume-role-policy.json

# Roleにマネージドポリシーを紐付け
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration \
--role-name FPTemplateTestRole

プレプロビジョニングフックの Lambda 関数の準備

# Lambda用のPolicyドキュメントを用意
cat << EOF > lambda-assume-role-policy.json
{
  "Version": "2012-10-17",
  "Statement":[
    {
      "Effect": "Allow",
      "Principal":{
        "Service":"lambda.amazonaws.com"
      },
      "Action":"sts:AssumeRole"
      }
  ]
}
EOF

# Lambda用のRoleを作成
aws iam create-role --role-name FPTestHookLambdaRole \
--assume-role-policy-document file://lambda-assume-role-policy.json


# Lambda実行用のマネージドポリシーを紐付け
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \
--role-name FPTestHookLambdaRole

# Lambda用のRole ARNを後で利用するので変数に設定
LAMBDA_ROLE=$(aws iam get-role --role-name FPTestHookLambdaRole --query Role.Arn --output text)

Lambda 関数の作成

# hook用のLambdaソースコード
cat << EOF > lambda_function.py
import json
from datetime import datetime

def lambda_handler(event, context):
  print(event)
  provision_response = {
    "allowProvisioning": True, # set False if you don't allow to provisioning
    "parameterOverrides": {
      "CreateDateTime": datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
    }
  }

  return provision_response
EOF

# Lambdaのソースコードをzip化
zip -j lambda.zip lambda_function.py

# Lambda Functionの作成
aws lambda create-function \
--function-name FPTestHookLambda \
--runtime python3.8 \
--zip-file fileb://lambda.zip \
--handler lambda_function.lambda_handler \
--role ${LAMBDA_ROLE}

# LambdaがAWS IoT Coreから起動できるように権限を付与
aws lambda add-permission \
--function-name FPTestHookLambda \
--statement-id iot-permission \
--action lambda:InvokeFunction \
--principal iot.amazonaws.com

プロビジョニングテンプレートの登録

# 作成したLambdaのARNを変数に設定
LAMBDA_HOOK_ARN=$(aws lambda get-function --function-name FPTestHookLambda --query Configuration.FunctionArn --output text)

# プロビジョニングhookが呼び出すLambdaのドキュメントを作成
cat << EOF > hook.json
{
  "targetArn": "${LAMBDA_HOOK_ARN}",
  "payloadVersion":"2020-04-01"
}
EOF

# プロビジョニングテンプレートのドキュメントを作成
cat << EOF > fleet-provisioning-template.json
{
  "Parameters" : {
    "SerialNumber": {
      "Type": "String"
    },
    "CreateDateTime": {
      "Type": "String"
    }
  },
  "Resources" : {
    "thing" : {
      "Type" : "AWS::IoT::Thing",
      "Properties" : {
        "AttributePayload" : {
          "SerialNumber" : {"Ref":"SerialNumber"},
          "CreateDateTime": {"Ref":"CreateDateTime"}
        },
        "ThingName" : {"Fn::Join":["_",["FPTest",{"Ref":"SerialNumber"}]]}
      },
      "OverrideSettings" : {
        "AttributePayload" : "MERGE"
      }
    },
    "certificate" : {
      "Type" : "AWS::IoT::Certificate",
      "Properties" : {
        "CertificateId": {"Ref": "AWS::IoT::Certificate::Id"},
        "Status" : "Active"
        }
    },
    "policy" : {
      "Type" : "AWS::IoT::Policy",
      "Properties" : {
        "PolicyDocument" : "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action\": \"iot:*\",\"Resource\": \"*\"}]}"
      }
    }
  },
  "DeviceConfiguration": {
  "DeviceCreateDate": {"Ref": "CreateDateTime"}
  }
}
EOF

# プロビジョニングで使われるRoleのARNを変数に設定
FP_TEMPLATE_ROLE_ARN=$(aws iam get-role --role-name FPTemplateTestRole --query Role.Arn --output text)
# テンプレート名を変数に設定
TEMPLATE_NAME=FPTestTemplate

# プレプロビジョニングテンプレートを作成
aws iot create-provisioning-template \
--template-name ${TEMPLATE_NAME} \
--provisioning-role-arn ${FP_TEMPLATE_ROLE_ARN} \
--template-body file://fleet-provisioning-template.json \
--pre-provisioning-hook file://hook.json \
--enabled

Claim 用のキーペアを作成

mkdir certs
# AWS IoT CoreのCA証明書を取得
wget -O certs/root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem

# AWS IoT Coreで証明書と秘密鍵を発行
aws iot create-keys-and-certificate \
--set-as-active \
--certificate-pem-outfile certs/claim_cert.crt \
--public-key-outfile certs/claim_pub.key \
--private-key-outfile certs/claim_private.key

# 作成した証明書のARNを変数に設定
CERTIFICATE_ARN=create-keys-and-certificate の結果で表示される certificateArn の値を指定

# AWS のAccount IDを変数に設定
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Claim用の証明書に紐づくIoT Policyのドキュメントを作成
cat << EOF > claim-iot-policy.json
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Action": "iot:Connect",
      "Resource": "*"
    },
    {
      "Effect":"Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource":[
        "arn:aws:iot:${AWS_DEFAULT_REGION}:${ACCOUNT_ID}:topic/\$aws/certificates/create/*",
        "arn:aws:iot:${AWS_DEFAULT_REGION}:${ACCOUNT_ID}:topic/\$aws/provisioning-templates/${TEMPLATE_NAME}/provision/*"
      ]
    },
    {
      "Effect":"Allow",
      "Action": "iot:Subscribe",
      "Resource":[
        "arn:aws:iot:${AWS_DEFAULT_REGION}:${ACCOUNT_ID}:topicfilter/\$aws/certificates/create/*",
        "arn:aws:iot:${AWS_DEFAULT_REGION}:${ACCOUNT_ID}:topicfilter/\$aws/provisioning-templates/${TEMPLATE_NAME}/provision/*"
      ]
    }
  ]
}
EOF

# Claim証明書に紐づくIoT Policyを作成
aws iot create-policy --policy-name FPTestIoTPolicy \
--policy-document file://claim-iot-policy.json

# IoT PolicyをClaim証明書に紐付け
aws iot attach-policy --policy-name FPTestIoTPolicy \
--target ${CERTIFICATE_ARN}

Step1 の作業は最初に1回だけおこないます。この作業で FleetProvisioning に必要な AWS 側の設定と IoT デバイスに格納する Claim 用のキーペアが作成されます。今回の例では、IoT Policyにはすべての操作を許可する iot:* が指定されていますが、本番環境での利用では必要最低限の権限に限定したり、AWS IoT Core policy variables を利用してセキュアなポリシーを設定してください。

Step2 Fleet Provisioning の動作確認

ターミナルに以下のコマンドを入力してセットアップを進めます。

# AWS IoT Device SDK v2 for Python のインストール
python3 -m pip install awsiotsdk

# AWS IoT Device SDK v2 for Python のサンプルを使うため、ソースコードを取得
git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git

サンプルプログラムを実行

# AWS IoT Coreの接続先を変数に設定
IOT_ENDPOINT=$(aws iot describe-endpoint --endpoint-type iot:Data-ATS --output text)

# 新しいデバイスで登録する場合はこの値を変更
SERIAL_NUMBER=123456

# サンプルプログラムを実行
python3 aws-iot-device-sdk-python-v2/samples/fleetprovisioning.py \
--endpoint ${IOT_ENDPOINT} \
--root-ca certs/root.ca.pem \
--cert certs/claim_cert.crt \
--key certs/claim_private.key \
--templateName ${TEMPLATE_NAME} \
--templateParameters "{\"SerialNumber\":\"${SERIAL_NUMBER}\"}"

サンプルのプログラムを実行すると、証明書が新たに発行され Thing と IoT Policy が紐づけられるのが確認できたと思います。
Provisioning Template で DeviceConfiguration の指定がされているので、実行結果を見るとフックの Lambda で設定した情報をデバイス側で受け取ることもできます。

Received a new message awsiot.iotidentity.RegisterThingResponse(device_configuration={'DeviceCreateDate': '2021-09-08T07:52:28'}, thing_name='FPTest_123456')

このサンプルでは発行された証明書はログ出力されるだけで終わりますが、実際は発行された証明書と秘密鍵をデバイス上に保存し、この情報と Thing 名で AWS IoT Core と接続してデータの送信などを行います。
また、 SERIAL_NUMBER の値を変えて、サンプルプログラムを再実行すると、新しい Thing と証明書が登録されるのが確認できます。

大量のデバイスををセキュアに保つことに利用できるその他のサービス

セキュアな接続に必要なキーペアの作成を Fleet Provisioning を使えば簡単にできることが確認できたと思いますが、 AWS IoT サービスでは、他にも IoT デバイスをセキュアに保つ仕組みを提供しています。

AWS IoT Device Defender を使ったデバイスの監視

AWS IoT Core に接続されたデバイスが送ってくるデータや、接続情報を元に、クラウド側だけでセキュリティの監視が行うことができたり、デバイスに AWS IoT Device Defender の SDK を組み込んで情報を送ることで、デバイス側で取得できる情報をもとにセキュリティの監視やルールを作成することができます。このサービスを利用することで、遠隔地にある大量のデバイスのセキュリティ問題を発見することができます。

Job を使ったファームウエアの更新について

IoT デバイスは一度ファームウエアを作成したら終わりではなく、セキュリティのパッチやバグフィックス、機能追加など常に更新する可能性があります。その様な Over-The-Air(OTA) でのファームウエアのアップデートをする仕組みを作成するのも課題として上がりますが、 AWS IoT Device Management の機能を使って、対象となるデバイスを絞り込み、 OTA を実行する仕組みを実現することができます。リモートからのアップデートが出来ることで、 IoT デバイスを常にセキュアに保つことができます。

この様な IoT デバイスの運用に関わるサービスについて紹介している資料動画 がありますので、ぜひご覧ください。

まとめ

いかがでしたでしょうか?  Fleet Provisioning を使うことで個別の証明書をデバイスに事前に埋め込むことなく、安全にかつ大量のデバイスのプロビジョニングが行えることが確認できたかと思います。
今回紹介していないような、自社の認証局で発行する証明書を使うパターンの設定や、デバイス管理に役立つ Job などの機能を体験できるAWS IoT Device Management ワークショップが公開されていますので、ぜひ試していただければと思います。


著者

市川 純
プロトタイピングソリューションアーキテクト
AWS では IoT に関連するプロトタイピングを支援する、プロトタイピング ソリューション アーキテクトとして、お客様の IoT 関連案件を支援しています。