Fleet provisioning

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.

Directory

Use the directory ~/provisioning/fleet-provisioning for the exercises in this chapter.

~/provisioning/fleet-provisioning

Create a provisioning template

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 claim certificate and key

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

Fleet provision a device

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

Use your fleet provisioned device

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?

(Optional) Pre-provisioning hook

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.

Create a Lambda function

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:

  1. Under FILE SYSTEM
  2. Expand DeviceManagementWorkshop
  3. Expand FleetProvisioningHook
  4. You should see now lambda_function.py
  5. Right click on lambda_function.py
  6. Open
  7. Replace the existing code with the following:
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

  1. File (in the menu bar)
  2. Save
  3. In the right pane click the λ icon or AWS Resources
  4. Expand Local Functions
  5. Right click on FleetProvisioningHook
  6. Deploy
  7. You should see an activity icon next to FleetProvisioningHook
  8. Wait until the deployment has been finished
  9. Expand Remote Functions
  10. You should see your Lambda function with a name like cloud9-FleetProvisioningHook-FleetProvisioningHook-[A_UNIQUE_STRING]. [A_UNIQUE_STRING] could look like 1XB6V0EI92HQ3

Add permissions

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\"}"
}

Update provisioning template

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.

Try to provision devices

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.