Padrão Valet Key

Azure
Armazenamento do Azure

Use um token que fornece aos clientes acesso direto e restrito a um recurso específico, para descarregar a transferência de dados do aplicativo. Isso é particularmente útil em aplicativos que utilizam filas ou sistemas de armazenamento hospedados na nuvem e podem minimizar custos e maximizar a escalabilidade e o desempenho.

Contexto e problema

Os programas cliente e navegadores da Web geralmente precisam ler e gravar arquivos ou fluxos de dados para e do armazenamento de um aplicativo. Normalmente, o aplicativo irá tratar o movimento dos dados, seja buscando-o do armazenamento e transmitido-o por streaming para o cliente, ou lendo o fluxo carregado do cliente e armazenando-o no armazenamento de dados. No entanto, essa abordagem absorve recursos valiosos, como computação, memória e largura de banda.

Os repositórios de dados têm a capacidade de tratar o upload e download de dados diretamente, sem exigir que o aplicativo execute qualquer processamento para mover esses dados. Mas, isso normalmente exige que o cliente tenha acesso às credenciais de segurança do repositório. Essa pode ser uma técnica útil para minimizar os custos de transferência de dados e os requisitos para escalar horizontalmente o aplicativo e maximizar o desempenho. Isso significa, porém, que o aplicativo não é mais capaz de gerenciar a segurança dos dados. Depois que o cliente tiver uma conexão com o repositório de dados para acesso direto, o aplicativo não poderá agir como o gatekeeper. Pois já não estará mais no controle do processo e não poderá impedir de fazer uploads ou downloads subsequentes do repositório de dados.

Essa não é uma abordagem realista em sistemas distribuídos que precisam servir clientes não confiáveis. Em vez disso, os aplicativos devem poder controlar de forma segura o acesso aos dados de forma granular, mas ainda reduzir a carga no servidor configurando essa conexão e, em seguida, permitindo que o cliente se comunique diretamente com o repositório de dados para executar as operações de leitura ou gravação necessárias .

Solução

É necessário resolver o problema de controle de acesso a um repositório de dados, de maneira que o repositório não possa gerenciar a autenticação e autorização de clientes. Uma solução típica é restringir o acesso à conexão pública do repositório de dados e fornecer ao cliente uma chave ou token que possa ser validado pelo repositório de dados.

Essa chave ou token geralmente é referido como uma valet key. Ele fornece acesso com limite de tempo a recursos específicos e permite apenas operações pré-definidas, como leitura e gravação para armazenamento ou filas, ou fazer o upload ou o download em um navegador da Web. Os aplicativos podem criar e emitir keys valet para dispositivos de cliente e navegadores da Web de forma rápida e fácil, permitindo que os clientes executem as operações necessárias sem exigir que o aplicativo trate diretamente a transferência de dados. Isso remove a sobrecarga de processamento e o impacto no desempenho e escalabilidade, a partir do aplicativo e do servidor.

O cliente usa esse token para acessar um recurso específico no repositório de dados por apenas um período específico e com restrições específicas nas permissões de acesso, conforme mostrado na figura. Após o período especificado, a chave torna-se inválida e não permitirá o acesso ao recurso.

Figura 1 - Visão geral do padrão

Também é possível configurar uma chave que tenha outras dependências, como o escopo dos dados. Por exemplo, dependendo dos recursos do repositório de dados, a chave pode especificar uma tabela completa em um repositório de dados ou apenas as linhas específicas em uma tabela. Nos sistemas de armazenamento em nuvem, a chave pode especificar um contêiner ou apenas um item específico dentro de um contêiner.

A chave também pode ser invalidada pelo aplicativo. Essa é uma abordagem útil se o cliente notificar o servidor que a operação de transferência de dados está completa. O servidor poderá invalidar essa chave para evitar mais acesso.

Usar esse padrão pode simplificar o gerenciamento de acesso aos recursos, porque não há necessidade de criar e autenticar um usuário, conceder permissões e, posteriormente, remover o usuário novamente. Isso também facilita limitar a localização, a permissão e do período de validade, simplesmente gerando uma chave no runtime. Os fatores importantes são para limitar o período de validade e especialmente a localização do recurso, tão rigorosamente o quanto possível para que o destinatário possa usá-la apenas para o propósito pretendido.

Problemas e considerações

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

Gerenciar o status de validade e o período da chave. Se vazada ou comprometida, a chave efetivamente desbloqueará o item de destino e o disponibilizará para uso malicioso durante o período de validade. Uma chave geralmente pode ser revogada ou desabilitada, dependendo de como foi emitida. As políticas do servidor podem ser alteradas ou a chave do servidor, com a qual foi assinada, pode ser invalidada. Especifique um período de validade curto para minimizar o risco de permitir que operações não autorizadas ocorram contra o repositório de dados. No entanto, se o período de validade for muito curto, o cliente não poderá concluir a operação antes que a chave expire. Permita que os usuários autorizados renovem a chave antes do período de validade expirar, caso sejam necessários múltiplos acessos ao recurso protegido.

Controle o nível de acesso que a chave fornecerá. Normalmente, a chave deve permitir ao usuário executar apenas as ações necessárias para concluir a operação, como o acesso somente leitura se o cliente não puder fazer o upload de dados no repositório de dados. Para carregamentos de arquivos é comum especificar uma chave que forneça permissão somente gravação, assim como a localização e o período de validade. É fundamental especificar com precisão o recurso ou o conjunto de recursos ao qual a chave se aplica.

Considere como controlar o comportamento dos usuários. A implementação desse padrão significa uma perda de controle sobre os recursos que os usuários possuem acesso. O nível de controle que pode ser exercido é limitado pelos recursos das políticas e permissões disponíveis para o serviço ou o repositório de dados de destino. Por exemplo, geralmente não é possível criar uma chave que limita o tamanho dos dados a serem gravados no armazenamento, ou o número de vezes que a chave pode ser usada para acessar um arquivo. Isso pode resultar em enormes custos inesperados para a transferência de dados, mesmo quando usado pelo cliente pretendido e podem ser causados por um erro no código que causa upload ou download repetido. Para limitar o número de vezes que um arquivo pode ser carregado, sempre que possível, force o cliente a notificar o aplicativo quando uma operação for concluída. Por exemplo, alguns repositórios de dados aumentam os eventos que o código do aplicativo pode usar para monitorar as operações e controlar o comportamento do usuário. No entanto, é difícil impor cotas para usuários individuais em um cenário multilocatário em que a mesma chave é usada por todos os usuários de um locatário.

Validar, e opcionalmente limpar, todos os dados carregados. Um usuário mal-intencionado que obtenha acesso à chave poderá carregar dados projetados para comprometer o sistema. Alternativamente, usuários autorizados podem carregar dados inválidos e, quando processados, poderão resultar em um erro ou falha no sistema. Para proteger contra isso, assegure-se de que todos os dados carregados sejam validados e verificados quanto a conteúdo malicioso antes de serem utilizados.

Auditar todas as operações. Muitos mecanismos baseados em chave podem registrar operações como uploads, downloads e falhas. Esses logs geralmente podem ser incorporados em um processo de auditoria e, também, utilizados para cobrança se o usuário for cobrado com base no tamanho do arquivo ou no volume de dados. Use os logs para detectar falhas de autenticação que podem ser causadas por problemas com o provedor de chave ou remoção acidental de uma política de acesso armazenada.

Entregar a chave com segurança. É possível ser inserida em uma URL que o usuário ativa em uma página da Web, ou pode ser utilizada em uma operação de redirecionamento do servidor para que o download ocorra automaticamente. Sempre use HTTPS para entregar a chave por um canal seguro.

Proteger dados confidenciais em trânsito. Os dados confiáveis entregues através do aplicativo geralmente ocorrem usando TLS e isso deve ser aplicado para clientes que acessem o repositório de dados diretamente.

Outras questões a serem reconhecidas ao implementar esse padrão são:

  • Se o cliente não notificar o servidor da conclusão da operação, ou não puder fazê-la, e o único limite for o período de validade da chave, o aplicativo não poderá realizar operações de auditoria, tais como contar o número de uploads ou downloads, ou impedir múltiplos carregamentos ou downloads.

  • A flexibilidade das políticas de chave que podem ser geradas pode ser limitada. Por exemplo, alguns mecanismos somente permitem o uso de um período de validade cronometrado. Outros não são capazes de especificar uma granularidade suficiente de permissões de leitura/gravação.

  • Se a hora de início para o período de validade de chave ou token for especificada, assegure-se de que seja um pouco mais cedo do que o tempo atual do servidor para permitir que os relógios do cliente possam estar ligeiramente fora da sincronização. O padrão, se não especificado, geralmente é o tempo atual do servidor.

  • A URL que contém a chave será gravada em arquivos de log do servidor. Embora a chave normalmente tenha expirado antes que os arquivos de log sejam utilizados para análise, assegure-se de limitar o acesso. Se os dados de log forem transmitidos para um sistema de monitoramento ou armazenados em outro local, considere implementar um atraso para evitar vazamentos de chaves até que o período de validade tenha expirado.

  • Se o código do cliente for executado em um navegador da Web, talvez o navegador precise suportar o CORS (Compartilhamento de Recursos entre Origens) para habilitar o código que executa dentro do navegador da Web para acessar dados em um domínio diferente daquele que atendia a página. Alguns navegadores mais antigos e alguns repositórios de dados não dão suporte ao CORS e o código executado nesses navegadores pode usar uma chave limitada para fornecer o acesso para dados em um domínio diferente, como uma conta de armazenamento em nuvem.

Quando usar esse padrão

Esse padrão não é útil para as seguintes situações:

  • Minimizar o carregamento de recursos e maximizar o desempenho e a escalabilidade. O uso de uma valet key não requer que o recurso seja bloqueado, nenhuma chamada remota do servidor é necessária, não há limite no número de valet keys que possam ser emitidas e evita um ponto único de falha resultante da execução da transferência de dados através do código do aplicativo. Criar uma valet key é tipicamente uma operação criptográfica simples de autenticar uma cadeia de caracteres com uma chave.

  • Minimizar o custo operacional. Permitir acesso direto a repositórios e filas é um recurso economicamente eficiente, podendo resultar em menos viagens de ida e volta da rede e permitir uma redução no número de recursos de computação necessários.

  • Quando os clientes fazem o upload ou download de dados regularmente, especialmente quando há um grande volume ou quando cada operação envolve arquivos grandes.

  • Quando o aplicativo possui recursos de computação limitados disponíveis, devido a limitações de hospedagem ou considerações de custo. Nesse cenário, o padrão é ainda mais útil se houver muitos uploads ou downloads simultâneos de dados, pois alivia o aplicativo de tratar com a transferência de dados.

  • Quando os dados são armazenados em um repositório de dados remoto ou em um datacenter diferente. Se o aplicativo fosse necessário para agir como um gatekeeper, poderia haver uma cobrança pela largura de banda adicional da transferência de dados entre datacenters, ou entre redes públicas ou privadas entre o cliente e o aplicativo e, depois, entre o aplicativo e o repositório de dados.

Esse padrão pode não ser útil nas seguintes situações:

  • Se o aplicativo tiver que executar alguma tarefa nos dados antes de ser armazenado ou antes de ser enviado ao cliente. Por exemplo, se o aplicativo precisar executar a validação, acessar o log com êxito ou executar uma transformação nos dados. No entanto, alguns repositórios de dados e clientes são capazes de negociar e realizar transformações simples, como compactação e descompactação (por exemplo, um navegador da Web geralmente pode tratar formatos gzip).

  • Se o design de um aplicativo existente dificulta a incorporação do padrão. O uso deste padrão normalmente requer uma abordagem arquitetônica diferente para entregar e receber dados.

  • Se for necessário manter trilhas de auditoria ou controlar o número de vezes que uma operação de transferência de dados é executada e o mecanismo de valet key em uso não fornece suporte para notificações que o servidor pode usar para gerenciar essas operações.

  • Se for necessário limitar o tamanho dos dados, especialmente durante as operações de upload. A única solução para isso é que o aplicativo verifique o tamanho dos dados após a conclusão da operação ou verifique o tamanho dos uploads após um período especificado ou com base em um agendamento.

Design de carga de trabalho

Um arquiteto deve avaliar como o padrão Valet Key pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:

Pilar Como esse padrão apoia os objetivos do pilar
As decisões de design de segurança ajudam a garantir a confidencialidade, integridade e disponibilidade dos dados e sistemas da sua carga de trabalho. Esse padrão permite que um cliente acesse diretamente um recurso sem precisar de credenciais duradouras ou permanentes. Todas as solicitações de acesso começam com uma transação auditável. O acesso concedido é então limitado em escopo e duração. Esse padrão também facilita a revogação do acesso concedido.

- SE:05 Gerenciamento de identidade e acesso
A otimização de custos se concentra em sustentar e melhorar o retorno sobre o investimento da sua carga de trabalho. Esse design descarrega o processamento como uma relação exclusiva entre o cliente e o recurso sem adicionar um componente para lidar diretamente com todas as solicitações do cliente. O benefício é mais dramático quando as solicitações do cliente são frequentes ou grandes o suficiente para exigir recursos de proxy significativos.

- CO:09 Custos de fluxo
A eficiência de desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações em dimensionamento, dados e código. Não usar um recurso intermediário para fazer proxy do acesso descarrega o processamento como uma relação exclusiva entre o cliente e o recurso sem exigir um componente ambassador que precise lidar com todas as solicitações do cliente de maneira eficiente. O benefício de usar esse padrão é mais significativo quando o proxy não agrega valor à transação.

- PE:07 Código e infraestrutura

Tal como acontece com qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com este padrão.

Exemplo

O Azure fornece suporte a assinaturas de acesso compartilhado no Armazenamento do Microsoft Azure para o controle de acesso granular para dados em blobs, tabelas e filas e para filas e tópicos do Barramento de Serviço. Um token de assinatura de acesso compartilhado pode ser configurado para fornecer direitos de acesso específicos, como ler, escrever, atualizar e excluir em uma tabela específica; um intervalo chaves dentro de uma tabela; uma fila; um blob; ou um contêiner de blob. A validade pode ser um período de tempo especificado. Essa funcionalidade é adequada para usar uma chave de manobrista para acesso.

Considere uma carga de trabalho que tenha centenas de clientes móveis ou de desktop carregando binários grandes com frequência. Sem esse padrão, a carga de trabalho tem essencialmente duas opções. A primeira é fornecer acesso permanente e configuração a todos os clientes para realizar uploads diretamente em uma conta de armazenamento. A outra é implementar o padrão Roteamento de Gateway para configurar um ponto de extremidade em que os clientes usam acesso por proxy ao armazenamento, mas isso pode não estar agregando valor adicional à transação. Ambas as abordagens sofrem problemas abordados no contexto padrão:

  • Segredos duradouros e pré-compartilhados. Potencialmente sem muita maneira de fornecer chaves diferentes para clientes diferentes.
  • Despesa adicional para executar um serviço de computação que tenha recursos suficientes para lidar com o recebimento atual de arquivos grandes.
  • Potencialmente retardando as interações do cliente adicionando uma camada extra de computação e salto de rede ao processo de carregamento.

O uso do padrão Valet Key aborda as preocupações de segurança, otimização de custos e desempenho.

Diagrama mostrando um cliente acessando uma conta de armazenamento depois de obter um token de acesso de uma API.

  1. Os clientes, no último momento responsável, se autenticam em uma API hospedada da Função Azure leve e dimensionável para zero para solicitar acesso.

  2. A API valida a solicitação e, em seguida, obtém e retorna um token SaS limitado por tempo e escopo.

    O token gerado pela API restringe o cliente às seguintes limitações:

    • Qual conta de armazenamento usar. Ou seja, o cliente não precisa saber essas informações com antecedência.
    • Um contêiner e um nome de arquivo específicos para usar; garantindo que o token possa ser usado com, no máximo, um arquivo.
    • Uma janela de operação curta, como três minutos. Esse curto período de tempo garante que os tokens tenham um TTL que não se estenda além de sua utilidade.
    • Permissões para criar apenas um blob, não baixar, atualizar ou excluir.
  3. Esse token é então usado pelo cliente, dentro da janela de tempo estreita, para carregar o arquivo diretamente na conta de armazenamento.

A API gera esses tokens para clientes autorizados usando uma chave de delegação de usuário com base na própria identidade gerenciada do Microsoft Entra ID da API. O registro em log é habilitado na(s) conta(s) de armazenamento e a API de geração de token permite a correlação entre as solicitações de token e o uso do token. A API pode usar informações de autenticação de cliente ou outros dados disponíveis para decidir qual conta de armazenamento ou contêiner usar, como em uma situação de multilocatário.

Um exemplo completo está disponível no GitHub no exemplo de padrão Valet Key. Os trechos de código a seguir são adaptados desse exemplo. Este primeiro mostra como a Função do Azure (encontrada em ValetKey.Web) gera um token de assinatura de acesso compartilhado delegado pelo usuário usando a própria identidade gerenciada da Função do Azure.

[Function("FileServices")]
public async Task<StorageEntitySas> GenerateTokenAsync([HttpTrigger(...)] HttpRequestData req, ..., 
                                                        CancellationToken cancellationToken)
{
  // Authorize the caller, select a blob storage account, container, and file name.
  // Authenticate to the storage account with the Azure Function's managed identity.
  ...

  return await GetSharedAccessReferenceForUploadAsync(blobContainerClient, blobName, cancellationToken);
}

/// <summary>
/// Return an access key that allows the caller to upload a blob to this
/// specific destination for about three minutes.
/// </summary>
private async Task<StorageEntitySas> GetSharedAccessReferenceForUploadAsync(BlobContainerClient blobContainerClient, 
                                                                            string blobName,
                                                                            CancellationToken cancellationToken)
{
  var blobServiceClient = blobContainerClient.GetParentBlobServiceClient();
  var blobClient = blobContainerClient.GetBlockBlobClient(blobName);

  // Allows generating a SaS token that is evaluated as the union of the RBAC permissions on the managed identity
  // (for example, Blob Data Contributor) and then narrowed further by the specific permissions in the SaS token.
  var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-3),
                                                                            DateTimeOffset.UtcNow.AddMinutes(3),
                                                                            cancellationToken);

  // Limit the scope of this SaS token to the following:
  var blobSasBuilder = new BlobSasBuilder
  {
      BlobContainerName = blobContainerClient.Name,     // - Specific container
      BlobName = blobClient.Name,                       // - Specific filename
      Resource = "b",                                   // - Blob only
      StartsOn = DateTimeOffset.UtcNow.AddMinutes(-3),  // - For about three minutes (+/- for clock drift)
      ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(3),  // - For about three minutes (+/- for clock drift)
      Protocol = SasProtocol.Https                      // - Over HTTPS
  };
  blobSasBuilder.SetPermissions(BlobSasPermissions.Create);

  return new StorageEntitySas
  {
      BlobUri = blobClient.Uri,
      Signature = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName).ToString();
  };
}

O trecho a seguir é o objeto de transferência de dados (DTO) usado pela API e pelo cliente.

public class StorageEntitySas
{
  public Uri? BlobUri { get; internal set; }
  public string? Signature { get; internal set; }
}

O cliente (encontrado em ValetKey.Client) usa o URI e o token retornados da API para executar o upload sem exigir recursos adicionais e com desempenho total de cliente para armazenamento.

...

// Get the SaS token (valet key)
var blobSas = await httpClient.GetFromJsonAsync<StorageEntitySas>(tokenServiceEndpoint);
var sasUri = new UriBuilder(blobSas.BlobUri)
{
    Query = blobSas.Signature
};

// Create a blob client using the SaS token as credentials
var blob = new BlobClient(sasUri.Uri);

// Upload the file directly to blob storage
using (var stream = await GetFileToUploadAsync(cancellationToken))
{
    await blob.UploadAsync(stream, cancellationToken);
}

...

Próximas etapas

As diretrizes a seguir podem ser relevantes na implementação desse padrão:

Os seguintes padrões também serão relevantes durante a implementação desse padrão:

  • Padrão de gatekeeper. Este padrão pode ser utilizado em conjunto com o padrão Valet Key para proteger aplicativos e serviços, utilizando uma instância de host dedicada que age como agente entre clientes e o aplicativo ou serviço. O gatekeeper valida e limpa as solicitações, e transmite as solicitações e os dados entre o cliente e o aplicativo. Isso pode fornecer uma camada adicional de segurança e reduzir a superfície de ataque do sistema.
  • Padrão de hospedagem de conteúdo estático. Descreve como implantar recursos estáticos em um serviço de armazenamento baseado em nuvem que pode entregar esses recursos diretamente ao cliente para reduzir o requisito de instâncias de computação dispendiosas. Onde os recursos não se destinam a ser publicamente disponíveis, o padrão Valet Key poderá ser usado para protegê-los.