IoT Edge: Retrieve DeviceTwin for TPM enrolled devices in a Edge Module

If you want to retrieve desired properties or report any change to a Device Twin and you have a Edge device auto-provisioned with TPM, you will need to properly instantiate a DeviceClient: this blog post is for you!

To date (12 february 2019) there is not a documented way to create, in a IoT Edge module, a DeviceClient to access twin properties.

If you have a Edge device DPS enrolled with TPM attestation, these are the steps to securely access your TPM and properly create a DeviceClient:

  1. Create a group and a user on host OS. Then give permissions to access the TPM
  2. Modify the Dockerfile to run Edge Module as the newly created user
  3. Implement the module code to instantiate a DeviceClient using TPM
  4. Specify proper DeviceMapping in createOptions section of your deployment.json
  5. only for ARM32 based device with a C# Edge Module: Rebuild TSS.NET and properly reference it in your custom Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj

1. Create a group and a user on host OS. Then give permissions to access the TPM

If you followed this doc in order to give IoT Edge access to the TPM, you may have given TPM access to the iotedge group.
Unfortunately this group could have a different gid across all your installations, making it very hard to manage the match between the “host  user” and “container user”.

That’s why, I suggest you to:

  • manually create a group (tpmusers in the example)
  • add the iotedge user to the newly created group (the -a flag is crucial to keep the existing groups linked to iotedge user)
  • create a user with a fixed uid and add it to the previously created group
#Create the tpmusers group
sudo groupadd -r -g 802 tpmusers
#Add iotedge user to tpmusers group without removing him from other groups
sudo usermod -aG tpmusers iotedge
#Add tpmuser1 whose uid will be mapped with the one in the IoT Edge Module
sudo useradd -r -u 800 -g 802 -ms /bin/bash tpmuser1

The next step, in order to give tpmusers the access to the TPM, is to modify the group in the previously created tpmaccess.rules file

2. Modify the Dockerfile to run Edge Module as the newly created user

Everything is ready, host OS side, for your module to run command and access the tpm as tpmuser1 user.
We need now to create a user inside the container, matching uid and gid of the ones created in the previous step:

FROM microsoft/dotnet:2.1-sdk AS build-env
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

FROM microsoft/dotnet:2.1-runtime-stretch-slim
WORKDIR /app
COPY --from=build-env /app/out ./

RUN groupadd -r -g 802 tpmusers
RUN useradd -r -u 800 -g 802 -ms /bin/bash tpmuser1
USER tpmuser1

ENTRYPOINT ["dotnet", "your module name.dll"]

3. Implement the module code to instantiate a DeviceClient using TPM

If you are developing a Node.js module, your code will contain something like this:

// include some required modules
var DeviceClient = require('azure-iot-device').Client;
var tpmSecurity = require('azure-iot-security-tpm');
var Transport = require('azure-iot-device-mqtt').Mqtt;

// instantiate a DeviceClient from TPM
var tpm0 = new tpmSecurity.TpmSecurityClient(process.env.IOTEDGE_DEVICEID);
var tpmAuthenticationProvider = tpmSecurity.TpmAuthenticationProvider.fromTpmSecurityClient(process.env.IOTEDGE_DEVICEID, process.env.IOTEDGE_IOTHUBHOSTNAME, tpm0);
var deviceClient = DeviceClient.fromAuthenticationProvider(tpmAuthenticationProvider, Transport);

// somewhere in your module code you can retrieve the twin as usual
// https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-node-node-twin-getstarted
deviceClient.getTwin(function(err,results){
	if(err){
		console.err(err);
	}else{
		console.log('IoT Hub DeviceClient got this twin');
		// do whatever you want with your twin
	}
})
Node.js module sample

Else if you are developing a C# module, your code will probably look like this:

// iotHubHostName can be retrieved from environment variable IOTEDGE_IOTHUBHOSTNAME
// deviceId can be retrieved from environment variable IOTEDGE_DEVICEID
// if you made no changes, registrationId is the same as deviceId

using System;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Client.Transport.Mqtt;
using Microsoft.Azure.Devices.Provisioning.Security;

SecurityProviderTpmHsm secProvTpm = new SecurityProviderTpmHsm(registrationId);
DeviceAuthenticationWithTpm authTpm = new DeviceAuthenticationWithTpm(deviceId, secProvTpm);
DeviceClient Client = DeviceClient.Create(iotHubHostName, authTpm, TransportType.Mqtt);
var twin = await Client.GetTwinAsync();
C# module sample

4. Specify proper DeviceMapping in createOptions section of your deployment.json

The IoT Hub Client SDK will try to access /dev/tpm0 in order to instantiate a DeviceClient.

You need to bind your TPM device inside the container and, in order to achieve this goal, you can modify the createOptions of your IoT Edge Module like this:

//properly set PathOnHost if your tpm is not on tpm0 (can be tpmrm0 if you are using a VM with a software TPM emulated by Hyper-V)

"createOptions": {
    "HostConfig": {
        "Devices": [
            {
                "PathOnHost": "/dev/tpm0",
                "PathInContainer": "/dev/tpm0",
                "CgroupPermissions": "rwm"
            }
        ]
    }
}

5. only for ARM32 based device with a C# Edge Module: Rebuild TSS.NET and properly reference it in your custom Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj

Microsoft.Azure.Devices.Provisioning.Security.Tpm is the NuGet package responsible of accessing the TPM.
To date, this is relying on Microsoft.TSS NuGet package (v.2.0.1).

Microsoft.TSS has, in it, only binaries compiled for linux-x64. For this reason, when doing “dotnet publish -c Release -o out -r linux-arm” in Dockerfile.arm32v7, it is possible that .NET Standard binary is picked up for your ARM32.

If that happens, when trying to access the TPM, you will receive a “System.DllNotFoundException” complaining about missing ‘bcrypt.dll’. (.NET Standard library was compiled with different defines: see here the TSS_USE_BCRYPT and its usage in the code)

Some Microsoft colleagues were alerted of this issue, to fix it as soon as possible.

In the meanwhile, in order to make it work:

  1. Locally clone TSS.MSR and azure-iot-sdk-csharp
  2. in /TSS.NET/TSS.Net/TSS.Net.csproj change this:
    <PropertyGroup Condition=" '$(RuntimeIdentifier)' == 'linux-x64' Or '$(OS)' == 'Unix'  Or '$(OS)' == 'Linux'">

    to this:

    <PropertyGroup Condition=" '$(RuntimeIdentifier)' == 'linux-x64' Or '$(RuntimeIdentifier)' == 'linux-arm' Or '$(OS)' == 'Unix'  Or '$(OS)' == 'Linux'">

     

  3. Reference the TSS.Net.csproj in Microsoft.Azure.Devices.Provisioning.Security.Tpm
  4. Manually reference Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj in your project (remember to remove the NuGet package reference)
  5. Your Dockerfile has to “dotnet publish -c Release -o out -r linux-arm
  6. Optional: if you do not want to rebuild dependencies every time, you can setup a private NuGet Feed in Azure DevOps where to store the packages and then reference your own Microsoft.Azure.Devices.Provisioning.Security.Tpm in edge module .csproj. Furthermore when doing this, remember to copy NuGet.Config before doing a dotnet restore in Dockerfile
Spread this article

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.