Compartilhar via


Provedores de armazenamento de chaves no ASP.NET Core

O sistema de proteção de dados emprega um mecanismo de descoberta por padrão para determinar onde as chaves criptográficas devem ser persistidas. O desenvolvedor pode substituir o mecanismo de descoberta padrão e especificar manualmente o local.

Aviso

Se você especificar um local explícito para a persistência de chave, o sistema de proteção de dados cancelará o registro do mecanismo padrão de criptografia de chave em repouso, de modo que as chaves não serão mais criptografadas em repouso. É recomendável que você especifique adicionalmente um mecanismo explícito de criptografia de chave para implantações de produção.

Sistema de arquivos

Para configurar um repositório de chaves baseado no sistema de arquivos, chame a rotina de configuração PersistKeysToFileSystem, conforme mostrado abaixo. Forneça um DirectoryInfo apontando para o repositório onde as chaves devem ser armazenadas:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));
}

O DirectoryInfo pode apontar para um diretório no computador local ou pode apontar para uma pasta em um compartilhamento de rede. Se estiver apontando para um diretório no computador local (e o cenário é que apenas aplicativos no computador local exigem acesso para usar esse repositório), considere usar o DPAPI do Windows (no Windows) para criptografar as chaves em repouso. Caso contrário, considere usar um certificado X.509 para criptografar chaves em repouso.

Armazenamento do Azure

O Azure.Extensions.AspNetCore.DataProtection.Blobs pacote NuGet fornece API para armazenar chaves de proteção de dados no Armazenamento de Blobs do Azure. As chaves podem ser compartilhadas em várias instâncias de um aplicativo Web. Os aplicativos podem compartilhar cookies de autenticação ou a proteção CSRF em vários servidores.

Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

Para interagir com o Azure Key Vault localmente usando credenciais de desenvolvedor, entre em sua conta de armazenamento no Visual Studio ou entre com a CLI do Azure. Se você ainda não instalou a CLI do Azure, confira Como instalar a CLI do Azure. Você pode executar o seguinte comando no painel do PowerShell do Desenvolvedor no Visual Studio ou em um shell de comando ao não usar o Visual Studio:

az login

Para obter mais informações, consulte Entrar no Azure usando ferramentas de desenvolvedor.

Configurar o Armazenamento de Blobs do Azure para manter as chaves de proteção de dados:

  • Crie uma conta de armazenamento do Azure.

  • Crie um contêiner para armazenar o arquivo de chave de proteção de dados.

  • É recomendável usar o Azure Managed Identity e controle de acesso baseado em função (RBAC) para acessar o blob de armazenamento de chaves. Você não precisa criar um arquivo de chave e carregá-lo no contêiner da conta de armazenamento. A estrutura cria o arquivo para você. Para inspecionar o conteúdo de um arquivo de chave, use o comando Exibir/editar do menu de contexto no final de uma linha de chave no portal.

Observação

Se você planeja usar um URI de blob com uma SAS (assinatura de acesso compartilhado) em vez de um Gerenciado Identity, use um editor de texto para criar um arquivo de chave XML em seu computador local:

<?xml version="1.0" encoding="utf-8"?>
<repository>
</repository>

Carregue o arquivo de chave no contêiner da conta de armazenamento. Use o comando Exibir/editar do menu de contexto no final da linha de chave no portal para confirmar se o blob contém o conteúdo anterior. Ao criar o arquivo manualmente, você poderá, em uma etapa posterior, obter o URI de blob com SAS do portal para configurar o aplicativo.

  • Crie uma Entidade Gerenciada Identity do Azure (ou adicione uma função à Entidade Gerenciada Identity existente que você planeja usar) com a função Storage Blob Data Contributor. Atribua o Identity Gerenciado ao Serviço de Aplicativo do Azure que está hospedando a implantação: Configurações>Identity>Atribuído pelo usuário>Adicionar.

    Observação

    Se você também planeja executar um aplicativo localmente com um usuário autorizado para acessar blobs usando a CLI do Azure ou a Autenticação de Serviço do Azure do Visual Studio, adicione sua conta de desenvolvedor do Azure em Controle de Acesso (IAM) com a função Colaborador de Dados de Blobs de Armazenamento. Se você quiser usar a CLI do Azure por meio do Visual Studio, execute o az login comando no painel do PowerShell do Desenvolvedor e siga os prompts para autenticar com o locatário.

Para configurar o provedor de Armazenamento de Blobs do Azure, chame uma das PersistKeysToAzureBlobStorage sobrecargas no aplicativo. O seguinte exemplo utiliza a sobrecarga que aceita um URI de blob e uma credencial de token (TokenCredential), contando com um Identity Gerenciado do Azure para controle de acesso baseado em função (RBAC).

Outras sobrecargas são baseadas em:

  • Um URI de blob e uma credencial de chave compartilhada de armazenamento (StorageSharedKeyCredential).
  • Um URI de blob com uma assinatura de acesso compartilhado (SAS).
  • Uma cadeia de conexão, um nome de contêiner e um nome de blob.
  • Um cliente de blob (BlobClient).

Para obter mais informações sobre a API e a autenticação do SDK do Azure, consulte Autenticar aplicativos .NET nos serviços do Azure usando a biblioteca do AzureIdentity. Para obter orientações sobre logs, consulte Log com o SDK do Azure para .NET: log sem registro do cliente. Para aplicativos que usam injeção de dependência, um aplicativo pode chamar AddAzureClientsCore, passando true para enableLogForwarding, para criar e conectar a infraestrutura de log.

No arquivo Program onde os serviços são registrados:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

builder.Services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential);

{MANAGED IDENTITY CLIENT ID}: o ID do cliente gerenciado do Azure Identity (GUID).

{TENANT ID}: ID do locatário.

{APPLICATION NAME}: SetApplicationName define o nome exclusivo desse aplicativo dentro do sistema de proteção de dados. O valor deve corresponder entre implantações do aplicativo.

{BLOB URI}: URI completo para o arquivo de chave. O URI é gerado pelo Armazenamento do Azure quando você cria o arquivo de chave. Não use um SAS.

Abordagem alternativa de SAS (assinatura de acesso compartilhado): como alternativa ao uso de um recurso gerenciado Identity para acessar o blob de chaves no Armazenamento de Blobs do Azure, você pode chamar a sobrecarga PersistKeysToAzureBlobStorage que aceita um URI de blob com um token SAS:

builder.Services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"));

Em Startup.ConfigureServices:

TokenCredential? credential;

if (_env.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential);

{MANAGED IDENTITY CLIENT ID}: o ID do cliente gerenciado do Azure Identity (GUID).

{TENANT ID}: ID do locatário.

{APPLICATION NAME}: SetApplicationName define o nome exclusivo desse aplicativo dentro do sistema de proteção de dados. O valor deve corresponder entre implantações do aplicativo.

{BLOB URI}: URI completo para o arquivo de chave. O URI é gerado pelo Armazenamento do Azure quando você cria o arquivo de chave. Não use um SAS.

Exemplo:

https://contoso.blob.core.windows.net/data-protection/keys.xml

Abordagem alternativa de SAS (assinatura de acesso compartilhado): como alternativa ao uso de um recurso gerenciado Identity para acessar o blob de chaves no Armazenamento de Blobs do Azure, você pode chamar a sobrecarga PersistKeysToAzureBlobStorage que aceita um URI de blob com um token SAS:

services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"));

{APPLICATION NAME}: SetApplicationName define o nome exclusivo desse aplicativo dentro do sistema de proteção de dados. O valor deve corresponder entre implantações do aplicativo.

{BLOB URI WITH SAS}: o URI completo em que o arquivo de chave deve ser armazenado com o token SAS como um parâmetro de cadeia de caracteres de consulta. O URI é gerado pelo Armazenamento do Azure quando você solicita uma SAS para o arquivo de chave carregado. No exemplo a seguir, o nome do contêiner é data-protection, e o nome da conta de armazenamento é contoso. O arquivo de chave é nomeado keys.xml. A cadeia de caracteres da consulta do SAS (assinatura de acesso compartilhado) está no final do URI (espaço reservado {SHARED ACCESS SIGNATURE}).

Exemplo:

https://contoso.blob.core.windows.net/data-protection/keys.xml{SHARED ACCESS SIGNATURE}

Se o aplicativo Web estiver em execução como um serviço do Azure, uma cadeia de conexão poderá ser usada para autenticar no Armazenamento do Azure usando BlobContainerClient, como visto no exemplo a seguir.

Aviso

Este artigo mostra o uso de cadeias de conexão. Ao usar um banco de dados local para desenvolvimento e teste, a autenticação do usuário do banco de dados por meio da cadeia de conexão não é necessária. Em ambientes de produção, as cadeias de conexão às vezes incluem uma senha para autenticar o acesso ao banco de dados ou operações de banco de dados. Uma ROPC (credencial de senha do proprietário do recurso) em uma cadeia de conexão é um risco de segurança a ser evitado em aplicativos de produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos implantados em ambientes de teste ou de produção, consulte ASP.NET Principais tópicos de segurança.

A chamada opcional para CreateIfNotExistsAsync provisiona o contêiner automaticamente caso ele não exista.

A cadeia de conexão (espaço reservado {CONNECTION STRING}) para a conta de armazenamento pode ser encontrada no portal do Entra ou do Azure na seção "Chaves de Acesso" ou executando o seguinte comando da CLI do Azure:

az storage account show-connection-string --name <account_name> --resource-group <resource_group>

No arquivo Program onde os serviços são registrados:

string connectionString = "{CONNECTION STRING}";
string containerName = "{CONTAINER NAME}";
string blobName = "keys.xml";
var container = new BlobContainerClient(connectionString, containerName);
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(blobName);

builder.Services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient);

Em Startup.ConfigureServices:

string connectionString = "{CONNECTION STRING}";
string containerName = "{CONTAINER NAME}";
string blobName = "keys.xml";
var container = new BlobContainerClient(connectionString, containerName);
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(blobName);

services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient);

Redis

O pacote Microsoft.AspNetCore.DataProtection.StackExchangeRedis permite armazenar chaves de proteção de dados em um cache Redis. As chaves podem ser compartilhadas em várias instâncias de um aplicativo Web. Os aplicativos podem compartilhar cookies de autenticação ou a proteção CSRF em vários servidores.

O pacote Microsoft.AspNetCore.DataProtection.StackExchangeRedis permite armazenar chaves de proteção de dados em um cache Redis. As chaves podem ser compartilhadas em várias instâncias de um aplicativo Web. Os aplicativos podem compartilhar cookies de autenticação ou a proteção CSRF em vários servidores.

Para configurar no Redis, chame uma das sobrecarga PersistKeysToStackExchangeRediss:

public void ConfigureServices(IServiceCollection services)
{
    var redis = ConnectionMultiplexer.Connect("<URI>");
    services.AddDataProtection()
        .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
}

Para configurar no Redis, chame uma das sobrecarga PersistKeysToRediss:

public void ConfigureServices(IServiceCollection services)
{
    var redis = ConnectionMultiplexer.Connect("<URI>");
    services.AddDataProtection()
        .PersistKeysToRedis(redis, "DataProtection-Keys");
}

Aviso

Ao usar o Redis para persistir as chaves de proteção de dados, lembre-se de que o Redis não persiste dados por padrão ao reiniciar. Isso pode fazer com que a Proteção de Dados emita novas chaves, invalidando dados protegidos anteriormente.

Você pode configurar o Redis para habilitar a persistência de dados para atenuar isso: a documentação do Redis tem informações sobre como configurar a persistência. Se você estiver usando o Redis Gerenciado do Azure, verifique se você habilitou a persistência de dados. O Cache do Azure para Redis precisa de uma camada premium ou superior para habilitar a persistência de dados.

Para mais informações, consulte os seguintes tópicos:

Registro

Aplica-se somente a implantações do Windows.

Às vezes, o aplicativo pode não ter acesso de gravação ao sistema de arquivos. Considere um cenário em que um aplicativo está sendo executado como uma conta de serviço virtual (como a identidade do pool de aplicativos de w3wp.exe). Nesses casos, o administrador pode provisionar uma chave do Registro acessível pela identidade da conta de serviço. Chame o método de extensão PersistKeysToRegistry, conforme mostrado abaixo. Forneça um RegistryKey apontando para o local onde as chaves criptográficas devem ser armazenadas:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Sample\keys", true));
}

Importante

É recomendável usar o DPAPI do Windows para criptografar as chaves em repouso.

Entity Framework Core

O pacote Microsoft.AspNetCore.DataProtection.EntityFrameworkCore fornece um mecanismo para armazenar chaves de proteção de dados em um banco de dados usando o Entity Framework Core. O pacote NuGet Microsoft.AspNetCore.DataProtection.EntityFrameworkCore deve ser adicionado ao arquivo de projeto, não faz parte do metapacote Microsoft.AspNetCore.App.

Com esse pacote, as chaves podem ser compartilhadas entre várias instâncias de um aplicativo Web.

Para configurar o provedor EF Core, chame o método PersistKeysToDbContext:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    // Add a DbContext to store your Database Keys
    services.AddDbContext<MyKeysContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("MyKeysConnection")));

    // using Microsoft.AspNetCore.DataProtection;
    services.AddDataProtection()
        .PersistKeysToDbContext<MyKeysContext>();

    services.AddDefaultIdentity<IdentityUser>()
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

O parâmetro genérico TContext deve herdar de DbContext e implementar IDataProtectionKeyContext:

using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;

namespace WebApp1
{
    class MyKeysContext : DbContext, IDataProtectionKeyContext
    {
        // A recommended constructor overload when using EF Core 
        // with dependency injection.
        public MyKeysContext(DbContextOptions<MyKeysContext> options) 
            : base(options) { }

        // This maps to the table that stores keys.
        public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
    }
}

Criar a tabela DataProtectionKeys.

Execute os seguintes comandos na janela do Console do Gerenciador de Pacotes (PMC):

Add-Migration AddDataProtectionKeys -Context MyKeysContext
Update-Database -Context MyKeysContext

MyKeysContext é o DbContext definido no exemplo de código anterior. Se você estiver usando um DbContext com um nome diferente, substitua seu nome de DbContext por MyKeysContext.

A classe/entidade DataProtectionKeys adota a estrutura mostrada na tabela a seguir.

Propriedade/Campo Tipo CLR Tipo SQL
Id int int, PK, IDENTITY(1,1), não nulo
FriendlyName string nvarchar(MAX), nulo
Xml string nvarchar(MAX), nulo

Repositório de chaves personalizadas

Se os mecanismos internos não forem apropriados, o desenvolvedor poderá especificar seu próprio mecanismo de persistência de chave fornecendo um IXmlRepository personalizado.