diff --git a/eng/.docsettings.yml b/eng/.docsettings.yml index ca21a1741d9fc..ba11e8494604a 100644 --- a/eng/.docsettings.yml +++ b/eng/.docsettings.yml @@ -9,6 +9,9 @@ omitted_paths: - samples/* - sdk/*/*.Management.*/* - sdk/*/*/perf/* + - sdk/*/*/integration/* + - sdk/*/*/tests/Samples/* + - sdk/*/*/tests/samples/* - sdk/*/*/samples/* - sdk/*/samples/* - sdk/*/swagger/* diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 8e7dc2ecb658c..cefa93d71937c 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -102,15 +102,15 @@ - - - - + + + + - + diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index ea6cc121c2021..d8f5cd3b4c84e 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -1,4 +1,9 @@ # Release History +## 1.8.2 (2023-02-08) + +### Bugs Fixed +- Fixed error message parsing in `AzurePowerShellCredential` which would misinterpret AAD errors with the need to install PowerShell. [#31998](https://github.com/Azure/azure-sdk-for-net/issues/31998) +- Fix regional endpoint validation error when using `ManagedIdentityCredential`. [#32498])(https://github.com/Azure/azure-sdk-for-net/issues/32498) ## 1.8.1 (2023-01-13) diff --git a/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs b/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs new file mode 100644 index 0000000000000..0e792196f251a --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Azure.Core; +using Azure.Identity; +using Azure.Storage.Blobs; +using Microsoft.AspNetCore.Mvc; + +namespace WebApp.Controllers +{ + + [ApiController] + [Route("[controller]")] + public class TestController : ControllerBase + { + + [HttpGet(Name = "GetTest")] + public IActionResult Get() + { + string resourceId = Environment.GetEnvironmentVariable("IDENTITY_WEBAPP_USER_DEFINED_IDENTITY")!; + string account1 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_1")!; + string account2 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_2")!; + + var credential1 = new ManagedIdentityCredential(); + var credential2 = new ManagedIdentityCredential(new ResourceIdentifier(resourceId)); + var client1 = new BlobServiceClient(new Uri($"https://{account1}.blob.core.windows.net/"), credential1); + var client2 = new BlobServiceClient(new Uri($"https://{account2}.blob.core.windows.net/"), credential2); + try + { + var results = client1.GetBlobContainers().ToList(); + results = client2.GetBlobContainers().ToList(); + return Ok("Successfully acquired a token from ManagedIdentityCredential"); + } + catch (Exception ex) + { + return BadRequest(ex.ToString()); + } + } + } +} diff --git a/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj b/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj new file mode 100644 index 0000000000000..8460903024cfe --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/sdk/identity/Azure.Identity/integration/WebApp/Program.cs b/sdk/identity/Azure.Identity/integration/WebApp/Program.cs new file mode 100644 index 0000000000000..70c0b74c91a90 --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/WebApp/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/sdk/identity/Azure.Identity/integration/WebApp/appsettings.Development.json b/sdk/identity/Azure.Identity/integration/WebApp/appsettings.Development.json new file mode 100644 index 0000000000000..0c208ae9181e5 --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/WebApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/sdk/identity/Azure.Identity/integration/WebApp/appsettings.json b/sdk/identity/Azure.Identity/integration/WebApp/appsettings.json new file mode 100644 index 0000000000000..10f68b8c8b4f7 --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/WebApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/sdk/identity/Azure.Identity/integration/nuget.config b/sdk/identity/Azure.Identity/integration/nuget.config new file mode 100644 index 0000000000000..9ac17c067abd9 --- /dev/null +++ b/sdk/identity/Azure.Identity/integration/nuget.config @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index 1a86651681371..7c2723fb7c0bc 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -2,9 +2,9 @@ This is the implementation of the Azure SDK Client Library for Azure Identity Microsoft Azure.Identity Component - 1.8.1 + 1.8.2 - 1.8.0 + 1.8.1 Microsoft Azure Identity;$(PackageCommonTags) $(RequiredTargetFrameworks) $(NoWarn);3021;AZC0011 diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs index 6a51c09fdc40d..c7d61aadf5980 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs @@ -27,7 +27,7 @@ public class AzurePowerShellCredential : TokenCredential internal bool UseLegacyPowerShell { get; set; } private const string Troubleshooting = "See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/powershellcredential/troubleshoot"; - private const string AzurePowerShellFailedError = "Azure PowerShell authentication failed due to an unknown error. " + Troubleshooting; + internal const string AzurePowerShellFailedError = "Azure PowerShell authentication failed due to an unknown error. " + Troubleshooting; private const string RunConnectAzAccountToLogin = "Run Connect-AzAccount to login"; private const string NoAccountsWereFoundInTheCache = "No accounts were found in the cache"; private const string CannotRetrieveAccessToken = "cannot retrieve access token"; @@ -170,8 +170,10 @@ private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool private static void CheckForErrors(string output) { - bool noPowerShell = output.IndexOf("not found", StringComparison.OrdinalIgnoreCase) != -1 || - output.IndexOf("is not recognized", StringComparison.OrdinalIgnoreCase) != -1; + bool noPowerShell = (output.IndexOf("not found", StringComparison.OrdinalIgnoreCase) != -1 || + output.IndexOf("is not recognized", StringComparison.OrdinalIgnoreCase) != -1) && + // If the error contains AADSTS, this should be treated as a general error to be bubbled to the user + output.IndexOf("AADSTS", StringComparison.OrdinalIgnoreCase) == -1; if (noPowerShell) { throw new CredentialUnavailableException(PowerShellNotInstalledError); diff --git a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs index e45dd7c812d08..5b7779a9cfb14 100644 --- a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs @@ -12,7 +12,6 @@ namespace Azure.Identity { internal class MsalConfidentialClient : MsalClientBase { - private const string s_instanceMetadata = "{\"tenant_discovery_endpoint\":\"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration\",\"api-version\":\"1.1\",\"metadata\":[{\"preferred_network\":\"login.microsoftonline.com\",\"preferred_cache\":\"login.windows.net\",\"aliases\":[\"login.microsoftonline.com\",\"login.windows.net\",\"login.microsoft.com\",\"sts.windows.net\"]}]}"; internal readonly string _clientSecret; internal readonly bool _includeX5CClaimHeader; internal readonly IX509Certificate2Provider _certificateProvider; @@ -76,7 +75,7 @@ protected override async ValueTask CreateClientA { confClientBuilder.WithAppTokenProvider(_appTokenProviderCallback) .WithAuthority(_authority.AbsoluteUri, TenantId, false) - .WithInstanceDiscoveryMetadata(s_instanceMetadata); + .WithInstanceDiscovery(false); } else { @@ -104,6 +103,7 @@ protected override async ValueTask CreateClientA confClientBuilder.WithCertificate(clientCertificate); } + // When the appTokenProviderCallback is set, meaning this is for managed identity, the regional authority is not relevant. if (_appTokenProviderCallback == null && !string.IsNullOrEmpty(RegionalAuthority)) { confClientBuilder.WithAzureRegion(RegionalAuthority); diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index a812955a46934..ebbfd9c12c1f4 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -93,21 +93,25 @@ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestPara private static IEnumerable ErrorScenarios() { - yield return new object[] { "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError }; - yield return new object[] { "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError }; - yield return new object[] { "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError }; - yield return new object[] { "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError }; - yield return new object[] { "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError }; + yield return new object[] { "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError, typeof(CredentialUnavailableException) }; + yield return new object[] { "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { + "AADSTS500011: The resource principal named was not found in the tenant named", + AzurePowerShellCredential.AzurePowerShellFailedError + " AADSTS500011: The resource principal named was not found in the tenant named", + typeof(AuthenticationFailedException) }; } [Test] [TestCaseSource(nameof(ErrorScenarios))] - public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios(string errorMessage, string expectedError) + public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios(string errorMessage, string expectedError, Type expectedException) { var testProcess = new TestProcess { Error = errorMessage }; AzurePowerShellCredential credential = InstrumentClient( new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); - var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + var ex = Assert.ThrowsAsync(expectedException, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); Assert.AreEqual(expectedError, ex.Message); } diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialLiveTests.cs index a80a3df680ea4..7304410143d1b 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialLiveTests.cs @@ -7,6 +7,7 @@ using Azure.Core; using Azure.Core.TestFramework; using Microsoft.Identity.Client; +using Moq; using NUnit.Framework; namespace Azure.Identity.Tests diff --git a/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs b/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs index fc12b9afcb90b..1b748a5c3ac1b 100644 --- a/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs +++ b/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs @@ -82,12 +82,12 @@ public static TestFileSystemService CreateFileSystemForVisualStudioCode(Identity { var sb = new StringBuilder("{"); - if (testEnvironment.TestTenantId != default) + if (testEnvironment.IdentityTenantId != default) { - sb.AppendFormat("\"azure.tenant\": \"{0}\"", testEnvironment.TestTenantId); + sb.AppendFormat("\"azure.tenant\": \"{0}\"", testEnvironment.IdentityTenantId); } - if (testEnvironment.TestTenantId != default && cloudName != default) + if (testEnvironment.IdentityTenantId != default && cloudName != default) { sb.Append(','); } @@ -117,7 +117,7 @@ public static async ValueTask GetAuthenticationRecordAsync var username = testEnvironment.Username; var password = testEnvironment.Password; - var tenantId = testEnvironment.TestTenantId; + var tenantId = testEnvironment.IdentityTenantId; var result = await PublicClientApplicationBuilder.Create(clientId) .WithTenantId(tenantId) @@ -138,7 +138,7 @@ public static async Task GetRefreshTokenAsync(IdentityTestEnvironment te var clientId = "aebc6443-996d-45c2-90f0-388ff96faa56"; var username = testEnvironment.Username; var password = testEnvironment.Password; - var authorityUri = new Uri(new Uri(testEnvironment.AuthorityHostUrl), testEnvironment.TestTenantId).ToString(); + var authorityUri = new Uri(new Uri(testEnvironment.AuthorityHostUrl), testEnvironment.IdentityTenantId).ToString(); var client = PublicClientApplicationBuilder.Create(clientId) .WithAuthority(authorityUri) diff --git a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs index 8ef5f8aca62d8..a89aaf08def5d 100644 --- a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs @@ -62,6 +62,7 @@ public async Task DefaultAzureCredential_UseVisualStudioCredential() [RecordedTest] [RunOnlyOnPlatforms(Windows = true, OSX = true, ContainerNames = new[] { "ubuntu_netcore_keyring" })] + [Ignore("https://github.com/Azure/azure-sdk-for-net/issues/27263")] public async Task DefaultAzureCredential_UseVisualStudioCodeCredential() { var options = InstrumentClientOptions(new DefaultAzureCredentialOptions @@ -73,7 +74,7 @@ public async Task DefaultAzureCredential_UseVisualStudioCodeCredential() ExcludeVisualStudioCredential = true, ExcludeAzureCliCredential = true, ExcludeVisualStudioCodeCredential = false, - VisualStudioCodeTenantId = TestEnvironment.TestTenantId + VisualStudioCodeTenantId = TestEnvironment.IdentityTenantId }); var cloudName = Guid.NewGuid().ToString(); @@ -102,6 +103,7 @@ public async Task DefaultAzureCredential_UseVisualStudioCodeCredential() [RecordedTest] [RunOnlyOnPlatforms(Windows = true, OSX = true, ContainerNames = new[] { "ubuntu_netcore_keyring" })] + [Ignore("https://github.com/Azure/azure-sdk-for-net/issues/27263")] public async Task DefaultAzureCredential_UseVisualStudioCodeCredential_ParallelCalls() { var options = InstrumentClientOptions(new DefaultAzureCredentialOptions @@ -112,7 +114,7 @@ public async Task DefaultAzureCredential_UseVisualStudioCodeCredential_ParallelC ExcludeManagedIdentityCredential = true, ExcludeAzureCliCredential = true, ExcludeVisualStudioCodeCredential = false, - VisualStudioCodeTenantId = TestEnvironment.TestTenantId + VisualStudioCodeTenantId = TestEnvironment.IdentityTenantId }); var cloudName = Guid.NewGuid().ToString(); @@ -149,7 +151,7 @@ public async Task DefaultAzureCredential_UseAzureCliCredential() ExcludeSharedTokenCacheCredential = true, ExcludeManagedIdentityCredential = true, ExcludeVisualStudioCodeCredential = false, - VisualStudioCodeTenantId = TestEnvironment.TestTenantId + VisualStudioCodeTenantId = TestEnvironment.IdentityTenantId }); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); @@ -188,7 +190,7 @@ public async Task DefaultAzureCredential_UseAzureCliCredential_ParallelCalls() ExcludeSharedTokenCacheCredential = true, ExcludeManagedIdentityCredential = true, ExcludeVisualStudioCodeCredential = false, - VisualStudioCodeTenantId = TestEnvironment.TestTenantId + VisualStudioCodeTenantId = TestEnvironment.IdentityTenantId }); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); diff --git a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs index 671d49c9c899f..a760018dc9a4d 100644 --- a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs +++ b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs @@ -30,7 +30,6 @@ public class IdentityTestEnvironment : TestEnvironment public string UserAssignedVault => GetRecordedOptionalVariable("IDENTITYTEST_IMDSTEST_USERASSIGNEDVAULT"); public string TestPassword => GetOptionalVariable("AZURE_IDENTITY_TEST_PASSWORD") ?? "SANITIZED"; - public string TestTenantId => GetRecordedOptionalVariable("TENANT_ID") ?? GetRecordedVariable("AZURE_IDENTITY_TEST_TENANTID"); public string KeyvaultScope => GetRecordedOptionalVariable("AZURE_KEYVAULT_SCOPE") ?? "https://vault.azure.net/.default"; public string ServicePrincipalClientId => GetRecordedVariable("IDENTITY_SP_CLIENT_ID"); @@ -39,5 +38,6 @@ public class IdentityTestEnvironment : TestEnvironment public string ServicePrincipalCertificatePfxPath => GetOptionalVariable("IDENTITY_SP_CERT_PFX") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); public string ServicePrincipalCertificatePemPath => GetOptionalVariable("IDENTITY_SP_CERT_PEM") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); public string ServicePrincipalSniCertificatePath => GetOptionalVariable("IDENTITY_SP_CERT_SNI") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); + public string IdentityTestWebName => GetRecordedVariable("IDENTITY_WEBAPP_NAME"); } } diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs index 69a7c8455d272..9401753977704 100644 --- a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs @@ -109,10 +109,9 @@ public async Task VerifyImdsRequestWithClientIdMockAsync() [TestCase("westus")] public async Task VerifyImdsRequestWithClientIdAndRegionalAuthorityNameMockAsync(string regionName) { - using var environment = new TestEnvVar(new() { {"AZURE_REGIONAL_AUTHORITY_NAME", regionName}, {"MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } }); + using var environment = new TestEnvVar(new() { { "AZURE_REGIONAL_AUTHORITY_NAME", regionName }, { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } }); - var response = CreateMockResponse(200, ExpectedToken); - var mockTransport = new MockTransport(response); + var mockTransport = new MockTransport(req => CreateMockResponse(200, ExpectedToken)); var options = new TokenCredentialOptions() { Transport = mockTransport }; var pipeline = CredentialPipeline.GetInstance(options); @@ -121,18 +120,6 @@ public async Task VerifyImdsRequestWithClientIdAndRegionalAuthorityNameMockAsync AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); Assert.AreEqual(ExpectedToken, actualToken.Token); - - MockRequest request = mockTransport.Requests[0]; - - string query = request.Uri.Query; - - Assert.AreEqual(request.Uri.Host, "169.254.169.254"); - Assert.AreEqual(request.Uri.Path, "/metadata/identity/oauth2/token"); - Assert.IsTrue(query.Contains("api-version=2018-02-01")); - Assert.IsTrue(query.Contains($"resource={Uri.EscapeDataString(ScopeUtilities.ScopesToResource(MockScopes.Default))}")); - Assert.IsTrue(request.Headers.TryGetValue("Metadata", out string metadataValue)); - Assert.IsTrue(query.Contains($"{Constants.ManagedIdentityClientId}=mock-client-id")); - Assert.AreEqual("true", metadataValue); } [NonParallelizable] @@ -147,9 +134,12 @@ public async Task VerifyImdsRequestWithClientIdAndNonPubCloudMockAsync(Uri autho var options = new TokenCredentialOptions() { Transport = mockTransport, AuthorityHost = authority }; //var pipeline = CredentialPipeline.GetInstance(options); var _pipeline = new HttpPipeline(mockTransport); - var pipeline = new CredentialPipeline(authority, _pipeline, new ClientDiagnostics(options)); + var pipeline = new CredentialPipeline(authority, _pipeline, new ClientDiagnostics(options)); - ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(new ManagedIdentityClient( pipeline, "mock-client-id"))); + ManagedIdentityCredential credential = InstrumentClient( + new ManagedIdentityCredential( + new ManagedIdentityClient( + new ManagedIdentityClientOptions { Pipeline = pipeline, ClientId = "mock-client-id", Options = options }))); AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); @@ -696,10 +686,11 @@ public async Task VerifyInitialImdsConnectionTimeoutHonored() var startTime = DateTimeOffset.UtcNow; var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + var endTime = DateTimeOffset.UtcNow; Assert.That(ex.Message, Does.Contain(ImdsManagedIdentitySource.AggregateError)); - Assert.Less(DateTimeOffset.UtcNow - startTime, TimeSpan.FromSeconds(2)); + Assert.Less(endTime - startTime, TimeSpan.FromSeconds(2)); await Task.CompletedTask; } @@ -857,7 +848,6 @@ public static IEnumerable AuthorityHostValues() yield return new object[] { AzureAuthorityHosts.AzureGermany }; yield return new object[] { AzureAuthorityHosts.AzureGovernment }; yield return new object[] { AzureAuthorityHosts.AzurePublicCloud }; - yield return new object[] { new Uri("https://foo.bar") }; } private MockResponse CreateMockResponse(int responseCode, string token) diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs new file mode 100644 index 0000000000000..8a7d7ed4e3ed3 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Identity.Tests +{ + public class ManagedIdentityCredentialWebAppTests : IdentityRecordedTestBase + { + private HttpPipeline _pipeline; + private Uri _testEndpoint; + + public ManagedIdentityCredentialWebAppTests(bool isAsync) : base(isAsync) + { + } + + [SetUp] + public void Setup() { + var options = new TokenCredentialOptions(); + _testEndpoint = new Uri($"https://{TestEnvironment.IdentityTestWebName}.azurewebsites.net/test"); + _pipeline = HttpPipelineBuilder.Build(InstrumentClientOptions(options), Array.Empty(), Array.Empty(), new ResponseClassifier()); + } + + [RecordedTest] + [SyncOnly] + // This test leverages the test app found in Azure.Identity\integration\WebApp + // It validates that ManagedIdentityCredential can acquire a token in an actual Azure Web App environment + public async Task CallTestWebApp() + { + Request request = _pipeline.CreateRequest(); + request.Uri.Reset(_testEndpoint); + Response response = await _pipeline.SendRequestAsync(request, default); + + Assert.AreEqual((int)HttpStatusCode.OK, response.Status); + Assert.AreEqual("Successfully acquired a token from ManagedIdentityCredential", response.Content.ToString(), response.Content.ToString()); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/SessionRecords/ManagedIdentityCredentialWebAppTests/CallTestWebApp.json b/sdk/identity/Azure.Identity/tests/SessionRecords/ManagedIdentityCredentialWebAppTests/CallTestWebApp.json new file mode 100644 index 0000000000000..44e3adaebda69 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/SessionRecords/ManagedIdentityCredentialWebAppTests/CallTestWebApp.json @@ -0,0 +1,31 @@ +{ + "Entries": [ + { + "RequestUri": "https://chrissidentity-webapp.azurewebsites.net/test", + "RequestMethod": "GET", + "RequestHeaders": { + "User-Agent": "azsdk-net-Identity/1.9.0-alpha.20221205.1 (.NET 6.0.11; Microsoft Windows 10.0.22621)", + "x-ms-client-request-id": "da316510b79eb7bb601cb0177206c845", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Content-Type": "text/plain; charset=utf-8", + "Date": "Mon, 05 Dec 2022 21:01:43 GMT", + "Server": "Microsoft-IIS/10.0", + "Set-Cookie": [ + "ARRAffinity=79e06db539acb57119e709978d2cf1da299e8341753d6f6345007fcab3f69bc5;Path=/;HttpOnly;Secure;Domain=chrissidentity-webapp.azurewebsites.net", + "ARRAffinitySameSite=79e06db539acb57119e709978d2cf1da299e8341753d6f6345007fcab3f69bc5;Path=/;HttpOnly;SameSite=None;Secure;Domain=chrissidentity-webapp.azurewebsites.net" + ], + "Transfer-Encoding": "chunked", + "X-Powered-By": "ASP.NET" + }, + "ResponseBody": "Successfully acquired a token from ManagedIdentityCredential" + } + ], + "Variables": { + "IDENTITY_WEBAPP_NAME": "chrissidentity-webapp", + "RandomSeed": "958608913" + } +} diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialLiveTests.cs index cb29fc9b7b20f..31021d4609e63 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialLiveTests.cs @@ -11,6 +11,7 @@ namespace Azure.Identity.Tests { + [Ignore("https://github.com/Azure/azure-sdk-for-net/issues/27020")] public class VisualStudioCodeCredentialLiveTests : IdentityRecordedTestBase { private const string ExpectedServiceName = "VS Code Azure"; @@ -28,7 +29,7 @@ public async Task AuthenticateWithVscCredential() var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment, cloudName); using IDisposable fixture = await CredentialTestHelpers.CreateRefreshTokenFixtureAsync(TestEnvironment, Mode, ExpectedServiceName, cloudName); - var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.TestTenantId }); + var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.IdentityTenantId }); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options, default, default, fileSystem, default)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(new[] { TestEnvironment.KeyvaultScope}), CancellationToken.None); Assert.IsNotNull(token.Token); @@ -41,7 +42,7 @@ public async Task AuthenticateWithVscCredential_NoSettingsFile() var fileSystemService = new TestFileSystemService { ReadAllHandler = s => throw new FileNotFoundException() }; var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", refreshToken); - var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.TestTenantId }); + var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.IdentityTenantId }); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options, default, default, fileSystemService, vscAdapter)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(new[] { TestEnvironment.KeyvaultScope}), CancellationToken.None); Assert.IsNotNull(token.Token); @@ -54,7 +55,7 @@ public async Task AuthenticateWithVscCredential_BrokenSettingsFile() var fileSystemService = new TestFileSystemService { ReadAllHandler = s => "{a,}" }; var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", refreshToken); - var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.TestTenantId }); + var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.IdentityTenantId }); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options, default, default, fileSystemService, vscAdapter)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(new[] {TestEnvironment.KeyvaultScope}), CancellationToken.None); Assert.IsNotNull(token.Token); @@ -67,7 +68,7 @@ public async Task AuthenticateWithVscCredential_EmptySettingsFile() var fileSystemService = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment); var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", refreshToken); - var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.TestTenantId }); + var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.IdentityTenantId }); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options, default, default, fileSystemService, vscAdapter)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(new[] {TestEnvironment.KeyvaultScope}), CancellationToken.None); @@ -97,7 +98,7 @@ public void AuthenticateWithVscCredential_NoVscInstalled() var cloudName = Guid.NewGuid().ToString(); var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment, cloudName); - var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.TestTenantId }); + var options = InstrumentClientOptions(new VisualStudioCodeCredentialOptions { TenantId = TestEnvironment.IdentityTenantId }); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options, default, default, fileSystem, default)); Assert.CatchAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(new[] {TestEnvironment.KeyvaultScope}), CancellationToken.None)); @@ -106,7 +107,7 @@ public void AuthenticateWithVscCredential_NoVscInstalled() [Test] public void AuthenticateWithVscCredential_NoRefreshToken() { - var tenantId = TestEnvironment.TestTenantId; + var tenantId = TestEnvironment.IdentityTenantId; var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", null); var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment); @@ -119,7 +120,7 @@ public void AuthenticateWithVscCredential_NoRefreshToken() [Test] public void AuthenticateWithVscCredential_AuthenticationCodeInsteadOfRefreshToken() { - var tenantId = TestEnvironment.TestTenantId; + var tenantId = TestEnvironment.IdentityTenantId; var fileSystemService = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment); var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", "{}"); @@ -132,7 +133,7 @@ public void AuthenticateWithVscCredential_AuthenticationCodeInsteadOfRefreshToke [Test] public void AuthenticateWithVscCredential_InvalidRefreshToken() { - var tenantId = TestEnvironment.TestTenantId; + var tenantId = TestEnvironment.IdentityTenantId; var fileSystemService = CredentialTestHelpers.CreateFileSystemForVisualStudioCode(TestEnvironment); var vscAdapter = new TestVscAdapter(ExpectedServiceName, "AzureCloud", Guid.NewGuid().ToString()); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index c5f969eed8852..0ac114ef00a9c 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -12,6 +12,7 @@ namespace Azure.Identity.Tests { + [Ignore("https://github.com/Azure/azure-sdk-for-net/issues/27263")] public class VisualStudioCodeCredentialTests : CredentialTestBase { public VisualStudioCodeCredentialTests(bool isAsync) : base(isAsync) @@ -19,7 +20,7 @@ public VisualStudioCodeCredentialTests(bool isAsync) : base(isAsync) public override TokenCredential GetTokenCredential(TokenCredentialOptions options) { - using var env = new TestEnvVar(new Dictionary { { "TENANT_ID", TenantId } }); + using var env = new TestEnvVar(new Dictionary { { "IDENTITY_TENANT_ID", TenantId } }); var environment = new IdentityTestEnvironment(); var vscOptions = new VisualStudioCodeCredentialOptions { @@ -47,7 +48,7 @@ public void Setup() [NonParallelizable] public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { - using var env = new TestEnvVar(new Dictionary { { "TENANT_ID", TenantId } }); + using var env = new TestEnvVar(new Dictionary { { "IDENTITY_TENANT_ID", TenantId } }); var environment = new IdentityTestEnvironment(); var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, AdditionallyAllowedTenants = { TenantIdHint }, Transport = new MockTransport() }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); @@ -71,7 +72,7 @@ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestPara { Console.WriteLine(parameters.ToDebugString()); - using var env = new TestEnvVar(new Dictionary { { "TENANT_ID", TenantId } }); + using var env = new TestEnvVar(new Dictionary { { "IDENTITY_TENANT_ID", TenantId } }); var environment = new IdentityTestEnvironment(); var options = new VisualStudioCodeCredentialOptions { @@ -86,7 +87,7 @@ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestPara var msalClientMock = new MockMsalPublicClient(AuthenticationResultFactory.Create()); - var cred = InstrumentClient( + var cred = InstrumentClient( new VisualStudioCodeCredential( options, null, diff --git a/sdk/identity/test-resources-post.ps1 b/sdk/identity/test-resources-post.ps1 new file mode 100644 index 0000000000000..cc8637d9a4e3e --- /dev/null +++ b/sdk/identity/test-resources-post.ps1 @@ -0,0 +1,19 @@ +param ( + [hashtable] $DeploymentOutputs +) + +$webappRoot = "$PSScriptRoot/Azure.Identity/integration" | Resolve-Path +$workingFolder = $webappRoot; +if ($null -ne $Env:AGENT_WORKFOLDER) { + $workingFolder = $Env:AGENT_WORKFOLDER +} +az login --service-principal -u $DeploymentOutputs['IDENTITY_CLIENT_ID'] -p $DeploymentOutputs['IDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['IDENTITY_TENANT_ID'] +az account set --subscription $DeploymentOutputs['IDENTITY_SUBSCRIPTION_ID'] +dotnet publish "$webappRoot/WebApp/Integration.Identity.WebApp.csproj" -o "$workingFolder/Pub" /p:EnableSourceLink=false +Compress-Archive -Path "$workingFolder/Pub/*" -DestinationPath "$workingFolder/Pub/package.zip" -Force +az webapp deploy --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --name $DeploymentOutputs['IDENTITY_WEBAPP_NAME'] --src-path "$workingFolder/Pub/package.zip" +Remove-Item -Force -Recurse "$workingFolder/Pub" +if ($null -eq $Env:AGENT_WORKFOLDER) { + Remove-Item -Force -Recurse "$webappRoot/%AGENT_WORKFOLDER%" +} +az logout \ No newline at end of file diff --git a/sdk/identity/test-resources.bicep b/sdk/identity/test-resources.bicep new file mode 100644 index 0000000000000..b175d52a5e5ee --- /dev/null +++ b/sdk/identity/test-resources.bicep @@ -0,0 +1,133 @@ +@description('The client OID to grant access to test resources.') +param testApplicationOid string + +@minLength(6) +@maxLength(50) +@description('The base resource name.') +param baseName string = resourceGroup().name + +@description('The location of the resource. By default, this is the same as the resource group.') +param location string = resourceGroup().location + +//See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles +var blobContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') //Storage Blob Data Contributor +var websiteContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') //Website Contributor + +resource usermgdid 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: baseName + location: location +} + +resource blobRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: sa + name: guid(resourceGroup().id, blobContributor) + properties: { + principalId: web.identity.principalId + roleDefinitionId: blobContributor + principalType: 'ServicePrincipal' + } +} + +resource blobRole2 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: sa2 + name: guid(resourceGroup().id, blobContributor, usermgdid.id) + properties: { + principalId: usermgdid.properties.principalId + roleDefinitionId: blobContributor + principalType: 'ServicePrincipal' + } +} + +resource webRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: web + name: guid(resourceGroup().id, websiteContributor) + properties: { + principalId: testApplicationOid + roleDefinitionId: websiteContributor + principalType: 'ServicePrincipal' + } +} + +resource sa 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: baseName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + accessTier: 'Hot' + } +} + +resource sa2 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: '${baseName}2' + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + accessTier: 'Hot' + } +} + +resource farm 'Microsoft.Web/serverfarms@2021-03-01' = { + name: '${baseName}_asp' + location: location + sku: { + name: 'F1' + tier: 'Free' + size: 'F1' + family: 'F' + capacity: 0 + } + properties: { } + kind: 'app' +} + +resource web 'Microsoft.Web/sites@2021-03-01' = { + name: '${baseName}-webapp' + location: location + kind: 'app' + identity: { + type: 'SystemAssigned, UserAssigned' + userAssignedIdentities: { + '${usermgdid.id}' : { } + } + } + properties: { + enabled: true + serverFarmId: farm.id + httpsOnly: true + keyVaultReferenceIdentity: 'SystemAssigned' + siteConfig: { + netFrameworkVersion: 'v6.0' + http20Enabled: true + minTlsVersion: '1.2' + appSettings: [ + { + name: 'AZURE_REGIONAL_AUTHORITY_NAME' + value: 'eastus' + } + { + name: 'IDENTITY_STORAGE_NAME_1' + value: sa.name + } + { + name: 'IDENTITY_STORAGE_NAME_2' + value: sa2.name + } + { + name: 'IDENTITY_WEBAPP_USER_DEFINED_IDENTITY' + value: usermgdid.id + } + ] + } + } +} + +output IDENTITY_WEBAPP_NAME string = web.name +output IDENTITY_WEBAPP_USER_DEFINED_IDENTITY string = usermgdid.id +output IDENTITY_STORAGE_NAME_1 string = sa.name +output IDENTITY_STORAGE_NAME_2 string = sa2.name diff --git a/sdk/identity/tests.yml b/sdk/identity/tests.yml index 764ca96da07b2..2a2bdfd0e908d 100644 --- a/sdk/identity/tests.yml +++ b/sdk/identity/tests.yml @@ -1,36 +1,12 @@ trigger: none -resources: - containers: - - container: 'ubuntu_netcore_keyring' - # See ./eng/containers/UbuntuNetCoreKeyring/Dockerfile for tool versions - image: 'azsdkengsys.azurecr.io/dotnet/ubuntu_netcore_keyring:1347171' - endpoint: 'azsdkengsys' - options: -ti --cap-add=IPC_LOCK - extends: template: ../../eng/pipelines/templates/stages/archetype-sdk-tests.yml parameters: TimeoutInMinutes: 120 - AdditionalMatrixConfigs: - - Name: identity_container - Path: sdk/identity/platform-matrix.json - Selection: sparse - GenerateContainerJobs: true ServiceDirectory: identity SupportedClouds: 'Public,UsGov,China,Canary' PreSteps: - - pwsh: Install-Module -Name Az -Scope CurrentUser -AllowClobber -Force -Verbose - displayName: Install Azure PowerShell module - condition: and(succeededOrFailed(), startsWith(variables['Agent.JobName'], 'ubuntu_keyring_container')) - - script: | - set -x - export $(dbus-launch) - gnome-keyring-daemon --start --daemonize --components=secrets - echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_SESSION_BUS_ADDRESS" - echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_PID]$DBUS_SESSION_BUS_PID" - echo "##vso[task.setvariable variable=GNOME_KEYRING_CONTROL]$GNOME_KEYRING_CONTROL" - condition: and(succeededOrFailed(), startsWith(variables['Agent.JobName'], 'ubuntu_keyring_container')) - pwsh: | [System.Convert]::FromBase64String($env:PFX_CONTENTS) | Set-Content -Path $(Agent.TempDirectory)/test.pfx -AsByteStream Set-Content -Path $(Agent.TempDirectory)/test.pem -Value $env:PEM_CONTENTS