.Net Core 5.0 - Sql Azure + Always Encrypted + Identità gestita
Ho un database SQL di Azure con colonne crittografate (sempre crittografato con Azure KeyVault). Posso accedere a questo db da SSMS e posso vedere i dati decrittografati.
Ho anche un'app Web realizzata con .Net Core 5.0 che viene distribuita al servizio app di Azure. Il servizio app ha Managed Identity attivato e Key Vault con chiavi enc / dec per quel database SQL dispone di impostazioni dei criteri di accesso per consentire a questo servizio app di decrittografare i dati.
L'app Web funziona con l'identità gestita poiché posso vedere che i dati non crittografati vengono recuperati senza problemi.
Inoltre, la stringa di connessione include Column Encryption Setting=enabled;. Ecco la stringa di connessione:
Server=tcp:server.database.windows.net,1433;Database=somedb;Column Encryption Setting=enabled;
Il problema è che non riesco a trovare NESSUN campione con questo tipo di configurazione. Ne ho trovati alcuni e ho capito che devo registrarmi SqlColumnEncryptionAzureKeyVaultProvider. Ecco il mio codice per ottenere 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;
}
}
Questo codice non genera eccezioni e funziona per le query non crittografate. Ma per le query crittografate ottengo il seguente errore:
Impossibile decrittografare una chiave di crittografia della colonna. Nome del provider dell'archivio chiavi non valido: "AZURE_KEY_VAULT". Il nome di un provider di archivio chiavi deve indicare un provider di archivio chiavi di sistema o un provider di archivio chiavi personalizzato registrato. I nomi dei provider di archivio chiavi di sistema validi sono: "MSSQL_CERTIFICATE_STORE", "MSSQL_CNG_STORE", "MSSQL_CSP_PROVIDER". I nomi dei provider di archivio chiavi personalizzato validi (attualmente registrati) sono:. Verificare le informazioni sul provider dell'archivio chiavi nelle definizioni della chiave master della colonna nel database e verificare che tutti i provider dell'archivio chiavi personalizzato utilizzati nell'applicazione siano registrati correttamente. Impossibile decrittografare una chiave di crittografia della colonna. Nome del provider dell'archivio chiavi non valido: "AZURE_KEY_VAULT". Il nome di un provider di archivio chiavi deve indicare un provider di archivio chiavi di sistema o un provider di archivio chiavi personalizzato registrato.I nomi dei provider di archivio chiavi di sistema validi sono: "MSSQL_CERTIFICATE_STORE", "MSSQL_CNG_STORE", "MSSQL_CSP_PROVIDER". I nomi dei provider di archivio chiavi personalizzato validi (attualmente registrati) sono:. Verificare le informazioni sul provider dell'archivio chiavi nelle definizioni della chiave master della colonna nel database e verificare che tutti i provider dell'archivio chiavi personalizzato utilizzati nell'applicazione siano registrati correttamente.
Sembra che il provider dell'insieme di credenziali delle chiavi non sia registrato.
Cosa devo fare per far funzionare la query sui dati crittografati?
pacchetti utilizzati
<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" />
Risposte
Risulta che è impossibile leggere i dati decrittografati in .NET 5 quando viene utilizzato MSI. C'è un bug nei pacchetti MS e il servizio dell'app non è mai autorizzato.
Devi usare l'entità servizio. Funziona a meraviglia!
Aggiornare
Devo ringraziare gli ingegneri MS che hanno offerto una soluzione funzionante:
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);
}
Sono stato in grado di utilizzare questo codice che utilizza fornisce un TokenCredential al provider SqlColumnEncryption. DefaultAzureCredential restituisce un'identità gestita quando distribuito come servizio app:
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider }
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Chiamalo dal tuo metodo startup.Configure.