У меня есть база данных SQL Azure с зашифрованными столбцами (всегда зашифрованными с помощью Azure KeyVault). Я могу получить доступ к этой базе данных из SSMS и увидеть расшифрованные данные.
У меня также есть веб-приложение, созданное с помощью .Net Core 5.0, которое развернуто в службе приложений Azure. В службе приложений включена управляемая идентификация, а в Key Vault, имеющем ключи enc/dec для этой базы данных SQL, есть параметр политики доступа, позволяющий этой службе приложений расшифровывать данные.
Веб-приложение работает с управляемой идентификацией, поскольку я вижу, что незашифрованные данные извлекаются без каких-либо проблем.
Кроме того, строка подключения включает Column Encryption Setting=enabled;
. Вот строка подключения:
Server=tcp:server.database.windows.net,1433;Database=somedb;Column Encryption Setting=enabled;
Проблема в том, что я не могу найти НИКАКИХ образцов с такой настройкой. Я нашел некоторые, и я понимаю, что мне нужно зарегистрироваться SqlColumnEncryptionAzureKeyVaultProvider
. Вот мой код для получения 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://learn.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;
}
}
Этот код не создает никаких исключений и работает для незашифрованных запросов. Но для зашифрованных запросов я получаю следующую ошибку:
Не удалось расшифровать ключ шифрования столбца. Недопустимое имя поставщика хранилища ключей: "AZURE_KEY_VAULT". Имя поставщика хранилища ключей должно обозначать либо поставщика системного хранилища ключей, либо зарегистрированного поставщика настраиваемого хранилища ключей. Допустимые имена поставщиков хранилища системных ключей: «MSSQL_CERTIFICATE_STORE», «MSSQL_CNG_STORE», «MSSQL_CSP_PROVIDER». Действительные (в настоящее время зарегистрированные) имена поставщиков пользовательских хранилищ ключей: . Проверьте информацию о поставщике хранилища ключей в определениях главного ключа столбца в базе данных и убедитесь, что все поставщики пользовательских хранилищ ключей, используемые в вашем приложении, правильно зарегистрированы. Не удалось расшифровать ключ шифрования столбца. Недопустимое имя поставщика хранилища ключей: "AZURE_KEY_VAULT". Имя поставщика хранилища ключей должно обозначать либо поставщика системного хранилища ключей, либо зарегистрированного поставщика настраиваемого хранилища ключей. Допустимые имена поставщиков хранилища системных ключей: «MSSQL_CERTIFICATE_STORE», «MSSQL_CNG_STORE», «MSSQL_CSP_PROVIDER». Действительные (в настоящее время зарегистрированные) имена поставщиков пользовательских хранилищ ключей: . Проверьте информацию о поставщике хранилища ключей в определениях главного ключа столбца в базе данных и убедитесь, что все поставщики пользовательских хранилищ ключей, используемые в вашем приложении, правильно зарегистрированы.
Похоже, поставщик хранилища ключей не зарегистрирован.
Что мне сделать, чтобы он работал для запроса зашифрованных данных?
<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" />
Я чувствую твою боль... это почти полезно, но нет. learn.microsoft.com/en-us/sql/relational-databases/security/…
Не могли бы вы предоставить политику доступа, которую вы настраиваете для MSI?
Как обычно: Клавиша: Get, List; Криптовалюта: развернуть, обернуть, проверить, подписать. Иначе он не работал бы нигде. Я считаю, что Key-List на самом деле не нужен. Но это все равно есть
Оказывается, невозможно прочитать расшифрованные данные в .NET 5, когда используется MSI. В пакетах MS есть ошибка, и служба приложений никогда не авторизуется.
Вы должны использовать Service Principal. Это работает как шарм!
Я должен поблагодарить инженеров MS, которые предложили рабочее решение:
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);
}
Это не проблема с .NET 5. Вы взяли пример обратного вызова проверки подлинности для Azure Key Vault и изменили его, чтобы он относился к базе данных SQL Azure, а не к ресурсу AKV. Вам нужно настроить обратный вызов, чтобы получить действительный токен AKV. Это всего лишь один из способов получить токен с помощью библиотек Azure.Core и Azure.Identity:
private static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope)
{
return await Task.Run(() => new ManagedIdentityCredential().GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
}
Вот только это не работает. Я пробовал - github.com/dotnet/SqlClient/issues/847#issuecomment-754111237
Я в основном просто указал на твою ошибку. В вашем ответе было просто неверное общее заявление: «Невозможно прочитать расшифрованные данные в .NET 5, когда используется MSI. В пакетах MS есть ошибка, и служба приложений никогда не авторизуется». Я пытался указать вам на то, что решения есть, а вы смешиваете шифрование, MSI и .NET 5 вместе, тогда как эта группировка не имеет смысла. Все они являются независимыми функциями. Вам нужно реализовать правильный код, который заставляет их работать. В частности, код, который извлекает правильный маркер доступа для доступа к хранилищу ключей Azure.
понятно. да, ребята из MS дали мне несколько идей (и код!), чтобы попробовать. Так что я буду работать над этим на этой неделе и обновлю свой ответ.
Я смог использовать этот код, который использует TokenCredential для поставщика SqlColumnEncryption. DefaultAzureCredential возвращает управляемое удостоверение при развертывании в качестве службы приложений:
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider }
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Вызовите его из вашего метода startup.Configure.
Ответ для Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider
v3
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new(new ManagedIdentityCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new()
{
[SqlColumnEncryptionAzureKeyVaultProvider.ProviderName] = azureKeyVaultProvider
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Взято из microsoft/sql-server-samples после того, как я чуть не потерял рассудок.
Помимо этого требуется:
Назначьте роль управляемого удостоверения службы приложений Key Vault Crypto User
на вкладке IAM в Key Vault.
Предоставьте пользователю базы данных управляемых удостоверений службы приложений разрешения VIEW ANY COLUMN MASTER KEY DEFINITION
и VIEW ANY COLUMN ENCRYPTION KEY DEFINITION
:
GRANT VIEW ANY COLUMN MASTER KEY DEFINITION to [app-service-user]
GRANT VIEW ANY COLUMN ENCRYPTION KEY DEFINITION to [app-service-user]
Разрешить доступ к службе приложений через брандмауэр Key Vault (т. е. добавить исходящие IP-адреса службы приложений в правила брандмауэра Key Vault).
Это помогает? Похоже на ту же проблему, хотя принятый ответ, похоже, здесь не применяется stackoverflow.com/questions/51618582/…