Editar

Padrão de Chave Valet

Azure
Azure Storage

Utilize um token que conceda aos clientes acesso direto restrito a um recurso específico para descarregar a transferência de dados da aplicação. Esta ação é particularmente útil em aplicações que utilizam filas ou sistemas de armazenamento alojado na cloud e pode minimizar os custos e maximizar o desempenho e a escalabilidade.

Contexto e problema

Programas cliente e navegadores da Web geralmente precisam ler e gravar arquivos ou fluxos de dados de e para o armazenamento de um aplicativo. Normalmente, o aplicativo manipulará a movimentação dos dados — seja buscando-os do armazenamento e transmitindo-os para o cliente, ou lendo o fluxo carregado do cliente e armazenando-o no armazenamento de dados. No entanto, esta abordagem absorve recursos importantes, tais como a computação, a memória e a largura de banda.

Os arquivos de dados têm a capacidade de processar o carregamento e a transferência de dados diretamente, sem que a aplicação realize qualquer processamento para mover estes dados. No entanto, tal requer, normalmente, que o cliente tenha acesso às credenciais de segurança do arquivo. Tal pode ser uma técnica útil para minimizar os custos de transferência de dados e o requisito para aumentar horizontalmente a aplicação e para maximizar o desempenho. Significa, no entanto, que a aplicação já não é capaz de gerir a segurança dos dados. Depois de o cliente ter uma ligação ao arquivo de dados para proporcionar acesso direto, a aplicação não pode atuar como um controlador de chamadas. Este já não controla o processo e não pode impedir carregamentos ou transferências subsequentes do arquivo de dados.

Esta abordagem não é realista em sistemas distribuídos que precisem de servir clientes não fidedignos. Ao invés, as aplicações têm de ser capazes de controlar o acesso de forma segura aos dados de forma granular, mas ainda reduzir a carga no servidor ao configurar esta ligação e, em seguida, ao permitir que o cliente comunique diretamente com o arquivo de dados para realizar as operações de leitura ou escrita necessárias.

Solução

Tem de resolver o problema do controlo do acesso a um arquivo de dados em que o arquivo não consiga gerir a autenticação e autorização de clientes. Uma solução típica é restringir o acesso à conexão pública do armazenamento de dados e fornecer ao cliente uma chave ou token que o armazenamento de dados possa validar.

Geralmente, esta chave ou token é denominada uma chave valet. Concede acesso por tempo limitado a recursos específicos e permite apenas operações predefinidas como ler e escrever no armazenamento ou nas filas ou carregar e transferir num browser. As aplicações podem criar e emitir chaves valet para dispositivos cliente e browsers rápida e facilmente, permitindo que os clientes realizem as operações necessárias sem pedir que a aplicação lide diretamente com a transferência de dados. Deste modo, remove a sobrecarga de processamento e o impacto no desempenho e escalabilidade da aplicação e do servidor.

O cliente utiliza este token para aceder a um recurso específico no arquivo de dados apenas por 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 fica inválida e não permite o acesso ao recurso.

Figura 1 – Descrição geral do padrão

Também pode configurar uma chave que tenha outras dependências, tais como o âmbito dos dados. Por exemplo, consoante as capacidades do arquivo de dados, a chave pode especificar uma tabela completa num arquivo de dados, ou apenas linhas específicas numa tabela. Nos sistemas de armazenamento na cloud, a chave pode especificar um contentor ou apenas um item específico num contentor.

A chave também pode ser invalidada pela aplicação. Esta é uma abordagem útil se o cliente notificar o servidor que a operação de transferência de dados está concluída. O servidor pode então invalidar essa chave para impedir mais acessos.

A utilização deste padrão pode simplificar a gestão de acesso a recursos porque não há qualquer requisito para criar e autenticar um utilizador, conceder permissões e, em seguida, remover o utilizador novamente. Ele também facilita a limitação do local, da permissão e do período de validade, tudo isso simplesmente gerando uma chave em tempo de execução. Os fatores importantes são para limitar o máximo possível o período de validade e, especialmente, a localização do recurso, para que o destinatário possa apenas utilizá-lo para o objetivo pretendido.

Problemas e considerações

Na altura de decidir como implementar este padrão, considere os seguintes pontos:

Gerir o estado e o período de validade da chave. Se for divulgada ou estiver comprometida, a chave desbloqueia de forma eficaz o item de destino e disponibiliza-o para uma utilização maliciosa durante o período de validade. Uma chave pode, normalmente, ser revogada ou desativada, dependendo de como foi emitida. As políticas do lado do servidor podem ser alteradas ou a chave de servidor com a qual foram assinadas pode ser invalidada. Especifique um período de validade curto para minimizar o risco de permitir que ocorram operações não autorizadas no arquivo de dados. No entanto, se o período de validade for demasiado curto, o cliente poderá não conseguir concluir a operação antes de a chave expirar. Permita que os utilizadores autorizados renovem a chave antes de o período de validade expirar se forem precisos vários acessos ao recurso protegido.

Controlar o nível de acesso que a chave concede. Normalmente, a chave deve permitir que o utilizador execute apenas as ações necessárias para concluir a operação, como o acesso só de leitura se o cliente não conseguir carregar dados para o arquivo de dados. Para carregamentos de ficheiros, é comum especificar uma chave que conceda a permissão só de escrita, bem como a localização e o período de validade. É fundamental especificar com precisão o recurso ou o conjunto de recursos aos quais a chave se aplica.

Considere como controlar o comportamento dos usuários. A implementação deste padrão significa alguma perda de controlo sobre os recursos aos quais é concedido acesso aos utilizadores. O nível de controlo que pode ser exercido é limitado pelas capacidades das políticas e permissões disponíveis para o serviço ou o arquivo de dados de destino. Por exemplo, normalmente, não pode criar uma chave que limite o tamanho dos dados a ser escritos no armazenamento ou o número de vezes que a chave pode ser utilizada para aceder a um ficheiro. Tal pode resultar em grandes custos inesperados para a transferência de dados, mesmo em caso de utilização pelo cliente pretendido, e pode ser causado por um erro no código que resulta em carregamentos ou transferências repetidos. Para limitar o número de vezes que um ficheiro pode ser carregado, sempre que possível, force o cliente a notificar a aplicação quando uma operação é concluída. Por exemplo, alguns arquivos de dados geram eventos que o código da aplicação pode utilizar para monitorizar as operações e controlar o comportamento do utilizador. 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 utilizador malicioso que obtenha acesso à chave pode carregar dados concebidos para comprometer o sistema. Em alternativa, os utilizadores autorizados podem carregar dados inválidos e, quando processados, podem resultar num erro ou numa falha de sistema. Para impedir esta situação, garanta que todos os dados carregados são validados e verificados relativamente ao conteúdo malicioso antes da utilização.

Auditar todas as operações. Vários mecanismos baseados em chaves podem registar operações como carregamentos, transferências e falhas. Estes registos poderão, normalmente, ser incorporados num processo de auditoria e ser utilizados para a faturação se for cobrado um valor ao utilizador com base no volume de dados ou no tamanho do ficheiro. Utilize os registos para detetar falhas de autenticação que podem ser causadas por problemas com o fornecedor de chave ou por uma remoção acidental de uma política de acesso armazenado.

Enviar a chave de forma segura. Pode ser incorporada num URL que o utilizador ativa numa página Web ou pode ser utilizada numa operação de redirecionamento do servidor para que a transferência ocorra automaticamente. Utilize sempre HTTPS para enviar a chave através de um canal seguro.

Proteger os dados confidenciais em trânsito. Os dados confidenciais entregues através do aplicativo geralmente ocorrerão usando TLS, e isso deve ser aplicado para os clientes que acessam o armazenamento de dados diretamente.

Outros problemas a ter em consideração ao implementar este padrão:

  • Se o cliente não notificar ou não conseguir notificar o servidor quanto à conclusão da operação e o único limite for o período de expiração da chave, a aplicação não conseguirá realizar operações de auditorias como a contagem do número de carregamentos ou de transferências, nem impedir vários carregamentos ou transferências.

  • A flexibilidade das políticas de chaves que podem ser geradas poderá ser limitada. Por exemplo, alguns mecanismos permitem apenas a utilização de um período de expiração limitado. Outros mecanismos não são capazes de especificar uma granularidade suficiente de permissões de leitura/escrita.

  • Se a hora de início da chave ou o período de validade do token for especificado, garanta que é um pouco anterior à hora atual do servidor para permitir os relógios cliente que poderão estar ligeiramente fora de sincronização. Por predefinição, se não for especificada, é, normalmente, a hora atual do servidor.

  • O URL que contém a chave será registado nos ficheiros de registo do servidor. Geralmente, a chave terá expirado antes de os ficheiros de registo serem utilizados para análise e garanta que limita o acesso aos mesmos. Se os dados de registo forem transmitidos a um sistema de monitorização ou armazenados noutra localização, pondere implementar um atraso para evitar a fuga de chaves até após o período de validade expirar.

  • Se o código de cliente for executado num browser, o browser poderá ter de suportar a partilha de recursos de várias origens (CORS) para ativar o código que é executado num browser para aceder a dados num domínio diferente do que executou a página. Alguns navegadores mais antigos e alguns armazenamentos de dados não suportam CORS, e o código executado nesses navegadores pode não ser capaz de usar uma chave de manobrista para fornecer acesso a dados em um domínio diferente, como uma conta de armazenamento em nuvem.

Quando utilizar este padrão

Este padrão é útil nas seguintes situações:

  • Para minimizar o carregamento de recursos e maximizar o desempenho e a escalabilidade. A utilização de uma chave valet não requer que o recurso seja bloqueado, não precisa de qualquer chamada de servidor remoto, não tem qualquer limite para o número de chaves valet que podem ser emitidas e evita um ponto único de falha provocado pela transferência de dados através do código da aplicação. Criar uma chave valet é, normalmente, uma operação criptográfica simples de assinar uma cadeia com uma chave.

  • Para minimizar os custos operacionais. Permitir o acesso direto a filas e arquivos é eficiente a nível de recursos e custos, pode resultar em menos percursos de ida e volta na rede e poderá permitir uma redução do número de recursos de computação necessários.

  • Quando os clientes carregam ou transferem dados regularmente, particularmente quando há um grande volume ou quando cada operação envolve ficheiros grandes.

  • Quando a aplicação tem recursos de computação disponíveis limitados, devido a limitações de alojamento ou considerações de custos. Neste cenário, o padrão será ainda mais útil se existirem muitos carregamentos ou transferências de dados em simultâneo porque liberta a aplicação do processamento de transferência de dados.

  • Quando os dados são armazenados num arquivo de dados remoto ou num datacenter diferente. Se a aplicação foi obrigada a atuar como um controlador de chamadas, poderá ser cobrada uma taxa pela largura de banda adicional da transferência de dados entre datacenters ou redes públicas ou privadas entre o cliente e a aplicação e, em seguida, entre a aplicação e o arquivo de dados.

Este padrão pode não ser prático nas seguintes situações:

  • Se a aplicação tiver de realizar algumas tarefas nos dados antes de serem armazenados ou de serem enviados para o cliente. Por exemplo, se a aplicação tiver de realizar a validação, registar o acesso com êxito ou executar uma transformação nos dados. No entanto, alguns armazenamentos de dados e clientes são capazes de negociar e realizar transformações simples, como compressão e descompressão (por exemplo, um navegador da Web geralmente pode lidar com formatos gzip).

  • Se a conceção de uma aplicação existente dificultar a incorporação do padrão. Normalmente, este padrão requer uma abordagem arquitetónica diferente para a entrega e a receção de dados.

  • Se for necessário manter os registos de auditoria ou controlar o número de vezes que uma operação de transferência de dados é executada e o mecanismo de chave valet em utilização não suportar notificações que o servidor pode utilizar para gerir estas operações.

  • Se for necessário limitar o tamanho dos dados, especialmente durante as operações de carregamento. A única solução passa pela aplicação verificar o tamanho de dados depois de concluída a operação ou verificar o tamanho dos carregamentos após um período de tempo especificado ou de forma agendada.

Design da 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 suporta 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 tanto em termos de âmbito como de duração. Esse padrão também facilita a revogação do acesso concedido.

- SE:05 Gestão de identidades e acessos
A Otimização de Custos está focada em sustentar e melhorar o retorno do 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 às demandas de forma eficiente por meio de otimizações em escala, dados e código. Não usar um recurso intermediário para fazer proxy do processamento de descargas de acesso como uma relação exclusiva entre o cliente e o recurso sem exigir um componente ambassador que precisa lidar com todas as solicitações do cliente de forma 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

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

Exemplo

O Azure suporta assinaturas de acesso partilhado no Armazenamento do Azure para o controlo do acesso granular aos dados em blobs, tabelas e filas e para tópicos e filas do Service Bus. Pode ser configurado um token de assinatura de acesso partilhado para conceder direitos de acesso específicos, tais como a leitura, escrita, atualização e eliminação de uma tabela específica; um intervalo de chaves numa tabela; uma fila; um blob; ou um contentor de blobs. A validade pode ser um período de tempo especificado. Esta 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 frequentemente binários grandes. 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 de Roteamento de Gateway para configurar um ponto de extremidade em que os clientes usam acesso 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 pré-partilhados e de longa duração. 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 de arquivos grandes no momento.
  • Potencialmente retardando as interações do cliente adicionando uma camada extra de computação e salto de rede ao processo de upload.

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, autenticam-se em uma API hospedada leve e dimensionável para zero do Azure Function para solicitar acesso.

  2. A API valida a solicitação e, em seguida, obtém e retorna um token SaS limitado de tempo & 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 específico e nome de arquivo 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 ultrapasse sua utilidade.
    • Permissões para criar apenas um blob, não para baixar, atualizar ou excluir.
  3. Esse token é então usado pelo cliente, dentro da janela de tempo estreita, para carregar o arquivo diretamente para a 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 está 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 multilocatário.

Um exemplo completo está disponível no GitHub em Exemplo de padrão de chave de valet. 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 ao 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óximos passos

As orientações a seguir podem ser relevantes ao implementar esse padrão:

Os seguintes padrões também podem ser relevantes ao implementar esse padrão:

  • Padrão do controlador de chamadas. Este projeto pode ser usado em conjunto com o padrão da Chave Valet para proteger aplicações e serviços com uma instância de anfitrião dedicada que age como um mediador entre os clientes e a aplicação ou o serviço. O controlador de chamadas valida e limpa os pedidos e transmite os dados e os pedidos entre o cliente e a aplicação. Pode proporcionar uma camada adicional de segurança e reduzir a superfície de ataque do sistema.
  • Padrão de Alojamento de Conteúdo Estático. Descreve como implementar recursos estáticos num serviço de armazenamento baseado na cloud que pode proporcionar estes recursos diretamente ao cliente para reduzir a necessidade de instâncias de computação dispendiosas. Quando os recursos não se destinam a estar publicamente disponíveis, o padrão da Chave Valet pode ser utilizado para os proteger.