.Net Core 5.0 - Sql Azure + Always Encrypted + Managed Identity

.Net Core 5.0 - Sql Azure + Always Encrypted + Managed Identity

我有一个带有加密列的 Azure SQL 数据库(始终使用 Azure KeyVault 加密)。我可以从 SSMS 访问这个数据库,我可以看到解密的数据。

我还有一个使用 .Net Core 5.0 制作的 Web 应用程序,它已部署到 Azure 应用程序服务。应用服务已启用托管标识,并且具有 enc/dec 密钥的密钥保管库 SQL Db 具有访问策略设置以允许此应用服务解密数据。

Web 应用程序使用托管身份,因为我可以看到没有任何问题地检索未加密的数据。

此外,连接字符串确实包含 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;
        }
    }

此代码不会抛出任何异常,并且适用于非加密查询。但是对于加密查询,我收到以下错误:

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.

似乎密钥保管库提供程序未注册。

我应该怎么做才能查询加密数据?

使用的包

    <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包存在bug,应用服务未授权

您必须使用服务主体。这很有魅力!

更新

我要感谢提供有效解决方案的 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 的示例身份验证回调并将其更改为特定于 Azure SQL DB 而不是 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);
    }

我能够使用此代码,该代码使用向 SqlColumnEncryption 提供程序提供 TokenCredential。 DefaultAzureCredential returns 部署为应用服务时的托管身份:

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

从您的 startup.Configure 方法调用它。