.Net Core 5.0 - Sql Azure + zawsze szyfrowana + zarządzana tożsamość
Mam bazę danych Azure SQL Db z zaszyfrowanymi kolumnami (Always Encrypted with Azure KeyVault). Mam dostęp do tej bazy danych z SSMS i widzę odszyfrowane dane.
Mam również aplikację internetową utworzoną za pomocą .Net Core 5.0, która jest wdrażana w usłudze Azure App Service. Usługa aplikacji ma włączoną tożsamość zarządzaną, a Key Vault, który ma klucze szyfrowania / dekodowania dla tej bazy danych SQL, ma ustawienie zasad dostępu, które zezwala tej usłudze aplikacji na odszyfrowywanie danych.
Aplikacja internetowa działa z tożsamością zarządzaną, ponieważ widzę, że niezaszyfrowane dane są pobierane bez żadnego problemu.
Ponadto parametry połączenia obejmują Column Encryption Setting=enabled;
. Oto parametry połączenia:
Server=tcp:server.database.windows.net,1433;Database=somedb;Column Encryption Setting=enabled;
Problem w tym, że nie mogę znaleźć ŻADNYCH próbek z taką konfiguracją. Znalazłem kilka i rozumiem, że muszę się zarejestrować SqlColumnEncryptionAzureKeyVaultProvider
. Oto mój kod, aby uzyskać 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;
}
}
Ten kod nie zgłasza żadnych wyjątków i działa w przypadku niezaszyfrowanych zapytań. Ale w przypadku zaszyfrowanych zapytań otrzymuję następujący błąd:
Failed to decrypt a column encryption key. Invalid key store provider name: 'AZURE_KEY_VAULT'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered) custom key store provider names are: . Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly. Failed to decrypt a column encryption key. Invalid key store provider name: 'AZURE_KEY_VAULT'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered) custom key store provider names are: . Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.
Wygląda na to, że dostawca magazynu kluczy nie jest zarejestrowany.
Co należy zrobić, aby przeszukiwanie zaszyfrowanych danych działało?
używane pakiety
<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" />
Odpowiedzi
Okazuje się, że odczyt odszyfrowanych danych w .NET 5 jest niemożliwy, gdy używany jest MSI. W pakietach MS występuje błąd, a usługa aplikacji nigdy nie jest autoryzowana.
Musisz użyć nazwy głównej usługi. To działa jak urok!
Aktualizacja
Muszę podziękować inżynierom MS, którzy zaoferowali działające rozwiązanie:
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);
}
Udało mi się użyć tego kodu, który używa TokenCredential do dostawcy SqlColumnEncryption. DefaultAzureCredential zwraca tożsamość zarządzaną, gdy jest wdrażana jako usługa aplikacji:
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider }
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Wywołaj to z metody startup.Configure.