.Net Core 5.0 - Sql Azure + Always Encrypted + Identidad administrada

Dec 11 2020

Tengo un Azure SQL Db con columnas cifradas (siempre cifrado con Azure KeyVault). Puedo acceder a esta base de datos desde SSMS y puedo ver los datos descifrados.

También tengo una aplicación web hecha con .Net Core 5.0 que se implementa en Azure App Service. El servicio de la aplicación tiene la Identidad administrada activada y Key Vault que tiene claves enc / dec para esa base de datos SQL tiene una configuración de política de acceso para permitir que este servicio de la aplicación descifre los datos.

La aplicación web funciona con identidad administrada, ya que puedo ver que los datos no cifrados se recuperan sin ningún problema.

Además, la cadena de conexión incluye Column Encryption Setting=enabled;. Aquí está la cadena de conexión:

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

El problema es que no puedo encontrar NINGUNA muestra con este tipo de configuración. Encontré algunos y entiendo que necesito registrarme SqlColumnEncryptionAzureKeyVaultProvider. Aquí está mi código para obtener 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 no arroja ninguna excepción y funciona para consultas no cifradas. Pero para las consultas cifradas, aparece el siguiente error:

No se pudo descifrar una clave de cifrado de columna. Nombre de proveedor de almacén de claves no válido: 'AZURE_KEY_VAULT'. El nombre de un proveedor de almacén de claves debe indicar un proveedor de almacén de claves del sistema o un proveedor de almacén de claves personalizado registrado. Los nombres válidos del proveedor del almacén de claves del sistema son: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Los nombres de proveedores de almacenes de claves personalizados válidos (registrados actualmente) son:. Verifique la información del proveedor del almacén de claves en las definiciones de clave maestra de la columna en la base de datos y verifique que todos los proveedores del almacén de claves personalizados utilizados en su aplicación estén registrados correctamente. No se pudo descifrar una clave de cifrado de columna. Nombre de proveedor de almacén de claves no válido: 'AZURE_KEY_VAULT'. El nombre de un proveedor de almacén de claves debe indicar un proveedor de almacén de claves del sistema o un proveedor de almacén de claves personalizado registrado.Los nombres válidos del proveedor del almacén de claves del sistema son: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Los nombres de proveedores de almacenes de claves personalizados válidos (registrados actualmente) son:. Verifique la información del proveedor del almacén de claves en las definiciones de clave maestra de la columna en la base de datos y verifique que todos los proveedores del almacén de claves personalizados utilizados en su aplicación estén registrados correctamente.

Parece que el proveedor de la bóveda de claves no está registrado.

¿Qué debo hacer para que funcione la consulta de datos cifrados?

paquetes 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" />

Respuestas

2 alvipeo Jan 25 2021 at 23:15

Resulta que es imposible leer datos descifrados en .NET 5 cuando se usa MSI. Hay un error en los paquetes de MS y el servicio de aplicaciones nunca está autorizado.

Tienes que utilizar Service Principal. ¡Esto funciona de maravilla!

Actualizar

Tengo que agradecer a los ingenieros de MS que ofrecieron una solución 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

Pude usar este código que usa proporciona un TokenCredential para el proveedor SqlColumnEncryption. DefaultAzureCredential devuelve una identidad administrada cuando se implementa como un servicio de aplicaciones:

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

Llámalo desde tu método startup.Configure.