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
1
2
3
4
5
6
#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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 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
	}
})

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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();

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//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"
            }
        ]
    }
}