Fleet Provisioning

こちらのセクションではAWS IoT Core のフリートプロビジョニングの機能を使用して、初めて AWS IoT に接続するときにデバイス証明書とプライベートキーを生成し、AWS IoTからデバイスに対して安全に配信することができます。これによって製造時に個別の証明書をAWS IoTに登録する必要がありません。 初回接続時にLambda 関数を介してデバイスからのメッセージのペイロードを検証し、 ID を顧客の AWS アカウントに登録し、必要なすべてのアクセス許可とレジストリメタデータ(モノ、モノのグループなど)をデバイスに設定します。これはすべて、デバイスが AWS IoT Core に最初に接続したとき、またはデバイスが新しい認証情報または更新の必要があるときに自動的に行われるため、エンジニアの貴重な時間とリソースの節約にも繋がります。

フリートプロビジョニングでは、大きく分けて2つのプロビジョニング方法があります。このハンズオンではクレームによるプロビジョニングを行います。

クレームによるプロビジョニング

ブートストラップ証明書を用いたアプローチと呼ばれることがあります。(このハンズオンでは4.5のLambdaでデバイスが送信するシリアルナンバーからデバイス情報を検証しています。)

image

信頼できるユーザー(モバイル/ Web アプリユーザーなど)によるプロビジョニング

エンドユーザーやインストール技術者などの信頼されたユーザーがモバイルアプリを使用してデプロイされた場所にデバイスを設定するときに、デバイスは AWS IoT に初めて接続します。

image

プロビジョニングテンプレートを作成する

プロビジョニングテンプレートは、デバイスをプロビジョニングするときに実行する必要がある指示の詳細を示します。プロビジョニングテンプレートには、特定の証明書に関連付けるポリシー、デバイスレジストリのモノの名前、関連付けられた証明書をアクティブにするかどうかなどを含めることができます。詳細については、フリートプロビジョニングテンプレートのドキュメントをご覧ください。

フリートプロビジョニングの作業用ディレクトリに移動します。

cd ~/fleetProv

AWS IoT はリソースベースのポリシーを使用して Lambda を呼び出すため、Lambda 関数を呼び出すための AWS IoT アクセス許可を与える必要があります。 以下は、Lambda に IoT アクセス許可を与える add-permission を使用する例です。

aws lambda create-function \
    --region $AWS_REGION \
    --function-name preProvFook-$NAME \
    --zip-file fileb://lambda_function.zip \
    --role $ARN_LAMBDA_ROLE \
    --handler lambda_function.lambda_handler \
    --runtime python3.8 \
    --timeout 30 \
    --memory-size 128
aws lambda add-permission \
    --function-name preProvFook-$NAME \
    --statement-id iot-permission \
    --action lambda:InvokeFunction \
    --principal iot.amazonaws.com

事前プロビジョニングフックで使用するAWS Lambdaの内容

デバイスから送信されるペイロードを取得して、ブラック/ホワイトリストや内部の登録データベース、その他の内部リソースを使用して分析を行い、プロビジョニング要求を承認または拒否できます。このLambdaでは、シリアルナンバーが1~10000のデバイスに関しては承認して、プロビジョニング処理を行うことができます。 デバイスから送信された “provisioning-templates” に関する属性は、 Lambda のイベントの [“parameters”] タグから取得できます。 Lambdaが、プロビジョニング処理を行うためには、少なくともブール値の “allowProvisioning” ( True / False )を返す必要があります。


import json

provision_response = {'allowProvisioning': False}

def isBlacklisted(serial_number):
    if serial_number >= 1 and serial_number <= 10000:
        return True
    else:
        return False
    #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

上記のLambdaをテンプレートに登録するため、hooks.jsonを編集します。

ARN_LAMBDA_FLEET=$(aws lambda get-function --function-name preProvFook-$NAME | jq -r '.Configuration.FunctionArn')

echo '{"targetArn" : "'$ARN_LAMBDA_FLEET'","payloadVersion" : "2020-04-01"}' >> hooks.json

create-provisioning-templateコマンドを使用して、事前プロビジョニングフックを追加したプロビジョニングテンプレートを作成します。

aws iot create-provisioning-template \
    --template-name template-$NAME \
    --provisioning-role-arn $ARN_IOT_PROVISIONING_ROLE \
    --template-body file://template.json \
    --pre-provisioning-hook file://hooks.json \
    --enabled

デバイス証明書用の名前を設定します。

deviceCert=deviceFLEETCert

ブートストラップ用の証明書を作成します。certificateArnをコピーします。

wget -O certs/root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
aws iot create-keys-and-certificate \
    --set-as-active \
    --certificate-pem-outfile certs/$deviceCert.crt \
    --public-key-outfile  certs/${deviceCert}_pub.key\
    --private-key-outfile certs/${deviceCert}_private.key

ブートストラップ証明書用に制限付きポリシーを証明書にアタッチします。

ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account')
REGION=$(cat ~/.aws/config | grep region | sed 's/region = //g')
echo -e "{\n  \"Version\":\"2012-10-17\",\n  \"Statement\":[\n  {\n    \"Effect\":\"Allow\",\n    \"Action\":[\n      \"iot:Connect\"\n    ],\n    \"Resource\":[\n      \"*\"\n    ]\n  },\n  {\n    \"Effect\":\"Allow\",\n    \"Action\": [\n      \"iot:Publish\",\n      \"iot:Receive\"\n    ],\n    \"Resource\":[\n      \"arn:aws:iot:$REGION:$ACCOUNT_ID:topic/\$aws/certificates/create/*\",\n      \"arn:aws:iot:$REGION:$ACCOUNT_ID:topic/\$aws/provisioning-templates/template-$NAME/provision/*\"\n    ]\n  },\n  {\n    \"Effect\":\"Allow\",\n    \"Action\":[\n      \"iot:Subscribe\"\n    ],\n  \"Resource\":[\n     \"arn:aws:iot:$REGION:$ACCOUNT_ID:topicfilter/\$aws/certificates/create/*\",\n     \"arn:aws:iot:$REGION:$ACCOUNT_ID:topicfilter/\$aws/provisioning-templates/template-$NAME/provision/*\"\n    ]\n   }\n  ]\n}" > bootStrapBody.json
aws iot create-policy --policy-name "bootstrapPolicy-$NAME" --policy-document file://bootStrapBody.json
aws iot attach-policy --policy-name "bootstrapPolicy-$NAME" --target [先程コピーしたcertificateArn]

接続情報を定義します。

echo -e "[SETTINGS]\nSECURE_CERT_PATH = /home/ec2-user\nROOT_CERT = fleetProv/certs/root.ca.pem\nCLAIM_CERT = fleetProv/certs/${deviceCert}.crt\nSECURE_KEY = fleetProv/certs/${deviceCert}_private.key\nIOT_ENDPOINT = $IOT_ENDPOINT\nPROVISIONING_TEMPLATE_NAME = template-$NAME" > config.ini

接続情報を確認します。

cat config.ini

デバイスをAWS IoT Coreに接続してFleet Provisioningを行います。

python3 main.py

以下のように出力されればFleet Provisioningは成功です。アクティベートした公式の証明書で接続して以前のbootstrap証明書では受け取ることのできなかった"openworld"というトピックからメッセージを受信しています。

[ec2-user@ip-192-168-128-14 fleetProv]$ python3 main.py
                 ______   __                   __
                / ____/  / /  ___     ___     / /_
               / /_     / /  / _ \   / _ \   / __/
              / __/    / /  /  __/  /  __/  / /_
             /_/      /_/   \___/   \___/   \__/


    ____                  _      _             _
   / __ \_________ _   __(_)____(_)___  ____  (_)___  ____ _
  / /_/ / ___/ __ \ | / / / ___/ / __ \/ __ \/ / __ \/ __ `/
 / ____/ /  / /_/ / |/ / (__  ) / /_/ / / / / / / / / /_/ /
/_/   /_/   \____/|___/_/____/_/\____/_/ /_/_/_/ /_/\__, /
                                                   /____/



 ____________________________________________________________
/_____/_____/_____/_____/_____/_____/_____/_____/_____/_____/



##### CONNECTING WITH PROVISIONING CLAIM CERT #####
##### SUCCESS. SAVING KEYS TO DEVICE! #####
##### CREATING THING ACTIVATING CERT #####
##### CERT ACTIVATED AND THING TestFleetPrefix_1261 CREATED #####
##### CONNECTING WITH OFFICIAL CERT #####
##### ACTIVATED AND TESTED CREDENTIALS (e1a30e7da6-private.pem.key, e1a30e7da6-certificate.pem.crt). #####
##### FILES SAVED TO /home/ec2-user #####
{'service_response': '##### RESPONSE FROM PREVIOUSLY FORBIDDEN TOPIC #####'}

main.pyの内容

  1. デバイスが AWS IoT Core に正常に接続されたら、認証ペイロードを生成するために、予約済みの AWS IoT プロビジョニングトピックにパブリッシュします。
  2. 認証されると、CreateKeysAndCertificate を呼び出して、AWS 認定権限により新しい証明書とプライベートキーを作成します。
  3. AWS IoT Coreのサブスクライブに対しコールバック関数が呼ばれ2で作成した証明書を取得して保存します。
  4. 3で保存した新たな証明証をアクティベートして、デバイスを登録します。
  5. 新たな証明証でIoT Coreに対してメッセージをPublishしています。

クレームによるデバイスのプロビジョニング方法 https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/provision-wo-cert.html


def on_message_callback(self, message):
		""" Callback Message handler responsible for workflow routing of msg responses from provisioning services.

		Arguments:
			message {string} -- The response message payload.
		"""
		json_data = json.loads(message.payload)

		# A response has been recieved from the service that contains certificate data.
		if 'certificateId' in json_data:
			self.logger.info('##### SUCCESS. SAVING KEYS TO DEVICE! #####')
			print('##### SUCCESS. SAVING KEYS TO DEVICE! #####')
			self.assemble_certificates(json_data)

		# A response contains acknowledgement that the provisioning template has been acted upon.
		elif 'deviceConfiguration' in json_data:
			self.logger.info('##### CERT ACTIVATED AND THING {} CREATED #####'.format(json_data['thingName']))
			print('##### CERT ACTIVATED AND THING {} CREATED #####'.format(json_data['thingName']))
			self.rotate_certs()
		else:
			self.logger.info(json_data)

def rotate_certs(self):
		"""Responsible for (re)connecting to IoTCore with the newly provisioned/activated certificate - (first class citizen cert)
		"""
		self.logger.info('##### CONNECTING WITH OFFICIAL CERT #####')
		print('##### CONNECTING WITH OFFICIAL CERT #####')
		self.cert_validation_test()
		self.new_cert_pub_sub()
		print("##### ACTIVATED AND TESTED CREDENTIALS ({}, {}). #####".format(self.new_key_name, self.new_cert_name))
		print("##### FILES SAVED TO {} #####".format(self.secure_cert_path))