How to use custom allocation policies
A custom allocation policy gives you more control over how devices are assigned to an IoT hub. This is accomplished by using custom code in an Azure Function to assign devices to an IoT hub. The device provisioning service calls your Azure Function code providing all relevant information about the device and the enrollment. Your function code is executed and returns the IoT hub information used to provisioning the device.
By using custom allocation policies, you define your own allocation policies when the policies provided by the Device Provisioning Service don't meet the requirements of your scenario.
For example, maybe you want to examine the certificate a device is using during provisioning and assign the device to an IoT hub based on a certificate property. Or, maybe you have information stored in a database for your devices and need to query the database to determine which IoT hub a device should be assigned to.
This article demonstrates a custom allocation policy using an Azure Function written in C#. Two new IoT hubs are created representing a Contoso Toasters Division and a Contoso Heat Pumps Division. Devices requesting provisioning must have a registration ID with one of the following suffixes to be accepted for provisioning:
- -contoso-tstrsd-007: Contoso Toasters Division
- -contoso-hpsd-088: Contoso Heat Pumps Division
The devices will be provisioned based on one of these required suffixes on the registration ID. These devices will be simulated using a provisioning sample included in the Azure IoT C SDK.
You perform the following steps in this article:
- Use the Azure CLI to create two Contoso division IoT hubs (Contoso Toasters Division and Contoso Heat Pumps Division)
- Create a new group enrollment using an Azure Function for the custom allocation policy
- Create device keys for two device simulations.
- Set up the development environment for the Azure IoT C SDK
- Simulate the devices and verify that they are provisioned according to the example code in the custom allocation policy
If you don't have an Azure subscription, create a trial account before you begin.
Prerequisites
The following prerequisites are for a Windows development environment. For Linux or macOS, see the appropriate section in Prepare your development environment in the SDK documentation.
Visual Studio 2019 with the 'Desktop development with C++' workload enabled. Visual Studio 2015 and Visual Studio 2017 are also supported.
Latest version of Git installed.
You can use the local Azure CLI.
If you prefer, install the Azure CLI to run CLI reference commands.
Local Azure CLI, see how to install the Azure CLI. If you're running on Windows or macOS, consider running Azure CLI in a Docker container. For more information, see How to run the Azure CLI in a Docker container.
Sign in to the Azure CLI by using the az login command. To finish the authentication process, follow the steps displayed in your terminal. For other sign-in options, see Sign in with the Azure CLI.
When you're prompted, install the Azure CLI extension on first use. For more information about extensions, see Use extensions with the Azure CLI.
Run az version to find the version and dependent libraries that are installed. To upgrade to the latest version, run az upgrade.
Create the provisioning service and two divisional IoT hubs
In this section, you use the Azure CLI create a provisioning service and two IoT hubs representing the Contoso Toasters Division and the Contoso Heat Pumps division.
Tip
The commands used in this article create the provisioning service and other resources in the China North location. We recommend that you create your resources in the region nearest you that supports Device Provisioning Service. You can view a list of available locations by running the command az provider show --namespace Microsoft.Devices --query "resourceTypes[?resourceType=='ProvisioningServices'].locations | [0]" --out table
or by going to the Azure Status page and searching for "Device Provisioning Service". In commands, locations can be specified either in one word or multi-word format; for example: chinanorth, chinaeast2, etc. The value is not case sensitive. If you use multi-word format to specify location, enclose the value in quotes; for example, -- location "China North"
.
Use the Azure CLI to create a resource group with the az group create command. An Azure resource group is a logical container into which Azure resources are deployed and managed.
The following example creates a resource group named contoso-us-resource-group in the chinanorth region. It is recommended that you use this group for all resources created in this article. This approach will make clean up easier after you're finished.
az group create --name contoso-us-resource-group --location chinanorth
Use the Azure CLI to create a device provisioning service (DPS) with the az iot dps create command. The provisioning service will be added to contoso-us-resource-group.
The following example creates a provisioning service named contoso-provisioning-service-1098 in the chinanorth location. You must use a unique service name. Make up your own suffix in the service name in place of 1098.
az iot dps create --name contoso-provisioning-service-1098 --resource-group contoso-us-resource-group --location chinanorth
This command may take a few minutes to complete.
Use the Azure CLI to create the Contoso Toasters Division IoT hub with the az iot hub create command. The IoT hub will be added to contoso-us-resource-group.
The following example creates an IoT hub named contoso-toasters-hub-1098 in the chinanorth location. You must use a unique hub name. Make up your own suffix in the hub name in place of 1098.
Caution
The example Azure Function code for the custom allocation policy requires the substring
-toasters-
in the hub name. Make sure to use a name containing the required toasters substring.az iot hub create --name contoso-toasters-hub-1098 --resource-group contoso-us-resource-group --location chinanorth --sku S1
This command may take a few minutes to complete.
Use the Azure CLI to create the Contoso Heat Pumps Division IoT hub with the az iot hub create command. This IoT hub will also be added to contoso-us-resource-group.
The following example creates an IoT hub named contoso-heatpumps-hub-1098 in the chinanorth location. You must use a unique hub name. Make up your own suffix in the hub name in place of 1098.
Caution
The example Azure Function code for the custom allocation policy requires the substring
-heatpumps-
in the hub name. Make sure to use a name containing the required heatpumps substring.az iot hub create --name contoso-heatpumps-hub-1098 --resource-group contoso-us-resource-group --location chinanorth --sku S1
This command may take a few minutes to complete.
The IoT hubs must be linked to the DPS resource.
Run the following two commands to get the connection strings for the hubs you just created. Replace the hub resource names with the names you chose in each command:
hubToastersConnectionString=$(az iot hub connection-string show --hub-name contoso-toasters-hub-1098 --key primary --query connectionString -o tsv) hubHeatpumpsConnectionString=$(az iot hub connection-string show --hub-name contoso-heatpumps-hub-1098 --key primary --query connectionString -o tsv)
Run the following commands to link the hubs to the DPS resource. Replace the DPS resource name with the name you chose in each command:
az iot dps linked-hub create --dps-name contoso-provisioning-service-1098 --resource-group contoso-us-resource-group --connection-string $hubToastersConnectionString --location chinanorth az iot dps linked-hub create --dps-name contoso-provisioning-service-1098 --resource-group contoso-us-resource-group --connection-string $hubHeatpumpsConnectionString --location chinanorth
Create the custom allocation function
In this section, you create an Azure function that implements your custom allocation policy. This function decides which divisional IoT hub a device should be registered to based on whether its registration ID contains the string -contoso-tstrsd-007 or -contoso-hpsd-088. It also sets the initial state of the device twin based on whether the device is a toaster or a heat pump.
Sign in to the Azure portal. From your home page, select + Create a resource.
In the Search the Marketplace search box, type "Function App". From the drop-down list select Function App, and then select Create.
On Function App create page, under the Basics tab, enter the following settings for your new function app and select Review + create:
Resource Group: Select the contoso-us-resource-group to keep all resources created in this article together.
Function App name: Enter a unique function app name. This example uses contoso-function-app-1098.
Publish: Verify that Code is selected.
Runtime Stack: Select .NET Core from the drop-down.
Version: Select 3.1 from the drop-down.
Region: Select the same region as your resource group. This example uses China North.
Note
By default, Application Insights is enabled. Application Insights is not necessary for this article, but it might help you understand and investigate any issues you encounter with the custom allocation. If you prefer, you can disable Application Insights by selecting the Monitoring tab and then selecting No for Enable Application Insights.
On the Summary page, select Create to create the function app. Deployment may take several minutes. When it completes, select Go to resource.
On the left pane of the function app Overview page, click Functions and then + Add to add a new function.
On the Add function page, click HTTP Trigger, then click the Add button.
On the next page, click Code + Test. This allows you to edit the code for the function named HttpTrigger1. The run.csx code file should be opened for editing.
Reference required NuGet packages. To create the initial device twin, the custom allocation function uses classes that are defined in two NuGet packages that must be loaded into the hosting environment. With Azure Functions, NuGet packages are referenced using a function.proj file. In this step, you save and upload a function.proj file for the required assemblies. For more information, see Using NuGet packages with Azure Functions.
Copy the following lines into your favorite editor and save the file on your computer as function.proj.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Service" Version="1.16.3" /> <PackageReference Include="Microsoft.Azure.Devices.Shared" Version="1.27.0" /> </ItemGroup> </Project>
Click the Upload button located above the code editor to upload your function.proj file. After uploading, select the file in the code editor using the drop down box to verify the contents.
Make sure run.csx for HttpTrigger1 is selected in the code editor. Replace the code for the HttpTrigger1 function with the following code and select Save:
#r "Newtonsoft.Json" using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using Microsoft.Azure.Devices.Shared; // For TwinCollection using Microsoft.Azure.Devices.Provisioning.Service; // For TwinState public static async Task<IActionResult> Run(HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); // Get request body string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); log.LogInformation("Request.Body:..."); log.LogInformation(requestBody); // Get registration ID of the device string regId = data?.deviceRuntimeContext?.registrationId; string message = "Uncaught error"; bool fail = false; ResponseObj obj = new ResponseObj(); if (regId == null) { message = "Registration ID not provided for the device."; log.LogInformation("Registration ID : NULL"); fail = true; } else { string[] hubs = data?.linkedHubs?.ToObject<string[]>(); // Must have hubs selected on the enrollment if (hubs == null) { message = "No hub group defined for the enrollment."; log.LogInformation("linkedHubs : NULL"); fail = true; } else { // This is a Contoso Toaster Model 007 if (regId.Contains("-contoso-tstrsd-007")) { //Find the "-toasters-" IoT hub configured on the enrollment foreach(string hubString in hubs) { if (hubString.Contains("-toasters-")) obj.iotHubHostName = hubString; } if (obj.iotHubHostName == null) { message = "No toasters hub found for the enrollment."; log.LogInformation(message); fail = true; } else { // Specify the initial tags for the device. TwinCollection tags = new TwinCollection(); tags["deviceType"] = "toaster"; // Specify the initial desired properties for the device. TwinCollection properties = new TwinCollection(); properties["state"] = "ready"; properties["darknessSetting"] = "medium"; // Add the initial twin state to the response. TwinState twinState = new TwinState(tags, properties); obj.initialTwin = twinState; } } // This is a Contoso Heat pump Model 008 else if (regId.Contains("-contoso-hpsd-088")) { //Find the "-heatpumps-" IoT hub configured on the enrollment foreach(string hubString in hubs) { if (hubString.Contains("-heatpumps-")) obj.iotHubHostName = hubString; } if (obj.iotHubHostName == null) { message = "No heat pumps hub found for the enrollment."; log.LogInformation(message); fail = true; } else { // Specify the initial tags for the device. TwinCollection tags = new TwinCollection(); tags["deviceType"] = "heatpump"; // Specify the initial desired properties for the device. TwinCollection properties = new TwinCollection(); properties["state"] = "on"; properties["temperatureSetting"] = "65"; // Add the initial twin state to the response. TwinState twinState = new TwinState(tags, properties); obj.initialTwin = twinState; } } // Unrecognized device. else { fail = true; message = "Unrecognized device registration."; log.LogInformation("Unknown device registration"); } } } log.LogInformation("\nResponse"); log.LogInformation((obj.iotHubHostName != null) ? JsonConvert.SerializeObject(obj) : message); return (fail) ? new BadRequestObjectResult(message) : (ActionResult)new OkObjectResult(obj); } public class ResponseObj { public string iotHubHostName {get; set;} public TwinState initialTwin {get; set;} }
Create the enrollment
In this section, you'll create a new enrollment group that uses the custom allocation policy. For simplicity, this article uses Symmetric key attestation with the enrollment. For a more secure solution, consider using X.509 certificate attestation with a chain of trust.
Still on the Azure portal, open your provisioning service.
Select Manage enrollments on the left pane, and then select the Add enrollment group button at the top of the page.
On Add Enrollment Group, enter the following information, and select the Save button.
Group name: Enter contoso-custom-allocated-devices.
Attestation Type: Select Symmetric Key.
Auto Generate Keys: This checkbox should already be checked.
Select how you want to assign devices to hubs: Select Custom (Use Azure Function).
Subscription: Select the subscription where you created your Azure Function.
Function App: Select your function app by name. contoso-function-app-1098 was used in this example.
Function: Select the HttpTrigger1 function.
After saving the enrollment, reopen it and make a note of the Primary Key. You must save the enrollment first to have the keys generated. This key will be used to generate unique device keys for simulated devices later.
Derive unique device keys
In this section, you create two unique device keys. One key will be used for a simulated toaster device. The other key will be used for a simulated heat pump device.
To generate the device key, you use the Primary Key you noted earlier to compute the HMAC-SHA256 of the device registration ID for each device and convert the result into Base64 format. For more information on creating derived device keys with enrollment groups, see the group enrollments section of Symmetric key attestation.
For the example in this article, use the following two device registration IDs and compute a device key for both devices. Both registration IDs have a valid suffix to work with the example code for the custom allocation policy:
- breakroom499-contoso-tstrsd-007
- mainbuilding167-contoso-hpsd-088
If you're using a Windows-based workstation, you can use PowerShell to generate your derived device key as shown in the following example.
Replace the value of KEY with the Primary Key you noted earlier.
$KEY='oiK77Oy7rBw8YB6IS6ukRChAw+Yq6GC61RMrPLSTiOOtdI+XDu0LmLuNm11p+qv2I+adqGUdZHm46zXAQdZoOA=='
$REG_ID1='breakroom499-contoso-tstrsd-007'
$REG_ID2='mainbuilding167-contoso-hpsd-088'
$hmacsha256 = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha256.key = [Convert]::FromBase64String($KEY)
$sig1 = $hmacsha256.ComputeHash([Text.Encoding]::ASCII.GetBytes($REG_ID1))
$sig2 = $hmacsha256.ComputeHash([Text.Encoding]::ASCII.GetBytes($REG_ID2))
$derivedkey1 = [Convert]::ToBase64String($sig1)
$derivedkey2 = [Convert]::ToBase64String($sig2)
echo "`n`n$REG_ID1 : $derivedkey1`n$REG_ID2 : $derivedkey2`n`n"
breakroom499-contoso-tstrsd-007 : JC8F96eayuQwwz+PkE7IzjH2lIAjCUnAa61tDigBnSs=
mainbuilding167-contoso-hpsd-088 : 6uejA9PfkQgmYylj8Zerp3kcbeVrGZ172YLa7VSnJzg=
The simulated devices will use the derived device keys with each registration ID to perform symmetric key attestation.
Prepare an Azure IoT C SDK development environment
In this section, you prepare the development environment used to build the Azure IoT C SDK. The SDK includes the sample code for the simulated device. This simulated device will attempt provisioning during the device's boot sequence.
This section is oriented toward a Windows-based workstation. For a Linux example, see the set-up of the VMs in Tutorial: Provision for geolatency.
Download the CMake build system.
It is important that the Visual Studio prerequisites (Visual Studio and the 'Desktop development with C++' workload) are installed on your machine, before starting the
CMake
installation. Once the prerequisites are in place, and the download is verified, install the CMake build system.Find the tag name for the latest release of the SDK.
Open a command prompt or Git Bash shell. Run the following commands to clone the latest release of the Azure IoT C SDK GitHub repository. Use the tag you found in the previous step as the value for the
-b
parameter:git clone -b <release-tag> https://github.com/Azure/azure-iot-sdk-c.git cd azure-iot-sdk-c git submodule update --init
You should expect this operation to take several minutes to complete.
Create a
cmake
subdirectory in the root directory of the git repository, and navigate to that folder. Run the following commands from theazure-iot-sdk-c
directory:mkdir cmake cd cmake
Run the following command, which builds a version of the SDK specific to your development client platform. A Visual Studio solution for the simulated device will be generated in the
cmake
directory.cmake -Dhsm_type_symm_key:BOOL=ON -Duse_prov_client:BOOL=ON ..
If
cmake
doesn't find your C++ compiler, you might get build errors while running the command. If that happens, try running the command in the Visual Studio command prompt.Once the build succeeds, the last few output lines will look similar to the following output:
$ cmake -Dhsm_type_symm_key:BOOL=ON -Duse_prov_client:BOOL=ON .. -- Building for: Visual Studio 15 2017 -- Selecting Windows SDK version 10.0.16299.0 to target Windows 10.0.17134. -- The C compiler identification is MSVC 19.12.25835.0 -- The CXX compiler identification is MSVC 19.12.25835.0 ... -- Configuring done -- Generating done -- Build files have been written to: E:/IoT Testing/azure-iot-sdk-c/cmake
Simulate the devices
In this section, you update a provisioning sample named prov_dev_client_sample located in the Azure IoT C SDK you set up previously.
This sample code simulates a device boot sequence that sends the provisioning request to your Device Provisioning Service instance. The boot sequence will cause the toaster device to be recognized and assigned to the IoT hub using the custom allocation policy.
In the Azure portal, select the Overview tab for your Device Provisioning Service and note down the ID Scope value.
In Visual Studio, open the azure_iot_sdks.sln solution file that was generated by running CMake earlier. The solution file should be in the following location:
azure-iot-sdk-c\cmake\azure_iot_sdks.sln
In Visual Studio's Solution Explorer window, navigate to the Provision_Samples folder. Expand the sample project named prov_dev_client_sample. Expand Source Files, and open prov_dev_client_sample.c.
Find the
id_scope
constant, and replace the value with your ID Scope value that you copied earlier.static const char* id_scope = "0ne00002193";
Find the definition for the
main()
function in the same file. Make sure thehsm_type
variable is set toSECURE_DEVICE_TYPE_SYMMETRIC_KEY
as shown below:SECURE_DEVICE_TYPE hsm_type; //hsm_type = SECURE_DEVICE_TYPE_TPM; //hsm_type = SECURE_DEVICE_TYPE_X509; hsm_type = SECURE_DEVICE_TYPE_SYMMETRIC_KEY;
Right-click the prov_dev_client_sample project and select Set as Startup Project.
Simulate the Contoso toaster device
To simulate the toaster device, find the call to
prov_dev_set_symmetric_key_info()
in prov_dev_client_sample.c which is commented out.// Set the symmetric key if using they auth type //prov_dev_set_symmetric_key_info("<symm_registration_id>", "<symmetric_Key>");
Uncomment the function call and replace the placeholder values (including the angle brackets) with the toaster registration ID and derived device key you generated previously. The key value JC8F96eayuQwwz+PkE7IzjH2lIAjCUnAa61tDigBnSs= shown below is only given as an example.
// Set the symmetric key if using they auth type prov_dev_set_symmetric_key_info("breakroom499-contoso-tstrsd-007", "JC8F96eayuQwwz+PkE7IzjH2lIAjCUnAa61tDigBnSs=");
Save the file.
On the Visual Studio menu, select Debug > Start without debugging to run the solution. In the prompt to rebuild the project, select Yes, to rebuild the project before running.
The following output is an example of the simulated toaster device successfully booting up and connecting to the provisioning service instance to be assigned to the toasters IoT hub by the custom allocation policy:
Provisioning API Version: 1.3.6 Registering Device Provisioning Status: PROV_DEVICE_REG_STATUS_CONNECTED Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING Registration Information received from service: contoso-toasters-hub-1098.azure-devices.cn, deviceId: breakroom499-contoso-tstrsd-007 Press enter key to exit:
Simulate the Contoso heat pump device
To simulate the heat pump device, update the call to
prov_dev_set_symmetric_key_info()
in prov_dev_client_sample.c again with the heat pump registration ID and derived device key you generated earlier. The key value 6uejA9PfkQgmYylj8Zerp3kcbeVrGZ172YLa7VSnJzg= shown below is also only given as an example.// Set the symmetric key if using they auth type prov_dev_set_symmetric_key_info("mainbuilding167-contoso-hpsd-088", "6uejA9PfkQgmYylj8Zerp3kcbeVrGZ172YLa7VSnJzg=");
Save the file.
On the Visual Studio menu, select Debug > Start without debugging to run the solution. In the prompt to rebuild the project, select Yes to rebuild the project before running.
The following output is an example of the simulated heat pump device successfully booting up and connecting to the provisioning service instance to be assigned to the Contoso heat pumps IoT hub by the custom allocation policy:
Provisioning API Version: 1.3.6 Registering Device Provisioning Status: PROV_DEVICE_REG_STATUS_CONNECTED Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING Registration Information received from service: contoso-heatpumps-hub-1098.azure-devices.cn, deviceId: mainbuilding167-contoso-hpsd-088 Press enter key to exit:
Troubleshooting custom allocation policies
The following table shows expected scenarios and the results error codes you might receive. Use this table to help troubleshoot custom allocation policy failures with your Azure Functions.
Scenario | Registration result from Provisioning Service | Provisioning SDK Results |
---|---|---|
The webhook returns 200 OK with 'iotHubHostName' set to a valid IoT hub host name | Result status: Assigned | SDK returns PROV_DEVICE_RESULT_OK along with hub information |
The webhook returns 200 OK with 'iotHubHostName' present in the response, but set to an empty string or null | Result status: Failed Error code: CustomAllocationIotHubNotSpecified (400208) |
SDK returns PROV_DEVICE_RESULT_HUB_NOT_SPECIFIED |
The webhook returns 401 Unauthorized | Result status: Failed Error code: CustomAllocationUnauthorizedAccess (400209) |
SDK returns PROV_DEVICE_RESULT_UNAUTHORIZED |
An Individual Enrollment was created to disable the device | Result status: Disabled | SDK returns PROV_DEVICE_RESULT_DISABLED |
The webhook returns error code >= 429 | DPS' orchestration will retry a number of times. The retry policy is currently: - Retry count: 10 - Initial interval: 1s - Increment: 9s |
SDK will ignore error and submit another get status message in the specified time |
The webhook returns any other status code | Result status: Failed Error code: CustomAllocationFailed (400207) |
SDK returns PROV_DEVICE_RESULT_DEV_AUTH_ERROR |
Clean up resources
If you plan to continue working with the resources created in this article, you can leave them. If you don't plan to continue using the resources, use the following steps to delete all of the resources created in this article to avoid unnecessary charges.
The steps here assume you created all resources in this article as instructed in the same resource group named contoso-us-resource-group.
Important
Deleting a resource group is irreversible. The resource group and all the resources contained in it are permanently deleted. Make sure that you don't accidentally delete the wrong resource group or resources. If you created the IoT Hub inside an existing resource group that contains resources you want to keep, only delete the IoT Hub resource itself instead of deleting the resource group.
To delete the resource group by name:
Sign in to the Azure portal and select Resource groups.
In the Filter by name... textbox, type the name of the resource group containing your resources, contoso-us-resource-group.
To the right of your resource group in the result list, select ... then Delete resource group.
You'll be asked to confirm the deletion of the resource group. Type the name of your resource group again to confirm, and then select Delete. After a few moments, the resource group and all of its contained resources are deleted.
Next steps
- To learn more Reprovisioning, see IoT Hub Device reprovisioning concepts
- To learn more Deprovisioning, see How to deprovision devices that were previously autoprovisioned