Suporte ao SDK do cliente da Internet das Coisas do Azure para servidores de token de terceiros

IoT do Azure
Hub IoT do Azure

O artigo Controlar o acesso ao Hub IoT ilustra como um serviço de token de terceiros pode ser integrado ao Hub IoT. Este artigo descreve o suporte para a autenticação de token SAS (assinatura de acesso compartilhado) em cada um dos SDKs do cliente da Internet das Coisas do Azure. Ele também descreve o que precisa ser implementado em um aplicativo de dispositivo usando o SDK correspondente para cada linguagem de programação e como usar tokens no escopo do dispositivo ou no escopo do módulo para as políticas de acesso compartilhado de DeviceConnect ou ModuleConnect.

Contexto e problema

A documentação de segurança do Hub IoT do Azure atual discute o padrão de token-servidor de terceiros para autenticação SAS com o Hub IoT por dispositivos IoT usando os SDKs do cliente de Internet das Coisas do Azure. No entanto, suposições incorretas feitas por um cliente durante uma participação empresarial recente sugerem que, sem mais esclarecimentos, você pode desenvolver uma impressão enganosa sobre o nível de suporte implementado por padrão nos SDKs do cliente de Internet das Coisas do Azure.

Este artigo discute o aprendizado dessa participação e esclarece o que precisa ser feito em cada SDK para dispositivos para alcançar a autenticação de token-servidor de terceiros. Este artigo também deve impedir que você faça suposições incorretas semelhantes sobre o suporte para o padrão de token-servidor de terceiros no SDK do cliente de Internet das Coisas do Azure.

Solução

Os SDKs do cliente de Internet das Coisas do Azure fornecem diferentes níveis de suporte para autenticação de token SAS, cada um exigindo algum código personalizado para concluir a funcionalidade de gerenciamento de tokens e autenticação.

A frequência de avaliação de tokens depende do protocolo de transporte escolhido : MQTT, AMQP ou HTTPS. A variação depende da capacidade do protocolo de dar suporte à renovação proativa de tokens e tempos limite de sessão. Somente o AMQP implementa o suporte à renovação proativa. Isso significa que os outros transportes fecharão a conexão em caso de falha de autenticação de token SAS e, em seguida, precisarão executar uma nova operação de conexão. Essa é uma operação de conectividade potencialmente cara para o cliente.

Se a autenticação SAS falhar, a implementação de transporte gerará um erro que poderá ser tratado dentro do aplicativo do dispositivo por um manipulador de eventos "Status de Conexão Alterado". A falha na implementação desse manipulador normalmente resultará na parada do aplicativo do dispositivo devido ao erro. Com a implementação correta do manipulador de eventos e da funcionalidade de renovação de token, os transportes podem tentar novamente realizar a conexão.

A figura a seguir ilustra o padrão token-servidor de terceiros:

Illustration of the third-party token-server pattern

A seguinte figura ilustra o suporte à implementação no SDK do cliente de Internet das Coisas do Azure com integração com o Mobile Net Operator:

Flowchart of implementation support in the Azure IoT client SDK with Mobile Net Operator integration

Implementações de exemplo são incluídas no repositório de Exemplos do Azure no GitHub.

Problemas e considerações

Considere os seguintes pontos ao decidir como implementar esse padrão:

  • Os SDKs do DPS (Serviço de Provisionamento de Dispositivos) no Hub IoT do Azure não dão suporte à autenticação de token SAS. A API REST do DPS do Azure suporte à autenticação de token SAS. Portanto, para usar o DPS do Azure com um serviço de token de terceiros para autenticação SAS, um aplicativo de dispositivo precisa implementar o processo de DPS do dispositivo usando a API REST do DPS do Azure.

  • Isso consiste em fazer uma operação inicial de solicitação de registro e, em seguida, sondar a API de status operacional até que o processo do DPS seja bem-sucedido ou falhe. Em caso de êxito, os detalhes de provisionamento de dispositivos podem ser obtidos solicitando-os no Registro de Runtime da API REST do DPS do Azure.

Referências:

Quando usar esse padrão

Você deve usar esse padrão sempre que quiser se autenticar em Hub IoT do Azure de dispositivos IoT usando os diversos SDKs do Cliente de Internet das Coisas do Azure. Em vez de usar os SDKs do cliente para autenticação de token SAS, use a API REST do DPS do Azure para garantir a implementação do suporte de renovação proativa para todos os mecanismos de transporte.

Exemplos

As seções a seguir oferecem exemplos que você pode usar para diferentes linguagens de programação, como Embedded C, .NET, Java e Python.

SDK do dispositivo de Hub IoT do Azure para C e SDK do dispositivo de Hub IoT do Azure para Embedded C

A abordagem a seguir pode ser utilizada em aplicativos de dispositivo criados usando o SDK C de Internet das Coisas do Azure ou o SDK Embedded C da Internet das Coisas do Azure. Nenhum SDK fornece gerenciamento de tempo de vida do token SAS, portanto, você precisará implementar uma funcionalidade de gerenciador de tempo de vida do token SAS.

Os tokens SAS podem ser usados por meio da estrutura IOTHUB_CLIENT_CONFIG definindo o membro deviceSasToken como o token e tornando o deviceKey nulo. Outros valores não utilizados, como protocolGatewayHostName, também precisam ser definidos como nulos.

IOTHUB_CLIENT_CONFIG* CONFIG = (IOTHUB_CLIENT_CONFIG*)malloc(sizeof(IOTHUB_CLIENT_CONFIG));

CONFIG->PROTOCOL = PROTOCOL;
CONFIG->DEVICEID = DEVICEID;
CONFIG->IOTHUBNAME = IOTHUBNAME;
CONFIG->IOTHUBSUFFIX = IOTHUBSUFFIX;
CONFIG->DEVICEKEY = 0;
CONFIG->DEVICESASTOKEN = TOKEN;
CONFIG->PROTOCOLGATEWAYHOSTNAME = 0;

// The created IOTHUB_CLIENT_CONFIG can then be provided to the IoTHubDeviceClient_Create function to establish a DeviceClient instance.
if ((IOTHUBCLIENTHANDLE = IoTHubDeviceClient_Create(CONFIG)) == NULL) {
    (void)printf("ERROR: IOTHUBCLIENTHANDLE IS NULL!\r\n");
}

// To capture SAS token authentication failures, a handler needs to be implemented for the IoTHubDeviceClient_SetConnectionStatusCallback.
(void)IoTHubDeviceClient_SetConnectionStatusCallback(IOTHUBCLIENTHANDLE, CONNECTION_STATUS_CALLBACK, NULL);

O connection_status_callback pode capturar a IOTHUB_CLIENT_CONNECTION_STATUS_REASON de IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN para disparar uma renovação do token SAS por meio do serviço de token de terceiros. Isso é necessário para todos os transportes para capturar problemas de conexão, mas é especificamente exigido por transportes que não dão suporte à renovação proativa de token SAS. O gerenciamento proativo de tempo de vida do token SAS pode ser implementado como uma função executada repetidamente durante o loop "operacional" dos aplicativos de dispositivo. Garantir que o tempo de vida do token seja avaliado com frequência e que a renovação de token possa ser executada proativamente quando necessário.

Resumo da implementação da autenticação de token SAS para SDKs C:

  1. Implemente um manipulador ConnectionStatusCallback para capturar o evento IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN e disparar a renovação do token.

  2. Use um IOTHUB_CLIENT_CONFIG para fornecer o token SAS do dispositivo para IoTHubDeviceClient_Create.

  3. Implemente o gerenciamento proativo de tempo de vida do token SAS como parte do loop de operação do aplicativo de dispositivo.

SDK do dispositivo de Hub IoT do Azure para .NET

O SDK do cliente de Internet das Coisas do Azure para .NET implementa o suporte para o gerenciamento de tempo de vida do token SAS por meio da classe abstrata DeviceAuthenticationWithTokenRefresh. Uma implementação concreta dessa classe, adicionando a funcionalidade de renovação de token, pode ser fornecida como o método de autenticação para um método DeviceClient.Create. As implementações de transporte renovarão automaticamente o token por meio do método de autenticação, conforme necessário. Um ConnectionStatusChangesHandler é necessário para capturar alterações de conexão e evitar que exceções sejam geradas pelos transportes.

Implementação de exemplo com base na classe DeviceAuthenticationWithTokenRefreash:

internal class StsDeviceAuthenticationWithTokenRefresh : DeviceAuthenticationWithTokenRefresh
{

    private readonly string _stsConnectUrl = "http://localhost:8080/sts/azure/token/operations?sr={0}/devices/{1}";

    private const int DEFAULTTIMETOLIVESECONDS = 1 * 60 * 60;

    private const int DEFAULTBUFFERPERCENTAGE = 15;

    public StsDeviceAuthenticationWithTokenRefresh(string deviceId, int suggestedTimeToLiveSeconds, int timeBufferPercentage) : BASE(deviceId, suggestedTimeToLiveSeconds, timeBufferPercentage)
    {
        If(String.IsNullOrWhitespace(deviceId)){
            throw new ArgumentNullException(nameof(deviceId));
        }
    }

    protected override async Task<string> SafeCreateNewToken(string iotHub, int suggestedTimeToLive)
    {
        string result;
        string url = string.Format(_stsConnectUrl, iotHub, deviceId);

        using (HttpClientHandler handler = new HttpClientHandler())
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                HttpResponseMessage response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    result = await response.Content.ReadAsStringAsync();
                }
                else
                {
                    throw new HttpRequestException($"Request failed with status code {response.StatusCode}.");
                }
            }
            catch (HttpRequestException)
            {
                result = null;
            }
        }

        return result;
    }
}

Resumo da implementação da autenticação de token SAS para SDK do dispositivo do Hub IoT do Azure para .NET:

  1. Implemente uma classe concreta com base na classe abstrata DeviceAuthenticationWithTokenRefresh, que implementa a funcionalidade de renovação de token.

  2. Implemente um ConnectionStatusChangesHandler para capturar o status da conexão de transporte e evitar exceções geradas pela implementação do transporte.

Referências:

SDK do dispositivo de Hub IoT do Azure para Java

O SDK do cliente de Internet das Coisas do Azure para Java implementa o suporte para o gerenciamento de tempo de vida do token SAS por meio da Interface SasTokenProvider. Uma classe que implementa essa interface com a funcionalidade de renovação de token SAS pode ser usada como SecurityProvider em um construtor DeviceClient. As implementações de transporte renovarão automaticamente o token por meio do provedor de segurança, conforme necessário. Um ConnectionStatusChangeCallback precisa ser registrado para capturar alterações de conexão e evitar que exceções sejam geradas pelos transportes.

Exemplo de implementação do provedor de segurança implementando a interface SasTokenProvider:

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class StsSecurityProvider implements SasTokenProvider {
    private final String hostname;
    private final String deviceId;
    private int renewalBufferSeconds;
    private long expiryTimeSeconds;
    private char[] sasToken;

    public StsSecurityProvider(String hostname, String deviceId) {
        this.hostname = hostname;
        this.deviceId = deviceId;
        this.renewalBufferSeconds = 120;
        this.expiryTimeSeconds = (System.currentTimeMillis() / 1000);
    }

    @Override
    public char[] getSasToken() {
        long currentTimeSeconds = (System.currentTimeMillis() / 1000);
        try {
            if (this.sasToken == null || this.expiryTimeSeconds + this.renewalBufferSeconds >= currentTimeSeconds) {
                this.sasToken = stsGetToken();
                assert this.sasToken != null;
                String t = String.copyValueOf(this.sasToken);
                String[] bits = t.split("SE=");
                long l = Long.parseLong(bits[1]);
                this.expiryTimeSeconds = l; // the SE= number
                this.renewalBufferSeconds = (int)(l * 0.15); // renew within 15% of expiry
            }
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
        return this.sasToken;
    }

    private char[] stsGetToken() throws IOException, InterruptedException {
        String stsUrl = String.format("http://localhost:8080/sts/azure/token/operations?sr=%s/devices/%s", this.hostname, this.deviceId);
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(stsUrl))
            .timeout(Duration.ofMinutes(2))
            .header("Content-Type", "application/json")
            .build();
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(20))
            .build();
        HttpResponse < String > response = client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            return null;
        }
        if (response.body().isEmpty()) {
            return null;
        }
        return response.body().toCharArray();
    }
}

Resumo da implementação da autenticação de token SAS para SDK do dispositivo de Hub IoT do Azure para Java:

  1. Implemente a interface SasTokenProvider em uma classe e inclua a funcionalidade de renovação de token.

  2. Implemente um manipulador ConnectionStatusChangeCallback para capturar alterações de status de conexão de transporte e evitar exceções geradas pela implementação do transporte.

Referências:

SDK do dispositivo de Hub IoT do Azure para Python

O SDK do dispositivo Hub IoT do Azure para Python implementa o suporte ao token SAS por meio de métodos no objeto IoTHubDeviceClient. Esses métodos permitem a criação de um cliente de dispositivo usando um token e a capacidade de fornecer um token atualizado após a criação do cliente do dispositivo. Eles não implementam o gerenciamento de tempo de vida do token, mas isso pode ser implementado facilmente como uma operação assíncrona.

Uma implementação de exemplo do Python 3.7 mostrando apenas a descrição geral da funcionalidade:

import asyncio
import iothub_device_client

async def main():
    # Get a SAS token you generated
    sastoken = get_new_sastoken()
    # The client object is used to interact with your Azure IoT Hub.
    device_client = iothub_device_client.create_from_sastoken(sastoken)

    # Connect the client
    await device_client.connect()

    # Define behavior for providing new SAS tokens to prevent expiry
    async def sastoken_keepalive():
        while True:
            await asyncio.sleep(new_token_interval)
            sastoken = get_new_sastoken()
            await device_client.update_sastoken(sastoken)

    # Also run the SAS token keepalive in the event loop
    keepalive_task = asyncio.create_task(sastoken_keepalive())

    # Cancel the SAS token update task
    keepalive_task.cancel()

    # Finally, shut down the client
    await device_client.shutdown()

if __name__ == "main":
    asyncio.run(main())

Resumo do SDK do dispositivo do Hub IoT do Azure para autenticação de token SAS do Python:

  1. Crie uma função de geração de token SAS.

  2. Crie um cliente de dispositivo usando IoTHubDeviceClient.create_from_sastoken.

  3. Gerencie o tempo de vida do token como uma atividade separada, fornecendo ao cliente do dispositivo um token renovado quando necessário pelo método IoTHubDeviceClient.update_sastoken.

Referências:

SDK do dispositivo de Hub IoT do Azure para Node.js/JavaScript

A Internet das Coisas do Azure para Node.js/JavaScript implementa um SharedAccessSignatureAuthenticationProvider que servirá um token SAS para o cliente do dispositivo e transportará para autenticar com o Hub IoT. Ele não implementa nenhuma funcionalidade de renovação de token. O aplicativo do dispositivo precisa gerenciar o tempo de vida do token, renovando o token conforme necessário.

Use os métodos de cliente do dispositivo fromSharedAccessSignature e updateSharedAccessSignature para iniciar uma conexão com Hub IoT e fornecer um token renovado ao SharedAccessSignatuteAuthenticationProvider, o que fará com que o provedor de autenticação emita um evento newTokenAvailable para os transportes.

Um exemplo de token SAS básico é fornecido no exemplo simple_sample_device_with_sas.js.

Resumo do SDK do dispositivo Hub IoT do Azure para Node.js/JavaScript:

  1. Implemente o gerenciamento e a renovação do tempo de vida do token SAS.

  2. Use o cliente do dispositivo fromSharedAccessSignature para construir uma instância do cliente do dispositivo.

  3. Use o cliente do dispositivo updateSharedAccessSignature para fornecer um token renovado.

Referências:

Próximas etapas