Skip to content

Refactor lab API and remove certificate based auth #5023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
</ItemGroup>
<ItemGroup>
<!-- Packages for the tests -->
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.6.0" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
<PackageVersion Include="Azure.Identity.Broker" Version="1.2.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.6" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.6" />
<PackageVersion Include="coverlet.collector" Version="3.1.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public class CacheExecutionTests

private static async Task<LabUserData> GetPublicAadUserDataAsync()
{
var api = new LabServiceApi();
LabResponse labResponse = (await api.GetLabResponseFromApiAsync(UserQuery.PublicAadUserQuery).ConfigureAwait(false));
LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false);

return new LabUserData(
labResponse.User.Upn,
labResponse.User.GetOrFetchPassword(),
Expand Down
72 changes: 72 additions & 0 deletions tests/Microsoft.Identity.Test.Common/Core/Helpers/CertHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Identity.Test.Common.Core.Helpers
{
public static class CertificateFinder
{
/// <summary>
/// Try and locate a certificate matching the given <paramref name="subjectName"/> by searching in
/// the <see cref="StoreName.My"/> store subjectName for all available <see cref="StoreLocation"/>s.
/// </summary>
/// <param name="subjectName">Thumbprint of certificate to locate</param>
/// <returns><see cref="X509Certificate2"/> with <paramref subjectName="subjectName"/>, or null if no matching certificate was found</returns>
public static X509Certificate2 FindCertificateByName(string subjectName)
{
foreach (StoreLocation storeLocation in Enum.GetValues(typeof(StoreLocation)))
{
var certificate = FindCertificateByName(subjectName, storeLocation, StoreName.My);
if (certificate != null)
{
return certificate;
}
}

return null;
}
/// <summary>
/// Try and locate a certificate matching the given <paramref name="certName"/> by searching in
/// the in the given <see cref="StoreName"/> and <see cref="StoreLocation"/>.
/// </summary>
/// <param subjectName="certName">Thumbprint of certificate to locate</param>
/// <param subjectName="location"><see cref="StoreLocation"/> in which to search for a matching certificate</param>
/// <param subjectName="name"><see cref="StoreName"/> in which to search for a matching certificate</param>
/// <returns><see cref="X509Certificate2"/> with <paramref subjectName="certName"/>, or null if no matching certificate was found</returns>
public static X509Certificate2 FindCertificateByName(string certName, StoreLocation location, StoreName name)
{
// Don't validate certs, since the test root isn't installed.
const bool validateCerts = false;

using (var store = new X509Store(name, location))
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection collection = store.Certificates.Find(X509FindType.FindBySubjectName, certName, validateCerts);

X509Certificate2 certToUse = null;

// select the "freshest" certificate
foreach (X509Certificate2 cert in collection)
{
if (certToUse == null || cert.NotBefore > certToUse.NotBefore)
{
certToUse = cert;
}
}

return certToUse;

}
}
}

public enum KnownTestCertType
{
RSA,
ECD
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ public async Task ROPC_Ciam_StandardDomains_CompletesSuccessfully()
{
string authority;
//Get lab details
var labResponse = await LabUserHelper.GetLabUserDataAsync(new UserQuery()
{
FederationProvider = FederationProvider.CIAMCUD,
SignInAudience = SignInAudience.AzureAdMyOrg
}).ConfigureAwait(false);
var labResponse = await LabUserHelper.GetCiamUserAync().ConfigureAwait(false);

//https://tenantName.ciamlogin.com/
authority = string.Format("https://{0}.ciamlogin.com/", labResponse.User.LabName);
Expand Down Expand Up @@ -87,12 +83,7 @@ public async Task ClientCredentialCiam_WithClientCredentials_ReturnsValidTokens(
{
string authority;
//Get lab details
var labResponse = await LabUserHelper.GetLabUserDataAsync(new UserQuery()
{
FederationProvider = FederationProvider.CIAMCUD,
SignInAudience = SignInAudience.AzureAdMyOrg
}).ConfigureAwait(false);

var labResponse = await LabUserHelper.GetCiamUserAync().ConfigureAwait(false);

//https://tenantName.ciamlogin.com/
authority = string.Format("https://{0}.ciamlogin.com/", labResponse.User.LabName);
Expand All @@ -117,7 +108,7 @@ private async Task RunCiamCCATest(string authority, string appId)
//Acquire tokens
var msalConfidentialClientBuilder = ConfidentialClientApplicationBuilder
.Create(appId)
.WithCertificate(CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName))
.WithCertificate(CertificateFinder.FindCertificateByName(TestConstants.AutomationTestCertName))
.WithExperimentalFeatures();

if (authority.Contains(Constants.CiamAuthorityHostSuffix))
Expand Down Expand Up @@ -157,11 +148,7 @@ public async Task OBOCiam_CustomDomain_ReturnsValidTokens()
string ciamWebApi = "634de702-3173-4a71-b336-a4fab786a479";

//Get lab details
LabResponse labResponse = await LabUserHelper.GetLabUserDataAsync(new UserQuery()
{
FederationProvider = FederationProvider.CIAMCUD,
SignInAudience = SignInAudience.AzureAdMyOrg
}).ConfigureAwait(false);
var labResponse = await LabUserHelper.GetCiamUserAync().ConfigureAwait(false);

//Acquire tokens
var msalPublicClient = PublicClientApplicationBuilder
Expand All @@ -184,7 +171,7 @@ public async Task OBOCiam_CustomDomain_ReturnsValidTokens()
//Acquire tokens for OBO
var msalConfidentialClient = ConfidentialClientApplicationBuilder
.Create(ciamWebApi)
.WithCertificate(CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName))
.WithCertificate(CertificateFinder.FindCertificateByName(TestConstants.AutomationTestCertName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we create a new self signed cert for this purpose? and move away from the SNI cert for lab apps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still makes sense to have a single cert secure all our tests apps.

.WithAuthority(authorityCud, false)
.WithRedirectUri(_ciamRedirectUri)
.BuildConcrete();
Expand Down Expand Up @@ -212,11 +199,5 @@ public async Task OBOCiam_CustomDomain_ReturnsValidTokens()
Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
Assert.AreEqual(TokenSource.Cache, resultObo.AuthenticationResultMetadata.TokenSource);
}

private string GetCiamSecret()
{
KeyVaultSecretsProvider provider = new KeyVaultSecretsProvider();
return provider.GetSecretByName("msidlabciam2-cc").Value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@ public async Task RefreshOnIsEnabled(bool useRegional)
Assert.Inconclusive("Can't run regional on local devbox.");
}

var cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
var cert = CertificateFinder.FindCertificateByName(TestConstants.AutomationTestCertName);

var builder = ConfidentialClientApplicationBuilder.Create(LabAuthenticationHelper.LabAccessConfidentialClientId)
var builder = ConfidentialClientApplicationBuilder.Create(LabApiConstants.LabClientId)
.WithCertificate(cert, sendX5C: true)
.WithAuthority(LabAuthenticationHelper.LabClientInstance, LabAuthenticationHelper.LabClientTenantId);
.WithAuthority(LabApiConstants.LabClientInstance, LabApiConstants.LabClientTenantId);

// auto-detect should work on Azure DevOps build
if (useRegional)
builder = builder.WithAzureRegion();

var cca = builder.Build();

var result = await cca.AcquireTokenForClient([LabAuthenticationHelper.LabScope]).ExecuteAsync().ConfigureAwait(false);
var result = await cca.AcquireTokenForClient([LabApiConstants.LabScope]).ExecuteAsync().ConfigureAwait(false);

Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn.HasValue, "refresh_in was not issued - did the MSAL SKU value change?");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ public void CheckPopRuntimeBrokerSupportTest()

private static X509Certificate2 GetCertificate()
{
X509Certificate2 cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
X509Certificate2 cert = CertificateFinder.FindCertificateByName(TestConstants.AutomationTestCertName);

if (cert == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.LabInfrastructure;
using Microsoft.Identity.Test.Unit;

Expand Down Expand Up @@ -196,7 +197,7 @@ public static IConfidentialAppSettings GetSettings(Cloud cloud)

public static Lazy<X509Certificate2> GetCertificateLazy(string certName) => new Lazy<X509Certificate2>(() =>
{
X509Certificate2 cert = CertificateHelper.FindCertificateByName(certName);
X509Certificate2 cert = CertificateFinder.FindCertificateByName(certName);
if (cert == null)
{
throw new InvalidOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http;
using Microsoft.Identity.Test.LabInfrastructure;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ namespace Microsoft.Identity.Test.Integration.Infrastructure
public class PoPValidator
{
// This endpoint is hosted in the MSID Lab and is able to verify any pop token bound to an HTTP request
private const string PoPValidatorEndpoint = "https://signedhttprequest.azurewebsites.net/api/validateSHR";
private static HttpClient s_httpClient = new HttpClient();
private static Lazy<string> s_popValidationEndpointLazy = new Lazy<string>(
() => LabUserHelper.KeyVaultSecretsProviderMsal.GetSecretByName(
"automation-pop-validation-endpoint",
"841fc7c2ccdd48d7a9ef727e4ae84325").Value);

/// <summary>
/// This calls a special endpoint that validates any POP token against a configurable HTTP request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.Identity.Test.LabInfrastructure
{
public static class CertificateHelper
internal static class CertificateHelper
{
/// <summary>
/// Try and locate a certificate matching the given <paramref name="subjectName"/> by searching in
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class KeyVaultInstance
public const string MsalTeam = "https://buildautomation.vault.azure.net/";
}

public class KeyVaultSecretsProvider : IDisposable
public class KeyVaultSecretsProvider
{
private CertificateClient _certificateClient;
private SecretClient _secretClient;
Expand Down Expand Up @@ -58,43 +58,20 @@ public class KeyVaultSecretsProvider : IDisposable
/// </remarks>
public KeyVaultSecretsProvider(string keyVaultAddress = KeyVaultInstance.MSIDLab)
{
var credentials = GetKeyVaultCredentialAsync().GetAwaiter().GetResult();
var credential = LabAuthenticationHelper.GetTokenCredential();
var keyVaultAddressUri = new Uri(keyVaultAddress);
_certificateClient = new CertificateClient(keyVaultAddressUri, credentials);
_secretClient = new SecretClient(keyVaultAddressUri, credentials);
}

~KeyVaultSecretsProvider()
{
Dispose();
_certificateClient = new CertificateClient(keyVaultAddressUri, credential);
_secretClient = new SecretClient(keyVaultAddressUri, credential);
}

public KeyVaultSecret GetSecretByName(string secretName)
{
return _secretClient.GetSecret(secretName).Value;
}

public KeyVaultSecret GetSecretByName(string secretName, string secretVersion)
{
return _secretClient.GetSecret(secretName, secretVersion).Value;
}


public async Task<X509Certificate2> GetCertificateWithPrivateMaterialAsync(string certName)
{
return await _certificateClient.DownloadCertificateAsync(certName).ConfigureAwait(false);
}

private async Task<TokenCredential> GetKeyVaultCredentialAsync()
{
var accessToken = await LabAuthenticationHelper.GetLabAccessTokenAsync(
"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/",
new[] { "https://vault.azure.net/.default" }).ConfigureAwait(false);
return DelegatedTokenCredential.Create((_, __) => accessToken);
}

public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@

namespace Microsoft.Identity.Test.LabInfrastructure
{
public class LabApiConstants
public static class LabApiConstants
{
public const string LabClientId = "f62c5ae3-bf3a-4af5-afa8-a68b800396e9";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we move to use Visual Studio credential for DevBox and UAMI based acccess for CI, we do not need this app flow anymore

public const string LabScope = "https://request.msidlab.com/.default";
public const string LabClientInstance = "https://login.microsoftonline.com/";
public const string LabClientTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
}

internal static class InternalConstants
{
// constants for Lab api
public const string MobileDeviceManagementWithConditionalAccess = "mdmca";
Expand Down
Loading
Loading