How to automate onboarding of IoT devices to AWS IoT Core at scale with Fleet Provisioning

Customers use AWS IoT to analyze the data generated by their IoT devices to quickly gain meaningful insights about their business. This helps them solve a variety of problems, such as identifying required improvements to their manufacturing processes, predicting device failures, or quickly diagnosing and troubleshooting device issues for their customers.  However, before IoT devices can connect to the cloud and do useful work, devices need to be provisioned. IoT device provisioning refers to the process of configuring devices with unique identities (e.g. X.509 certificate and a private key), registering these identities with the AWS IoT endpoint, and associating required permissions (e.g. an IoT Policy) so that devices can securely connect and operate with AWS IoT and other cloud-based applications.

Today, customers use AWS IoT Core features like Just-In-Time-Registration (JITR) and Just-In-Time-Provisioning (JITP) to automate and scale the process of registering device identities with the AWS Cloud and associating the required AWS IoT permissions. But, customers are still responsible for securely generating and flashing unique identities onto devices. For many customers, particularly for original equipment manufacturers (OEMs) manufacturing large numbers of devices, this process remains manual and time-consuming.

Now, with AWS IoT Core Fleet Provisioning, customers can securely automate the end-to-end device onboarding process.  More importantly, key attributes can now be sent from the device and validated in an AWS Lambda function for additional integrity. Fleet Provisioning securely delivers a unique digital identity to each device, validates the device payload via a Lambda function, registers the identity in a customers’ AWS account, and sets up the devices with all required permissions and registry metadata (e.g. Things, Thing Groups). All of this happens automatically upon the devices’ first connection to AWS IoT Core, or whenever a device needs to receive new credentials or updated configuration, saving valuable time and engineering resources for customers.

There are 2 primary provisioning methods available with Fleet Provisioning:

  1. Provisioning by Claim, often referred to as a bootstrap certificate approach.
  2. Provisioning by a trusted user (e.g. a Mobile/Web App user): this process is very similar to the provisioning by claim process.

In this blog, we demonstrate how to use the Provisioning by Claim approach in detail and explain when you should use this approach. At the end of this post, we will describe the differences when using the Provisioning by a Trusted User approach. So, let’s get started.

When to use the Provisioning by Claim workflow

Provisioning by Claim is designed to target scenarios wherein devices would be manufactured with a shared bootstrap certificate on them. These bootstrap certificates have limited IoT permissions that only allow the devices to do the following: 1/establish first connection with AWS IoT Core, 2/ prove their identity, and 3/ request a fully functional identity with the necessary IoT permissions that devices can use for subsequent communication with AWS IoT Core. This shared bootstrap certificate could be placed on devices at the factory or at a staging facility while flashing the initial software onto devices. If the device already has its own private key on board, it can send a certificate signing request (CSR) along with the bootstrap certificate to be signed by AWS IoT Core.

In addition to validating the bootstrap certificate presented by devices, Fleet Provisioning also provides Lambda-based provisioning hooks that enable appropriate validation for pertinent device attributes. Examples of device attributes could include a serial number, MAC ID, device location, etc. The Lambda functions should be leveraged in the provisioning transaction to automate the approval or denial of a particular device’s provision status based on the custom attributes sent during this process.

Solution Overview: Provisioning by Claim (with Bootstrap Cert)

This image showcases the Provisioning by Claim workflow described in this blog post.

The Provisioning by Claim workflow is showcased in the previous image (Fig. 1). When the device is powered on and has network access, the following takes place:

  1. The device connects to AWS IoT Core over a secure TLS 1.2 connection, using a bootstrapped certificate. If the device has a CSR, then that would be presented along with bootstrap certificate.
  2. The certificate has a highly restrictive policy associated, providing access only to IoT topics associated with the Fleet Provisioning process.
  3. The Fleet Provisioning service responds with the official certificate/private key payload along with a “proof of ownership” token to securely separate the transaction. This token is used in a subsequent call to activate the certificate. If a CSR was presented, then the certificate would be generated from that CSR.
  4. The device makes an MQTT request to AWS IoT Core, presenting the ownership token, name of the Fleet Provisioning template created by the account owner, and (optionally) device attributes for provisioning validation. We recommend that customers take advantage of the Lambda-based provisioning hooks to enable additional validation, such as validating the device’s serial number or MAC ID, against a pre-approved list.
  5. The Fleet Provisioning template is acted upon, and the provisioning transaction takes place and returns a result. Commonly: Validate device attributes in Lambda, activate certificate, attach production policy, and create Thing/groups (optional).
  6. Based on the sum of the provisioning transaction, a result is returned on the status of the new certificate. If successful, the bootstrap cert is deprecated/rotated for the “production” certificate. If the transaction is denied, an “access denied” error will be returned to the device.

Solution Walkthrough: Setting up the “Provisioning by Claim” workflow

In the next section, we’ll walk through the steps necessary to set up the provisioning by claim workflow described in the previous section. The instructions take place in the AWS Management Console. There are 3 main procedures for setting up the workflow:

  • Procedure-1: Create the Provisioning Template
  • Procedure-2: Define your provisioning claim (aka bootstrap certificate)
  • Procedure-3: Configure the Device Side Client Software

Once this workflow is set up, device onboarding happens automatically whenever devices are powered-on and provided network access.

Procedure 1 – Create a Provisioning Template

A provisioning template details the instructions that must be carried out when a device is to be provisioned. Provisioning templates can include things like which policy to associate with a particular certificate, what to name a Thing in the device registry, whether or not to activate the associated cert, and more. Visit the Fleet Provisioning Templates documentation to learn more.

To create your provisioning template

  • Open the AWS Management Console
  • In the navigation pane, under AWS IoT Core, select Onboard, and then select Fleet Provisioning Templates.
    • Note: The first visit may include an introduction, where you’ll navigate to Onboard Many devices/Create Templates.
  •  Select Create.

This image shows the portion of the AWS Management Console where you create your provisioning template.

  • Provide a name for your template (the template name will be referenced when calling from the device side).
  • Provide/Create a provisioning role.
    • Note: This role gives permission to AWS IoT Core to create/update resources in your AWS IoT account on your behalf.
  • [Recommended] select a Lambda function to hook-in to your provisioning transaction.
    • As mentioned in the introduction, you can define a Lambda function to process parameter payload from your device. You can take that payload and analyze it against black/white lists, internal subscription databases, or any other internal resources to approve or deny a provisioning request.
    • The attributes sent from the device in the “provisioning-templates” call can be pulled from the event [“parameters”] tag in Lambda.
    • At a minimum, your Lambda must return a Boolean “allowProvisioning” (True/False) to return the provisioning pipeline.

The following is an example of a basic provisioning hook Lambda function:

import json

provision_response = {'allowProvisioning': False}

def isBlacklisted(serial_number):
    #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"]["device_serial"]
    
    # Check serial against an isBlacklisted() function
    if not isBlacklisted(device_serial):
        provision_response["allowProvisioning"] = True
        
    return provision_response

Now, let’s finalize the template creation with “optional” settings.

This image shows the optional settings you can select when setting up your AWS IoT Fleet Provisioning template

  • Select the desired check boxes if you want your provisioned devices to be placed in the AWS IoT Registry and/or included with additional key/value attributes.
  • Select Next

Next, we select the policy that will be associated with devices that are fully provisioned. Policies manage the access your devices will have to the AWS IoT Core endpoint for things like connecting, subscribing and receiving on specified topics. Visit the AWS IoT Policy documentation for more information on how to create a policy.

To define the AWS IoT Policy

  • Create or select an existing IoT Policy to be associated to devices after they’ve been qualified to be fully provisioned.
    • For example, the following policy grants devices permission to connect to AWS IoT Core with a client ID that matches the thing name and to subscribe to and receive messages on the my/topic topic:

{"Version": "2012-10-17",
    "Statement": [
        {"Effect": "Allow",
            "Action": [
                "iot:Connect"
            ],
            "Resource": ["arn:aws:iot:REGION:123456789012:client/${iot:Connection.Thing.ThingName}"]
        },
        {"Effect": "Allow",
            "Action": [
                "iot:Subscribe"
            ],
            "Resource": [
                "arn:aws:iot:REGION:123456789012:topicfilter/my/topic"
            ]
        },
        {"Effect": "Allow",
            "Action": [
                "iot:Receive"
            ],
            "Resource": [
                "arn:aws:iot:REGION:123456789012:topic/my/topic"
            ]
        }
    ]
}

Now you have the opportunity to define additional management structures for “things” created in the registry by the template.

To define settings for the AWS IoT Thing Registry

  • Set a prefix for all things created in the registry.
    • Optional, but recommended: Apply a thing type for this template.
    • Optional: Select a group to place all things provisioned through the template.
    • Optional: Add any additional attributes you may want included in the registry.
  • Select Next

If you selected the option to send configuration information to devise that are provisioned,

  • Add attributes and default values (optional).
  • Select Create template

Procedure 2 – Define your provisioning claim (aka bootstrap certificate)

In this section, we will nominate/create the certificate which is used as the common bootstrap certificate.

This image shows where you can give certificates or users permission to provision devices in the AWS Management Console

To define your provisioning claim

  • Select a certificate formerly created by AWS IoT Core or use a certificate signed by your own certificate authority (CA).
    • If you have not yet created a certificate, Shift+click on “Generate a certificate” to walk through the following certificate creation workflow.
      • Select Create to start the wizard, and then select Create certificate to generate the certs.
      • Download the cert payload including the public and private keys, as well as the root CA.
      • Select Done (No need to Activate or Attach a policy.
      • Close the newly opened window, and refresh the page listing available certificates.
    • With the desired certificate selected, choose Attach policy to attach a restrictive policy to the certificate. As explained earlier, this restrictive policy will only allow devices possessing bootstrap certificates to establish first connection with AWS IoT Core, prove their identity, and request a fully functional identity with necessary IoT permissions.
    • Choose Enable template and then select Close.

Procedure 3 – Configure the Device Side Client Software

The final procedure will walk you through how to configure the device-side and client-side software. First, if you generated a bootstrap certificate in Procedure 2, ensure the certificate, root.ca and private key have been downloaded from the AWS IoT Console and stored on the device in a secure location.

Once you have ensured the bootstrap credentials are stored securely on board, the device must make an initial connection to AWS IoT Core. It must also define a callback to capture responses from the provisioning service.

The following example demonstrates how to connect your device to AWS IoT Core. While other AWS IoT SDK’s are supported, the sample below is shown with the AWS IoT Python SDK. A complete Python reference client can be found here: aws-iot-fleet-provisioning.

// Don't forget to define a callback method to capture return data.
// Note: In this example, on_message_callback must also be defined
// as a method in the class near the top.
self.primary_MQTTClient.onMessage = self.on_message_callback

def on_message_callback(self, message):
    # inspect message.payload

// Example standard python IoT Core connect configuration.
self.primary_MQTTClient.configureEndpoint(self.iot_endpoint, 8883)
self.primary_MQTTClient.configureCredentials("{}/{}".format(self.secure_cert_path,
       self.root_cert), "{}/{}".format(self.secure_cert_path, self.secure_key),
       "{}/{}".format(self.secure_cert_path, self.claim_cert))
 self.primary_MQTTClient.configureOfflinePublishQueueing(-1)  
 self.primary_MQTTClient.configureDrainingFrequency(2)  
 self.primary_MQTTClient.configureConnectDisconnectTimeout(10)  
 self.primary_MQTTClient.configureMQTTOperationTimeout(3)      
 self.logger.info('##### CONNECTING WITH PROVISIONING CLAIM CERT #####')
        
 self.primary_MQTTClient.connect()

Once your device is successfully connected to AWS IoT Core, your application must publish to a reserved AWS IoT provisioning topic responsible for generating the credential payload.

To make the initial publish to obtain cert payload

# Make a publish call to topic to get official certs
self.primary_MQTTClient.publish("$aws/certificates/create/json", "{}", 0)

The response will return the certificate, private key, and an ownership token and invoke the callback function. The certificate and key should be securely persisted from this response. The additional ownership token should be published to the second provisioning topic, along with the name of the intended provisioning template, and (optionally) any desired device parameters as illustrated in the following code example:

//Pack up the token, and any relevant device attributes.
register_template = {"certificateOwnershipToken": token,
    "parameters": {"serialNumber": serial, "customAttribute": someAttribute}}

#Publish payload to second provisioning topic and include desired template name.
self.primary_MQTTClient.publish(
     "$aws/provisioning-templates/{TEMPLATE_NAME}/provision/json", json.dumps(register_template), 0)

The service responds based on the result of the actions defined in the provisioning template.

Some devices are capable of generating a key pair using secure hardware-based solutions. In those cases, they must generate a certificate out of the key pair to connect with AWS IoT Core. This can be accomplished by generating a CSR and submitting that CSR to be signed by AWS IoT Core.  The initial publish to AWS IoT Core to obtain certificates would be modified as shown in the snippet below:

self.primary_MQTTClient.publish("$aws/certificates/create-from-csr/json", 
      "{ "certificateSigningRequest": "CSR_TEXT_HERE"}", 0)

Visit the AWS IoT API Reference documentation for more detail on how to create certificates from CSR.

This concludes the procedures required to set up the “Provisioning by Claim” workflow. Next, we will explain the differences when setting up AWS IoT Core Fleet Provisioning using the “Trusted User” method.

Setting up Fleet Provisioning via a “Trusted User”

Notably, there is an additional feature of fleet management that enables a “trusted user” to provision on behalf of a device. This is a common use case when provisioning a device via a mobile app user, for example, for consumer Wi-Fi devices that are shipped with a companion mobile app for the end user to configure the device using Wi-Fi. The flow is very similar to provisioning with a bootstrap certificate except that a user performs an initial call to AWS IoT Core from a trusted intermediary (like a mobile app) to obtain a bootstrap certificate. This certificate is then passed to the mobile device which then follows the same flow outlined in procedures 1-3 for bootstrapped devices. For more information, visit the AWS IoT Fleet Provisioning with a Trusted User documentation.

Conclusion

In this post, I introduced Fleet Provisioning, a new feature designed to help customers easily onboard large numbers of manufactured devices, from consumer devices to industrial equipment, to AWS IoT Core. I walked through the procedures required to use Fleet Provisioning with the Provisioning by Claim method, and explained the difference steps required to use the Provisioning by Trusted User method. A Python reference client is now available and can be used to demonstrate the entire provisioning flow when using a provisioning claim (bootstrap certificate).

For more information on AWS IoT Fleet Provisioning, visit the feature documentation or the Fleet Provisioning Forum. Finally, we have published a Fleet Provisioning demo on GitHub. I hope this post helps you easily get started using AWS IoT Core Fleet Provisioning. If you have any questions or suggestions for future blog post topics, please drop a comment below!

About the Authors

This post was written by Raleigh Murch and Alok Jha.

Raleigh Murch

Raleigh Murch is a Sr. IoT Architect for Emerging Services at Amazon Web Services. He works with customers globally across many industries focusing on solutions around IoT, computer vision and machine learning.

Alok Jha

Alok Jha is a Sr. Technical-Product Manager at Amazon Web Services. He focuses on developing AWS IoT cloud services that help customers across many industries achieve positive business outcomes.