.Net Core 5.0 - Sql Azure + Always Encrypted + Managed Identity

Dec 11 2020

Eu tenho um Azure SQL Db com colunas criptografadas (Sempre criptografado com Azure KeyVault). Posso acessar esse banco de dados do SSMS e posso ver os dados descriptografados.

Eu também tenho um aplicativo da web feito com .Net Core 5.0 que é implantado no Serviço de Aplicativo do Azure. O serviço de aplicativo tem Identidade Gerenciada ativada e Key Vault que tem chaves enc / dec para esse SQL Db tem configuração de política de acesso para permitir que este serviço de aplicativo descriptografe os dados.

O aplicativo da web funciona com identidade gerenciada, pois posso ver que dados não criptografados são recuperados sem nenhum problema.

Além disso, a string de conexão inclui Column Encryption Setting=enabled;. Aqui está a string de conexão:

Server=tcp:server.database.windows.net,1433;Database=somedb;Column Encryption Setting=enabled;

O problema é que não consigo encontrar NENHUMA amostra com este tipo de configuração. Encontrei alguns e entendo que preciso me registrar SqlColumnEncryptionAzureKeyVaultProvider. Este é meu código para obter SqlConnection:

    internal static class AzureSqlConnection
    {
        private static bool _isInitialized;

        private static void InitKeyVaultProvider(ILogger logger)
        {
            /*
             * from here - https://github.com/dotnet/SqlClient/blob/master/release-notes/add-ons/AzureKeyVaultProvider/1.2/1.2.0.md
             *      and  - https://github.com/dotnet/SqlClient/blob/master/doc/samples/AzureKeyVaultProviderExample.cs
             *
             */

            try
            {
                // Initialize AKV provider
                SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider =
                    new SqlColumnEncryptionAzureKeyVaultProvider(AzureActiveDirectoryAuthenticationCallback);

                // Register AKV provider
                SqlConnection.RegisterColumnEncryptionKeyStoreProviders(
                    new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(1, StringComparer.OrdinalIgnoreCase)
                    {
                        {SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider}
                    });

                _isInitialized = true;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Could not register SqlColumnEncryptionAzureKeyVaultProvider");
                throw;
            }
        }

        internal static async Task<SqlConnection> GetSqlConnection(string connectionString, ILogger logger)
        {
            if (!_isInitialized) InitKeyVaultProvider(logger);

            try
            {
                SqlConnection conn = new SqlConnection(connectionString);
                /*
                         * This is Managed Identity (not Always Encrypted)
                         *  https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi#modify-aspnet-core
                         *
                         */
#if !DEBUG
                conn.AccessToken = await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/");
                logger.LogInformation($"token: {conn.AccessToken}"); #endif await conn.OpenAsync(); return conn; } catch (Exception ex) { logger.LogError(ex, "Could not establish a connection to SQL Server"); throw; } } private static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope) { return await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/"); //AuthenticationContext? authContext = new AuthenticationContext(authority); //ClientCredential clientCred = new ClientCredential(s_clientId, s_clientSecret); //AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred); //if (result == null) //{ // throw new InvalidOperationException($"Failed to retrieve an access token for {resource}");
            //}

            //return result.AccessToken;
        }
    }

Este código não lança nenhuma exceção e funciona para consultas não criptografadas. Mas, para consultas criptografadas, recebo o seguinte erro:

Falha ao descriptografar uma chave de criptografia de coluna. Nome do provedor de armazenamento de chaves inválido: 'AZURE_KEY_VAULT'. Um nome de provedor de armazenamento de chaves deve denotar um provedor de armazenamento de chaves do sistema ou um provedor de armazenamento de chaves personalizado registrado. Os nomes de provedor de armazenamento de chaves do sistema válidos são: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Os nomes válidos (atualmente registrados) do provedor de armazenamento de chaves personalizado são: Verifique as informações do provedor de armazenamento de chaves nas definições de chave mestra da coluna no banco de dados e verifique se todos os provedores de armazenamento de chaves personalizados usados ​​em seu aplicativo estão registrados corretamente. Falha ao descriptografar uma chave de criptografia de coluna. Nome do provedor de armazenamento de chaves inválido: 'AZURE_KEY_VAULT'. Um nome de provedor de armazenamento de chaves deve denotar um provedor de armazenamento de chaves do sistema ou um provedor de armazenamento de chaves personalizado registrado.Os nomes de provedor de armazenamento de chaves do sistema válidos são: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Os nomes de provedor de armazenamento de chaves customizados válidos (atualmente registrados) são:. Verifique as informações do provedor de armazenamento de chaves nas definições de chave mestra da coluna no banco de dados e verifique se todos os fornecedores de armazenamento de chaves personalizados usados ​​em seu aplicativo estão registrados corretamente.

Parece que o provedor do cofre de chaves não está registrado.

O que devo fazer para que funcione a consulta de dados criptografados?

pacotes usados

    <PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.0" />
    <PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.0" />
    <PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="1.2.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />

Respostas

2 alvipeo Jan 25 2021 at 23:15

Acontece que é impossível ler dados descriptografados no .NET 5 quando o MSI é usado. Há um bug em pacotes MS e o serviço de aplicativo nunca é autorizado.

Você deve usar o Principal de Serviço. Isso funciona como um encanto!

Atualizar

Tenho que agradecer aos engenheiros da MS que ofereceram uma solução funcional:

public static async Task<string> KeyVaultAuthenticationCallback(string authority, string resource, string scope)
{
     return await Task.Run(() => new ManagedIdentityCredential().GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
     /********************** Alternatively, to use User Assigned Managed Identity ****************/
     // var clientId = {clientId_of_UserAssigned_Identity};
     // return await Task.Run(() => new ManagedIdentityCredential(clientId).GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
}
SpinDoctor Apr 10 2021 at 04:19

Consegui usar este código que fornece um TokenCredential para o provedor SqlColumnEncryption. DefaultAzureCredential retorna uma identidade gerenciada quando implantado como um serviço de aplicativo:

            SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
            Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
            {
                { SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider }
            };
            SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);

Chame-o a partir do seu método startup.Configure.