azure - .Net Core 5.0 - Sql Azure + Always Encrypted + Managed Identity
问题描述
我有一个带有加密列的 Azure SQL Db(始终使用 Azure KeyVault 加密)。我可以从 SSMS 访问这个数据库,我可以看到解密的数据。
我还有一个使用 .Net Core 5.0 制作的 Web 应用程序,它部署到 Azure 应用程序服务。应用服务已打开托管标识,并且具有该 SQL Db 的 enc/dec 密钥的 Key Vault 具有访问策略设置以允许此应用服务解密数据。
该网络应用程序与托管标识一起使用,因为我可以看到未加密的数据被检索到没有任何问题。
此外,连接字符串确实包括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://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;
}
}
此代码不会引发任何异常,并且适用于非加密查询。但对于加密查询,我收到以下错误:
无法解密列加密密钥。无效的密钥存储提供程序名称:“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" />
解决方案
事实证明,使用 MSI 时无法读取 .NET 5 中的解密数据。MS 包中存在错误,并且从未授权应用服务。
您必须使用服务主体。这就像一个魅力!
更新
我必须感谢提供有效解决方案的 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);
}