Autenticar o acesso aos recursos dos Hubs de Eventos usando assinaturas de acesso compartilhado (SAS)

A assinatura de acesso compartilhado (SAS) oferece controle granular sobre o tipo de acesso concedido aos clientes. Aqui estão alguns dos controles que você pode definir em uma SAS:

  • O intervalo durante o qual o SAS é válido, que inclui a hora de início e a hora de expiração.
  • As permissões concedidas pelo SAS. Por exemplo, uma SAS para um namespace de Hubs de Eventos pode conceder a permissão de escuta, mas não a permissão de envio.
  • Somente clientes que apresentam credenciais válidas podem enviar dados para um hub de eventos.
  • Um cliente não pode fazer-se passar por outro cliente.
  • Um cliente fraudulento pode ser impedido de enviar dados para um hub de eventos.

Este artigo aborda a autenticação do acesso aos recursos dos Hubs de Eventos usando o SAS. Para saber mais sobre como autorizar o acesso a recursos de Hubs de Eventos usando SAS, consulte este artigo.

Nota

A Microsoft recomenda que você use as credenciais do Microsoft Entra quando possível como uma prática recomendada de segurança, em vez de usar as assinaturas de acesso compartilhado, que podem ser mais facilmente comprometidas. Embora você possa continuar a usar assinaturas de acesso compartilhado (SAS) para conceder acesso refinado aos recursos dos Hubs de Eventos, o Microsoft Entra ID oferece recursos semelhantes sem a necessidade de gerenciar tokens SAS ou se preocupar em revogar um SAS comprometido.

Para obter mais informações sobre a integração do Microsoft Entra nos Hubs de Eventos do Azure, consulte Autorizar o acesso aos Hubs de Eventos usando a ID do Microsoft Entra.

Configurando para autenticação SAS

Você pode configurar uma regra SAS em um namespace de Hubs de Eventos ou uma entidade (instância de hub de eventos ou Tópico Kafka em um hub de eventos). No momento, não há suporte para a configuração de uma regra SAS em um grupo de consumidores, mas você pode usar regras configuradas em um namespace ou entidade para proteger o acesso ao grupo de consumidores.

A imagem a seguir mostra como as regras de autorização se aplicam a entidades de exemplo.

Configurar regra de autorização

Neste exemplo, o namespace de Hubs de Eventos de exemplo (ExampleNamespace) tem duas entidades: eh1 e Kafka topic1. As regras de autorização são definidas no nível da entidade e também no nível do namespace.

As regras de autorização manageRuleNS, sendRuleNS e listenRuleNS aplicam-se a eh1 e t1. As regras de autorização listenRule-eh e sendRule-eh aplicam-se apenas a eh1 e a regra de autorização sendRuleT aplica-se apenas a topic1.

Quando você usa a regra de autorização sendRuleNS, os aplicativos cliente podem enviar para eh1 e topic1. Quando a regra de autorização sendRuleT é usada, ela impõe acesso granular apenas ao topic1 e, portanto, os aplicativos cliente que usam essa regra para acesso agora não podem enviar para eh1, mas apenas para topic1.

Gerar um token de assinatura de acesso compartilhado

Qualquer cliente que tenha acesso ao nome de uma regra de autorização, nome e uma de suas chaves de assinatura pode gerar um token SAS. O token é gerado pela criação de uma cadeia de caracteres no seguinte formato:

  • se – Expiração instantânea do token. Inteiro refletindo segundos desde a época 00:00:00 UTC em 1 de janeiro de 1970 (época UNIX) quando o token expira
  • skn – Nome da regra de autorização, que é o nome da chave SAS.
  • sr – URI do recurso que está sendo acessado.
  • sig – Assinatura.

A cadeia de assinatura é o hash SHA-256 calculado sobre o URI do recurso (âmbito descrito na secção anterior) e a representação da cadeia do instantâneo de expiração do token, separado por CRLF. O cálculo do hash é semelhante ao seguinte pseudocódigo e devolve um valor hash de 256 bits/32 bytes.

SHA-256('https://<yournamespace>.servicebus.windows.net/'+'\n'+ 1438205742)

O token contém os valores não hash para que o destinatário possa recalcular o hash com os mesmos parâmetros ao verificar se o emissor está na posse de uma chave de assinatura válida.

O URI do recurso é o URI completo do recurso do Service Bus ao qual o acesso é reivindicado. Por exemplo, http://<namespace>.servicebus.windows.net/<entityPath> ou sb://<namespace>.servicebus.windows.net/<entityPath> seja, http://contoso.servicebus.windows.net/eh1.

O URI deve ser codificado em porcentagem.

A regra SAS usada para assinatura deve ser configurada na entidade especificada por esse URI ou por um de seus pais hierárquicos. Por exemplo, http://contoso.servicebus.windows.net/eh1 ou http://contoso.servicebus.windows.net no exemplo anterior.

Um token SAS é válido para todos os recursos prefixados com o <resourceURI> usado na cadeia de caracteres de assinatura.

Nota

Você gera um token de acesso para Hubs de Eventos usando a política de acesso compartilhado. Para obter mais informações, consulte Política de autorização de acesso compartilhado.

Gerando uma assinatura (token) a partir de uma política

A seção a seguir mostra a geração de um token SAS usando políticas de assinatura de acesso compartilhado,

NodeJS

function createSharedAccessToken(uri, saName, saKey) { 
  if (!uri || !saName || !saKey) { 
          throw "Missing required parameter"; 
      } 
  var encoded = encodeURIComponent(uri); 
  var now = new Date(); 
  var week = 60*60*24*7;
  var ttl = Math.round(now.getTime() / 1000) + week;
  var signature = encoded + '\n' + ttl; 
  var hash = crypto.createHmac('sha256', saKey).update(signature, 'utf8').digest('base64'); 
  return 'SharedAccessSignature sr=' + encoded + '&sig=' +  
      encodeURIComponent(hash) + '&se=' + ttl + '&skn=' + saName; 
}

Para usar um nome de política e um valor de chave para se conectar a um hub de eventos, use o EventHubProducerClient construtor que usa o AzureNamedKeyCredential parâmetro.

const producer = new EventHubProducerClient("NAMESPACE NAME.servicebus.windows.net", eventHubName, new AzureNamedKeyCredential("POLICYNAME", "KEYVALUE"));

Você precisa adicionar uma referência ao AzureNamedKeyCredential.

const { AzureNamedKeyCredential } = require("@azure/core-auth");

Para usar um token SAS que você gerou usando o código, use o EventHubProducerClient construtor que usa o AzureSASCredential parâmetro.

var token = createSharedAccessToken("https://NAMESPACENAME.servicebus.windows.net", "POLICYNAME", "KEYVALUE");
const producer = new EventHubProducerClient("NAMESPACENAME.servicebus.windows.net", eventHubName, new AzureSASCredential(token));

Você precisa adicionar uma referência ao AzureSASCredential.

const { AzureSASCredential } = require("@azure/core-auth");

JAVA

private static String GetSASToken(String resourceUri, String keyName, String key)
  {
      long epoch = System.currentTimeMillis()/1000L;
      int week = 60*60*24*7;
      String expiry = Long.toString(epoch + week);

      String sasToken = null;
      try {
          String stringToSign = URLEncoder.encode(resourceUri, "UTF-8") + "\n" + expiry;
          String signature = getHMAC256(key, stringToSign);
          sasToken = "SharedAccessSignature sr=" + URLEncoder.encode(resourceUri, "UTF-8") +"&sig=" +
                  URLEncoder.encode(signature, "UTF-8") + "&se=" + expiry + "&skn=" + keyName;
      } catch (UnsupportedEncodingException e) {

          e.printStackTrace();
      }

      return sasToken;
  }


public static String getHMAC256(String key, String input) {
    Mac sha256_HMAC = null;
    String hash = null;
    try {
        sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        Encoder encoder = Base64.getEncoder();

        hash = new String(encoder.encode(sha256_HMAC.doFinal(input.getBytes("UTF-8"))));

    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
   } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return hash;
}

PHP

function generateSasToken($uri, $sasKeyName, $sasKeyValue) 
{ 
    $targetUri = strtolower(rawurlencode(strtolower($uri))); 
    $expires = time(); 	
    $expiresInMins = 60; 
    $week = 60*60*24*7;
    $expires = $expires + $week; 
    $toSign = $targetUri . "\n" . $expires; 
    $signature = rawurlencode(base64_encode(hash_hmac('sha256', 			
     $toSign, $sasKeyValue, TRUE))); 
    
    $token = "SharedAccessSignature sr=" . $targetUri . "&sig=" . $signature . "&se=" . $expires . 		"&skn=" . $sasKeyName; 
    return $token; 
}

C#

private static string createToken(string resourceUri, string keyName, string key)
{
    TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
    var week = 60 * 60 * 24 * 7;
    var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
    string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
    using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
    {
        var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
        var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
        return sasToken;
    }
}

PowerShell

[Reflection.Assembly]::LoadWithPartialName("System.Web")| out-null
$URI="myNamespace.servicebus.windows.net/myEventHub/"
$Access_Policy_Name="RootManageSharedAccessKey"
$Access_Policy_Key="myPrimaryKey"
#Token expires now+300
$Expires=([DateTimeOffset]::Now.ToUnixTimeSeconds())+300
$SignatureString=[System.Web.HttpUtility]::UrlEncode($URI)+ "`n" + [string]$Expires
$HMAC = New-Object System.Security.Cryptography.HMACSHA256
$HMAC.key = [Text.Encoding]::ASCII.GetBytes($Access_Policy_Key)
$Signature = $HMAC.ComputeHash([Text.Encoding]::ASCII.GetBytes($SignatureString))
$Signature = [Convert]::ToBase64String($Signature)
$SASToken = "SharedAccessSignature sr=" + [System.Web.HttpUtility]::UrlEncode($URI) + "&sig=" + [System.Web.HttpUtility]::UrlEncode($Signature) + "&se=" + $Expires + "&skn=" + $Access_Policy_Name
$SASToken

BASH

get_sas_token() {
    local EVENTHUB_URI='EVENTHUBURI'
    local SHARED_ACCESS_KEY_NAME='SHAREDACCESSKEYNAME'
    local SHARED_ACCESS_KEY='SHAREDACCESSKEYVALUE'
    local EXPIRY=${EXPIRY:=$((60 * 60 * 24))} # Default token expiry is 1 day

    local ENCODED_URI=$(echo -n $EVENTHUB_URI | jq -s -R -r @uri)
    local TTL=$(($(date +%s) + $EXPIRY))
    local UTF8_SIGNATURE=$(printf "%s\n%s" $ENCODED_URI $TTL | iconv -t utf8)

    local HASH=$(echo -n "$UTF8_SIGNATURE" | openssl sha256 -hmac $SHARED_ACCESS_KEY -binary | base64)
    local ENCODED_HASH=$(echo -n $HASH | jq -s -R -r @uri)

    echo -n "SharedAccessSignature sr=$ENCODED_URI&sig=$ENCODED_HASH&se=$TTL&skn=$SHARED_ACCESS_KEY_NAME"
}

Autenticando editores de Hubs de Eventos com SAS

Um editor de eventos define um ponto de extremidade virtual para um hub de eventos. O editor só pode ser usado para enviar mensagens para um hub de eventos e não receber mensagens.

Normalmente, um hub de eventos emprega um editor por cliente. Todas as mensagens enviadas para qualquer um dos editores de um hub de eventos são enfileiradas dentro desse hub de eventos. Os editores permitem um controle de acesso refinado.

A cada cliente de Hubs de Eventos é atribuído um token exclusivo, que é carregado para o cliente. Os tokens são produzidos de tal forma que cada token único concede acesso a diferentes editores exclusivos. Um cliente que possui um token só pode enviar para um editor e nenhum outro editor. Se vários clientes compartilharem o mesmo token, cada um deles compartilhará o editor.

Todos os tokens são atribuídos com chaves SAS. Normalmente, todos os tokens são assinados com a mesma chave. Os clientes não estão cientes da chave, o que impede que os clientes fabriquem tokens. Os clientes operam com os mesmos tokens até que eles expirem.

Por exemplo, para definir regras de autorização com escopo reduzido para apenas envio/publicação em Hubs de Eventos, você precisa definir uma regra de autorização de envio. Isso pode ser feito em um nível de namespace ou dar um escopo mais granular a uma entidade específica (instância de hubs de eventos ou um tópico). Um cliente ou um aplicativo com escopo com esse acesso granular é chamado de editor de Hubs de Eventos. Para o fazer, siga estes passos:

  1. Crie uma chave SAS na entidade que você deseja publicar para atribuir o escopo de envio a ela. Para obter mais informações, consulte Políticas de autorização de acesso compartilhado.

  2. Gere um token SAS com um tempo de expiração para um editor específico usando a chave gerada na etapa 1. Para obter o código de exemplo, consulte Gerando uma assinatura (token) de uma política.

  3. Forneça o token ao cliente do editor, que só pode ser enviado para a entidade e para o editor ao qual o token concede acesso.

    Quando o token expira, o cliente perde seu acesso para enviar/publicar para a entidade.

Nota

Embora não seja recomendado, é possível equipar dispositivos com tokens que concedem acesso a um hub de eventos ou namespace. Qualquer dispositivo que contenha esse token pode enviar mensagens diretamente para esse hub de eventos. Além disso, o dispositivo não pode ser impedido de enviar para esse hub de eventos.

É sempre recomendável fornecer escopos específicos e granulares.

Importante

Depois que os tokens forem criados, cada cliente será provisionado com seu próprio token exclusivo.

Quando o cliente envia dados para um hub de eventos, ele marca sua solicitação com o token. Para impedir que um invasor espione e roube o token, a comunicação entre o cliente e o hub de eventos deve ocorrer por um canal criptografado.

Se um token for roubado por um invasor, o invasor pode se passar pelo cliente cujo token foi roubado. Bloquear um editor, torna esse cliente inutilizável até que ele receba um novo token que usa um editor diferente.

Autenticando consumidores de Hubs de Eventos com SAS

Para autenticar aplicativos back-end que consomem dos dados gerados pelos produtores de Hubs de Eventos, a autenticação de token de Hubs de Eventos exige que seus clientes tenham os direitos de gerenciamento ou os privilégios de escuta atribuídos ao namespace de Hubs de Eventos ou à instância ou tópico do hub de eventos. Os dados são consumidos de Hubs de Eventos usando grupos de consumidores. Embora a política SAS ofereça um escopo granular, esse escopo é definido apenas no nível da entidade e não no nível do consumidor. Isso significa que os privilégios definidos no nível do namespace ou da instância do hub de eventos ou no nível do tópico serão aplicados aos grupos de consumidores dessa entidade.

Desativando a autenticação de chave local/SAS

Para determinados requisitos de segurança organizacional, você deseja desabilitar completamente a autenticação de chave local/SAS e confiar na autenticação baseada em ID do Microsoft Entra, que é a maneira recomendada de se conectar aos Hubs de Eventos do Azure. Você pode desabilitar a autenticação de chave local/SAS no nível de namespace dos Hubs de Eventos usando o portal do Azure ou o modelo do Azure Resource Manager.

Desativando a autenticação de chave Local/SAS através do portal

Você pode desabilitar a autenticação de chave local/SAS para um determinado namespace de Hubs de Eventos usando o portal do Azure.

Conforme mostrado na imagem a seguir, na seção de visão geral do namespace, selecione Autenticação Local.

Visão geral do namespace para desabilitar a autenticação local

E, em seguida, selecione a opção Desativado e selecione Ok , conforme mostrado na imagem a seguir. Desativando a autenticação local

Desativando a autenticação de chave local/SAS usando um modelo

Você pode desabilitar a autenticação local para um determinado namespace de Hubs de Eventos definindo disableLocalAuth a propriedade como true mostrado no seguinte modelo do Azure Resource Manager (Modelo ARM).

"resources":[
      {
         "apiVersion":"[variables('ehVersion')]",
         "name":"[parameters('eventHubNamespaceName')]",
         "type":"Microsoft.EventHub/Namespaces",
         "location":"[variables('location')]",
         "sku":{
            "name":"Standard",
            "tier":"Standard"
         },
         "resources": [
    {
      "apiVersion": "2017-04-01",
      "name": "[parameters('eventHubNamespaceName')]",
      "type": "Microsoft.EventHub/Namespaces",
      "location": "[resourceGroup().location]",
      "sku": {
        "name": "Standard"
      },
      "properties": {
        "isAutoInflateEnabled": "true",
        "maximumThroughputUnits": "7", 
        "disableLocalAuth": false
      },
      "resources": [
        {
          "apiVersion": "2017-04-01",
          "name": "[parameters('eventHubName')]",
          "type": "EventHubs",
          "dependsOn": [
            "[concat('Microsoft.EventHub/namespaces/', parameters('eventHubNamespaceName'))]"
          ],
          "properties": {
            "messageRetentionInDays": "[parameters('messageRetentionInDays')]",
            "partitionCount": "[parameters('partitionCount')]"
          }

        }
      ]
    }
  ]

Exemplos

  • Consulte o exemplo .NET #6 neste local do GitHub para saber como publicar eventos em um hub de eventos usando credenciais de acesso compartilhado ou a identidade de credenciais padrão do Azure.
  • Consulte o exemplo .NET #5 neste local do GitHub para saber como consumir ou processar eventos usando credenciais de acesso compartilhado ou a identidade de credenciais padrão do Azure.

Próximos passos

Consulte os seguintes artigos:

Veja os seguintes artigos relacionados: