Tuesday, March 15, 2022

Geographically Provisioning IoT Devices on Azure

A major selling point of IoT is the ability to connect existing and new devices from almost anywhere. However, implementing such a promise in real life can be challenging. Connecting devices to a cloud-based solution can cause privacy and data sovereignty issues if they are allowed to connect and send data outside of their approved geographies as well as performance issues if they connect to resources in a geography that is significantly farther away.

The goal of this article is to articulate how to resolve this challenge using Azure IoT Hub, Device Provisioning Service (DPS), Functions and Maps. Using these Azure services, you will learn how to control an IoT device’s provisioning based on where they are connecting from.

 

Solution Overview

 

Global IoT Arch Pic.png

 


This solution is based on a strictly PaaS/Serverless architecture and uses the process below to control device provisioning:

  1. A device sends a provisioning request to an instance of DPS with a payload that contains its location data.
  2. The DPS instance forwards the request to a custom allocation policy, which is implemented as an Azure Function.
  3. The Azure Function uses the device’s location data, this can either be the device’s public IPv4 or IPv6 address or GPS coordinates, and the Azure Maps REST APIs to determine where it is located.
    1. Azure Maps offers multiple APIs that we could use to determine a device’s location, but which API you use will determine on the location data available. For this blog, we will focus on using the Azure Maps Geolocation - Get IP To Location API which will require us to send either the device’s public IPv4 or IPv6 address and return a country code based on that IP address.
  4. Once the Azure Function has the device’s country code, it will determine which IoT Hub, out of the DPS instance’s linked IoT Hubs, that device should be registered with.
    1. A sample snippet of this Azure Function’s code can be found in the Resources section below.
    2. A Geography Stamp is a logical grouping of Azure resources, such as IoT Hub, in one location/region. This concept is similar to Deployment Stamps and can be used to group not only your IoT devices by their location but your Azure resources as well.
    3. Although this blog only discusses returning an IoT Hub hostname from the Azure Function, you can return an HTTP error instead if you want to prevent the registration request and cause the provisioning to fail. This is mainly for scenarios where you want to stop devices in an unapproved geographic location from provisioning/registering.
  5. If the registration succeeds, the DPS instance will return the device’s IoT Hub connection settings and it can then establish a connection.

 

Conclusion

 

In this post we learned how to control where an IoT device can provision itself based on the country they are connecting from using Azure’s PaaS/serverless platform. With this solution, you can be confident that your devices are only connecting to their approved geographic locations.

Let us know what you think by commenting below.

 

Resources

 

Azure IoT Hub Device Provisioning Service | Overview

Azure IoT Hub Device Provisioning Service | Custom Allocation Policy

Azure Functions | Overview

Azure Maps | Overview

Azure Maps | Geolocation - Get IP To Location

Azure Maps | Search - Get Search Address Reverse

Azure IoT Hub | Overview

Azure IoT Hub | Deployment Stamps

 

#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);

    var client = new HttpClient();
    const string subscriptionKey = "ReplaceMeWithAzureMapsSubKey";
    string ipAddress = data?.deviceRuntimeContext["payload"]["IpV4"];

    log.LogInformation($"IP Address: {ipAddress}");

    string gUri = $"https://atlas.microsoft.com/geolocation/ip/json?subscription-key={subscriptionKey}&api-version=1.0&ip={ipAddress}";

    HttpResponseMessage response = await client.GetAsync(gUri);
    string responseBody = await response.Content.ReadAsStringAsync();
    dynamic locationInfo = JsonConvert.DeserializeObject(responseBody);

    log.LogInformation($"Response: {responseBody}");

    // Get registration ID of the device
    string regId = data?.deviceRuntimeContext?.registrationId;

    // Get country code
    string countryCode = locationInfo["countryRegion"]["isoCode"]?.ToString();
    log.LogInformation($"Country Code: {countryCode}");

    string message = "Uncaught error";
    bool fail = false;
    ResponseObj obj = new ResponseObj();

    string[] hubs = data?.linkedHubs.ToObject<string[]>();

    obj.iotHubHostName = hubs.FirstOrDefault();

    if (regId == null)
    {
        message = "Registration ID not provided for the device.";
        log.LogInformation("Registration ID : NULL");
        fail = true;
    }
    else if (countryCode == null)
    {
        message = "IP address of device is not correct";
        log.LogInformation($"Country Code: {countryCode}");
        fail = true;
    }

    

    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;}
}
Posted at https://sl.advdat.com/3I823v7https://sl.advdat.com/3I823v7