In this exercise you will use fleet provisioning to onboard a device with AWS IoT. One use case for fleet provisioning is manufacturing of devices where the same initial firmware should be used for all devices and devices will be provisioned when they connect for the first time to AWS IoT.
By using AWS IoT fleet provisioning, AWS IoT can generate and securely deliver device certificates and private keys to your devices when they connect to AWS IoT for the first time.
There are two ways to use fleet provisioning, by claim and by trusted user. For the workshop you will use provisioning by claim.
With fleet provisioning you use a so called provisioning claim certificate and private key and a provisioning template. The IoT policy associated to the claim certificate has limited permission that allow only the use together with fleet provisioning.
A provisioning template is a JSON document with a parameters and resources section. It defines how your devices are provisioned with AWS IoT.
When your device connects for the first time to AWS IoT Core it uses the provisioning claim certificate and communicate with the device provisioning MQTT API to be provisioned and get its final device certificate.
The exercise is based on the fleet provisioning example from the AWS IoT SDK for Python v2. It uses a modified version of the script fleetprovisioning.py
.
Use the directory ~/provisioning/fleet-provisioning
for the exercises in this chapter.
~/provisioning/fleet-provisioning
When you create a template you need to provide an IAM role which gives the AWS IoT service permission to create or update IoT resources.
An appropriate role has been created already and its
arn is stored in the shell environment variable $ARN_IOT_PROVISIONING_ROLE
.
A provisioning template has been copied into the directory ~/provisioning/fleet-provisioning
.
Create the thing group fleet-provisioning-group
. Device created by fleet provisioning will be added to this group.
aws iot create-thing-group --thing-group-name fleet-provisioning-group
Create the provisioning template
aws iot create-provisioning-template \
--template-name FleetProvisioningTemplate \
--provisioning-role-arn $ARN_IOT_PROVISIONING_ROLE \
--template-body file://./fleet-provisioning-template.json \
--enabled
Verify that the provisioning template has been created
aws iot list-provisioning-templates
You can also get the complete provisioning template
aws iot describe-provisioning-template --template-name FleetProvisioningTemplate
Create a provisioning claim certificate and key, an IoT policy and attach the policy to the claim certificate.
Create claim certificate and key
THING_NAME=provision-claim
aws iot create-keys-and-certificate --set-as-active \
--public-key-outfile $THING_NAME.public.key \
--private-key-outfile $THING_NAME.private.key \
--certificate-pem-outfile $THING_NAME.certificate.pem > provisioning-claim-result.json
Get the certificate arn from the result of the previous command. The certificate arn is required to attach an IoT policy to it.
CERTIFICATE_ARN=$(jq -r ".certificateArn" provisioning-claim-result.json)
Create an IoT policy for the claim certificate
aws iot create-policy --policy-name fleet-provisioning_Policy \
--policy-document file://./fleet-provisioning-policy.json
Attach the policy to the claim certificate
aws iot attach-policy --policy-name fleet-provisioning_Policy \
--target $CERTIFICATE_ARN
Use the script fleetprovisioning.py
to provision a device. On successf the fleet provisioning process will create a device named fleety_[serial_number]
. The serial number will be passed as an argument for the fleet provisioning process.
The device certificate and key will be stored to [device_name].certificate.pem
and [device_name].private.key
.
Start the fleet provisioning process
./fleetprovisioning.py --endpoint $IOT_ENDPOINT \
--root-ca ~/root.ca.bundle.pem \
--cert ./provision-claim.certificate.pem \
--key ./provision-claim.private.key \
--client-id fleet-device \
--templateName FleetProvisioningTemplate \
--templateParameters "{\"SerialNumber\":\"297468\",\"DeviceLocation\":\"Berlin\"}"
Verify if the device was created either in the AWS IoT Core console or with the command line
aws iot describe-thing --thing-name fleety_297468
Verify that the device has been added to the thing group
aws iot list-things-in-thing-group --thing-group-name fleet-provisioning-group
After your device has been provisioned successfully it is ready to use. Publish a message and verify that it arrives.
Subscribe to the topic fleet/provisioning
Publish a message
# publish
mosquitto_pub --cafile ~/root.ca.bundle.pem \
--cert fleety_297468.certificate.pem \
--key fleety_297468.private.key \
-h $IOT_ENDPOINT -p 8883 -q 1 \
-t fleet/provisioning \
-i fleety_297468 \
--tls-version tlsv1.2 \
-m "{\"fleet\": \"provisioned\", \"date\": \"$(date)\"}" -d
Did the message arrive?
To improve your fleet provisioning process you can set up a Lambda function to validate parameters passed from the device before allowing the device to be provisioned. A validation could for example be a lookup in a database if the device is allowed to be provisioned.
In this exercise you will use a simple verification: The serial number of the device must start with 297468
. But feel free to implement a more sophisticated verification process.
With AWS Cloud9 you can create and modify a Lambda function from within your environment and deploy ther function to the cloud.
A skeleton for the Lambda function has been created already. You need to replace the code and deploy the Lambda function.
In your Cloud9 environment:
DeviceManagementWorkshop
FleetProvisioningHook
lambda_function.py
lambda_function.py
import json
import logging
import sys
# Configure logging
logger = logging.getLogger()
for h in logger.handlers:
logger.removeHandler(h)
h = logging.StreamHandler(sys.stdout)
FORMAT = "[%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - %(funcName)s - %(message)s"
h.setFormatter(logging.Formatter(FORMAT))
logger.addHandler(h)
logger.setLevel(logging.INFO)
SERIAL_STARTSWITH = "297468"
def verify_serial(serial_number):
if serial_number.startswith(SERIAL_STARTSWITH):
logger.info("serial_number {} verification succeeded - starts with {}".format(serial_number, SERIAL_STARTSWITH))
return True
logger.error("serial_number {} verification failed - does not start with {}".format(serial_number, SERIAL_STARTSWITH))
return False
def lambda_handler(event, context):
response = {'allowProvisioning': False}
logger.info("event: {}".format(json.dumps(event, indent=2)))
if not "SerialNumber" in event["parameters"]:
logger.error("SerialNumber not provided")
else:
serial_number = event["parameters"]["SerialNumber"]
if verify_serial(serial_number):
response = {'allowProvisioning': True}
logger.info("response: {}".format(response))
return response
Local Functions
FleetProvisioningHook
FleetProvisioningHook
Remote Functions
cloud9-FleetProvisioningHook-FleetProvisioningHook-[A_UNIQUE_STRING]
. [A_UNIQUE_STRING] could look like 1XB6V0EI92HQ3
Add a permission to the Lambda to allow fleet provisioning to invoke the function. The permission is added through the command line. To add permission you need the exact name of your Lambda function.
In a Cloud9 terminal:
aws lambda list-functions --query 'Functions[?starts_with(FunctionName, `cloud9-FleetProvisioningHook-FleetProvisioningHook`) == `true`].FunctionName' --output text
The output of this command should look similar to cloud9-FleetProvisioningHook-FleetProvisioningHook-5N8LYVNVY3BP
In the next command replace [YOUR_LAMBDA_FUNCTION_NAME]
with the name of your Lambda function.
aws lambda add-permission --statement-id $(date '+%Y%m%d%H%M%S') \
--principal iot.amazonaws.com \
--action lambda:InvokeFunction \
--function-name [YOUR_LAMBDA_FUNCTION_NAME]
The result of the command contains the arn of your Lambda function. Take a note or the arn because it is needed in the next command to update the provision template.
Sample output:
{
"Statement": "{\"Sid\":\"20200812113213\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"iot.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:eu-west-2:123465017379:function:cloud9-FleetProvisioningHook-FleetProvisioningHook-XXXXXXXXXXXX\"}"
}
The provisioning template must be updated to use the Lambda function that you just created.
Update the provisioning template. Replace [YOUR_LAMBDA_FUNCTION_ARN]
with the arn of your Lambda function.
aws iot update-provisioning-template \
--template-name FleetProvisioningTemplate \
--pre-provisioning-hook payloadVersion=2020-04-01,targetArn=[YOUR_LAMBDA_FUNCTION_ARN]
Verify that the template has been updated.
You learned alread a command starting with aws iot describe-provisioning-template --t...
In the output of the command you should find the key preProvisioningHook
where your Lambda is defined.
You have already fleet provsioned a device with the command fleetprovisioning.py
.
Try to provision devices with different serial numbers. Only devices with serial numbers that starts with 297468
will be provisioned.
In case of unexpected errors start troubleshooting by analyzing the logs stored in Amazon CloudWatch.