Skip to content

Commit

Permalink
[3.1.6] Test | Updating tests to acquire token from user-assigned man…
Browse files Browse the repository at this point in the history
…aged identity (dotnet#2360) (dotnet#2473) (dotnet#2512)

* Test | Updating tests to acquire token from user-assigned managed identity (dotnet#2360) (dotnet#2473)
---------

Co-authored-by: David Engel <[email protected]>
Co-authored-by: Javad Rahnama <[email protected]>
Co-authored-by: David Engel <[email protected]>
Co-authored-by: Aris Rellegue <[email protected]>
  • Loading branch information
5 people authored May 17, 2024
1 parent a73c7ee commit 39b13a6
Show file tree
Hide file tree
Showing 10 changed files with 32 additions and 87 deletions.
2 changes: 0 additions & 2 deletions BUILDGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ Manual Tests require the below setup to run:
|AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} |
|AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` |
|AzureKeyVaultTenantId | (Optional) The Azure Active Directory tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ |
|AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ |
|AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ |
|LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.|
|SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`|
|SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
// See the LICENSE file in the project root for more information.

using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
using Azure.Identity;
using Xunit;
using Azure.Security.KeyVault.Keys;
using System.Reflection;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;

Expand All @@ -35,8 +33,7 @@ public static void LegacyAuthenticationCallbackTest()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))]
public static void TokenCredentialTest()
{
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential);
SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential());
byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey);
byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek);

Expand All @@ -49,8 +46,7 @@ public static void TokenCredentialRotationTest()
// SqlClientCustomTokenCredential implements a legacy authentication callback to request the access token from the client-side.
SqlColumnEncryptionAzureKeyVaultProvider oldAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential());

ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential);
SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential());

byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey);
byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithNewProvider);
Expand All @@ -72,15 +68,14 @@ public static void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion()
{
string keyName = keyPathUri.Segments[2];
string keyVersion = keyPathUri.Segments[3];
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
KeyClient keyClient = new KeyClient(vaultUri, clientSecretCredential);
KeyClient keyClient = new KeyClient(vaultUri, DataTestUtility.GetTokenCredential());
KeyVaultKey currentVersionKey = keyClient.GetKey(keyName);
KeyVaultKey specifiedVersionKey = keyClient.GetKey(keyName, keyVersion);

//If specified versioned key is the most recent version of the key then we cannot test.
if (!KeyIsLatestVersion(specifiedVersionKey, currentVersionKey))
{
SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential);
SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential());
// Perform an operation to initialize the internal caches
azureKeyProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVOriginalUrl, EncryptionAlgorithm, s_columnEncryptionKey);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ internal static X509Certificate2 CreateCertificate()

private static async Task SetupAKVKeysAsync()
{
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, clientSecretCredential);
KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential());
AsyncPageable<KeyProperties> keys = keyClient.GetPropertiesOfKeysAsync();
IAsyncEnumerator<KeyProperties> enumerator = keys.GetAsyncEnumerator();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,11 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public static class AADUtility
{
public static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
{
throw new Exception($"Failed to retrieve an access token for {resource}");
}

return result.AccessToken;
}

public static async Task<string> GetManagedIdentityToken(string clientId = null) =>
await new MockManagedIdentityTokenProvider().AcquireTokenAsync(clientId).ConfigureAwait(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
using Microsoft.Identity.Client;
using Microsoft.Data.SqlClient.TestUtilities;
using Xunit;
using Azure.Identity;
using Azure.Core;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
Expand All @@ -37,8 +39,6 @@ public static class DataTestUtility
public static readonly string AKVUrl = null;
public static readonly string AKVOriginalUrl = null;
public static readonly string AKVTenantId = null;
public static readonly string AKVClientId = null;
public static readonly string AKVClientSecret = null;
public static readonly string LocalDbAppName = null;
public static List<string> AEConnStrings = new List<string>();
public static List<string> AEConnStringsSetup = new List<string>();
Expand Down Expand Up @@ -122,8 +122,6 @@ static DataTestUtility()
}

AKVTenantId = c.AzureKeyVaultTenantId;
AKVClientId = c.AzureKeyVaultClientId;
AKVClientSecret = c.AzureKeyVaultClientSecret;

if (EnclaveEnabled)
{
Expand Down Expand Up @@ -316,7 +314,14 @@ public static bool IsNotAzureServer()
// Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse
public static bool IsAKVSetupAvailable()
{
return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(AKVClientId) && !string.IsNullOrEmpty(AKVClientSecret) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse();
return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse();
}

private static readonly DefaultAzureCredential s_defaultCredential = new(new DefaultAzureCredentialOptions { ManagedIdentityClientId = UserManagedIdentityClientId });

public static TokenCredential GetTokenCredential()
{
return s_defaultCredential;
}

public static bool IsUsingManagedSNI() => UseManagedSNIOnWindows;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
// See the LICENSE file in the project root for more information.

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Azure.Identity;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public class SqlClientCustomTokenCredential : TokenCredential
{
private const string DEFAULT_PREFIX = "/.default";

string _authority = "";
string _resource = "";
string _akvUrl = "";
Expand Down Expand Up @@ -70,40 +70,8 @@ private async Task<AccessToken> AcquireTokenAsync()
_akvUrl = DataTestUtility.AKVUrl;
}

string strAccessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource);
DateTime expiryTime = InterceptAccessTokenForExpiry(strAccessToken);
return new AccessToken(strAccessToken, new DateTimeOffset(expiryTime));
}

private DateTime InterceptAccessTokenForExpiry(string accessToken)
{
if (null == accessToken)
{
throw new ArgumentNullException(accessToken);
}

var jwtHandler = new JwtSecurityTokenHandler();
var jwtOutput = string.Empty;

// Check Token Format
if (!jwtHandler.CanReadToken(accessToken))
throw new FormatException(accessToken);

JwtSecurityToken token = jwtHandler.ReadJwtToken(accessToken);

// Re-serialize the Token Headers to just Key and Values
var jwtHeader = JsonConvert.SerializeObject(token.Header.Select(h => new { h.Key, h.Value }));
jwtOutput = $"{{\r\n\"Header\":\r\n{JToken.Parse(jwtHeader)},";

// Re-serialize the Token Claims to just Type and Values
var jwtPayload = JsonConvert.SerializeObject(token.Claims.Select(c => new { c.Type, c.Value }));
jwtOutput += $"\r\n\"Payload\":\r\n{JToken.Parse(jwtPayload)}\r\n}}";

// Output the whole thing to pretty JSON object formatted.
string jToken = JToken.Parse(jwtOutput).ToString(Formatting.Indented);
JToken payload = JObject.Parse(jToken).GetValue("Payload");

return new DateTime(1970, 1, 1).AddSeconds((long)payload[4]["Value"]);
AccessToken accessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource);
return accessToken;
}

private static string ValidateChallenge(string challenge)
Expand All @@ -127,16 +95,18 @@ private static string ValidateChallenge(string challenge)
/// <param name="authority">Authorization URL</param>
/// <param name="resource">Resource</param>
/// <returns></returns>
public static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource)
public static async Task<AccessToken> AzureActiveDirectoryAuthenticationCallback(string authority, string resource)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
{
throw new InvalidOperationException($"Failed to retrieve an access token for {resource}");
}
return result.AccessToken;
using CancellationTokenSource cts = new();
cts.CancelAfter(30000); // Hard coded for tests
string[] scopes = new string[] { resource + DEFAULT_PREFIX };
TokenRequestContext tokenRequestContext = new(scopes);
int separatorIndex = authority.LastIndexOf('/');
string authorityHost = authority.Remove(separatorIndex + 1);
string audience = authority.Substring(separatorIndex + 1);
TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions() { AuthorityHost = new Uri(authorityHost) };
AccessToken accessToken = await DataTestUtility.GetTokenCredential().GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false);
return accessToken;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,11 @@
<ItemGroup>
<ProjectReference Include="$(AddOnsPath)AzureKeyVaultProvider\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj" />
<PackageReference Include="Azure.Identity" Version="$(AzureIdentityVersion)" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="$(MicrosoftIdentityModelClientsActiveDirectoryVersion)" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="$(SystemConfigurationConfigurationManagerVersion)" />
<PackageReference Include="System.Runtime.Caching" Version="$(SystemRuntimeCachingVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkVersion)" />
<PackageReference Include="System.Linq.Expressions" Version="$(SystemLinqExpressionsVersion)" />
<PackageReference Include="System.Net.Sockets" Version="$(SystemNetSocketsVersion)" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="$(SystemIdentityModelTokensJwtVersion)" />
<PackageReference Condition="'$(TargetGroup)'=='netcoreapp' AND $(OS)=='Unix'" Include="Microsoft.Windows.Compatibility" Version="$(MicrosoftWindowsCompatibilityVersion)" />
<PackageReference Condition="'$(TargetGroup)'=='netcoreapp'" Include="Microsoft.DotNet.RemoteExecutor" Version="$(MicrosoftDotnetRemoteExecutorVersion)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ public class Config
public string AADServicePrincipalSecret = null;
public string AzureKeyVaultURL = null;
public string AzureKeyVaultTenantId = null;
public string AzureKeyVaultClientId = null;
public string AzureKeyVaultClientSecret = null;
public string LocalDbAppName = null;
public bool EnclaveEnabled = false;
public bool TracingEnabled = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
"AADServicePrincipalSecret": "",
"AzureKeyVaultURL": "",
"AzureKeyVaultTenantId": "",
"AzureKeyVaultClientId": "",
"AzureKeyVaultClientSecret": "",
"SupportsIntegratedSecurity": true,
"LocalDbAppName": "",
"SupportsFileStream": false,
Expand Down
2 changes: 0 additions & 2 deletions tools/props/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
<!-- Test Project Dependencies -->
<PropertyGroup>
<MicrosoftDotNetPlatformAbstractionsVersion>3.1.1</MicrosoftDotNetPlatformAbstractionsVersion>
<MicrosoftIdentityModelClientsActiveDirectoryVersion>5.2.6</MicrosoftIdentityModelClientsActiveDirectoryVersion>
<MicrosoftNETTestSdkVersion>15.9.0</MicrosoftNETTestSdkVersion>
<MicrosoftWindowsCompatibilityVersion>3.1.0</MicrosoftWindowsCompatibilityVersion>
<NewtonsoftJsonVersion>12.0.3</NewtonsoftJsonVersion>
Expand All @@ -69,7 +68,6 @@
<SystemDataOdbcVersion21>4.5.0</SystemDataOdbcVersion21>
<SystemDataOdbcVersion>4.6.0</SystemDataOdbcVersion>
<SystemNetSocketsVersion>4.3.0</SystemNetSocketsVersion>
<SystemIdentityModelTokensJwtVersion>6.8.0</SystemIdentityModelTokensJwtVersion>
<XunitVersion>2.4.1</XunitVersion>
<MicrosoftDotNetRemoteExecutorVersion>5.0.0-beta.20206.4</MicrosoftDotNetRemoteExecutorVersion>
<MicrosoftNETCoreRuntimeCoreCLRVersion>2.0.8</MicrosoftNETCoreRuntimeCoreCLRVersion>
Expand Down

0 comments on commit 39b13a6

Please sign in to comment.