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

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

  • O intervalo no qual a SAS é válida, que inclui a hora de início e a hora de expiração.
  • As permissões concedidas pelas SAS. Por exemplo, uma SAS para um namespace dos 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 representar outro cliente.
  • Um cliente invasor pode ser impedido de enviar dados para um hub de eventos.

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

Observação

A Microsoft recomenda que você use as credenciais do Microsoft Entra, quando possível, como uma melhor prática de segurança, em vez de usar as assinaturas de acesso compartilhado, que podem ser comprometidas com mais facilidade. Embora você possa continuar usando SAS (assinaturas de acesso compartilhado) para permitir acesso refinado aos seus recursos nos Hubs de Eventos, o Microsoft Entra ID oferece funcionalidades semelhantes sem a necessidade de gerenciar tokens SAS ou se preocupar com a revogação de uma SAS comprometida.

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

Como configurar para autenticação SAS

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

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

Configurar uma regra de autorização

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

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

Ao usar a regra de autorização sendRuleNS, os aplicativos cliente podem enviar tanto para eh1 quanto topic1. Quando a regra de autorização sendRuleT é usada, ela impõe o acesso granular somente para topic1 e, portanto, os aplicativos cliente que usam essa regra para acesso não podem então 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 e a uma de suas chaves de assinatura pode gerar um token SAS. O token é gerado ao criar uma cadeia de caracteres no seguinte formato:

  • se – instante da expiração do token. Número inteiro que reflete os 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 sendo acessado.
  • sig – assinatura.

A cadeia de caracteres de assinatura é o hash SHA-256 calculado no URI do recurso (escopo conforme descrito na seção anterior) e a representação de cadeia de caracteres do instante de expiração do token, separados por CRLF. O cálculo de hash é semelhante ao seguinte pseudocódigo e retorna um valor de 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, verificando se o emissor possui uma chave de assinatura válida.

O URI do recurso é o URI completo do recurso do Barramento de Serviço ao qual o acesso é solicitado. Por exemplo, http://<namespace>.servicebus.windows.net/<entityPath> ou sb://<namespace>.servicebus.windows.net/<entityPath>, ou seja, http://contoso.servicebus.windows.net/eh1.

O URI deve ser codificado por percentual.

A regra de SAS usada para assinatura precisa 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.

Observação

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

Como gerar uma assinatura (token) por meio 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 a fim de se conectar a um hub de eventos, use o construtor EventHubProducerClient que aceita o parâmetro AzureNamedKeyCredential.

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

Será necessário adicionar uma referência a AzureNamedKeyCredential.

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

Para usar um token SAS gerado usando o código, use o construtor EventHubProducerClient que aceita o parâmetro AzureSASCredential.

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

Será necessário adicionar uma referência a 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"
}

Como autenticar 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 a um hub de eventos, não para receber mensagens.

Normalmente, um hub de eventos emprega um editor por cliente. Todas as mensagens enviadas a um dos publicadores de um hub de eventos são enfileiradas nesse hub de eventos. Os editores habilitam o controle de acesso detalhado.

Cada cliente dos hubs de eventos recebe um token exclusivo que é carregado no cliente. Os tokens são produzidos de modo que cada token exclusivo permite acesso a um editor exclusivo diferente. Um cliente que contém um token pode enviar apenas para um editor específico e para nenhum outro. Se vários clientes compartilharem o mesmo token, cada um deles compartilhará o editor.

Chaves SAS são atribuídas a todos os tokens. Normalmente, todos os tokens são assinados com a mesma chave. Os clientes não reconhecem a chave, o que os impede de fabricar tokens. Os clientes operam nos mesmos tokens até expirarem.

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

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

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

  3. Forneça o token para o cliente do editor, que só pode enviar para a entidade e para o editor aos quais o token permite acesso.

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

Observação

Embora não seja recomendado, é possível equipar os dispositivos com tokens que permitem acesso a um hub de eventos ou a um 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 incluído na lista de bloqueados para ser impedido de enviar para esse hub de eventos.

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

Importante

Depois que os tokens são criados, cada cliente é configurado com seu próprio token exclusivo.

Quando o cliente envia dados a um hub de eventos, ele marca a respectiva solicitação com o token. Para evitar que um invasor intercepte e roube o token, a comunicação entre o cliente e o hub de eventos deve ocorrer em um canal criptografado.

Se um token for roubado por um invasor, este poderá representar o cliente cujo token foi roubado. Colocar um cliente na lista de bloqueio o inutilizará até ele receber um novo token que use um outro editor.

Como autenticar consumidores de Hubs de Eventos com SAS

Para autenticar aplicativos de back-end que consomem dos dados gerados por produtores de Hubs de Eventos, a autenticação de token dos Hubs de Eventos exige que os direitos de gerenciar ou os privilégios de escutar estejam atribuídos ao namespace dos Hubs de Eventos ou ao tópico ou instância dos Hubs de Eventos dos respectivos clientes. Os dados são consumidos dos Hubs de Eventos usando grupos de consumidores. Embora a política de SAS forneça o escopo granular, esse escopo é definido somente 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 na instância ou no nível do tópico do hub de eventos serão aplicados aos grupos de consumidores dessa entidade.

Desabilitar a autenticação de chave local/SAS

Para alguns requisitos de segurança organizacional, talvez você queira desabilitar por completo a autenticação de chave local/SAS e depender da autenticação baseada no Microsoft Entra ID, 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 modelo do Azure Resource Manager.

Desabilitando a autenticação de chave local/SAS por meio do portal

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

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

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

Em seguida, selecione a opção Desabilitada e selecione Ok, conforme mostrado na imagem a seguir. Desabilitando a autenticação local

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

Você pode desabilitar a autenticação local para um determinado namespace dos Hubs de Eventos definindo a propriedade disableLocalAuth como true, conforme mostrado no modelo de Azure Resource Manager a seguir (Modelo do 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

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

Próximas etapas

Veja os artigos a seguir:

Confira os seguintes artigos relacionados: