From 2db9a0f33668ae9cf0331613e1cd612bee105e1c Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 10 Jun 2024 09:51:06 -0500 Subject: [PATCH] AzureArcManagedIdentitySource fix --- eng/Packages.Data.props | 8 +- sdk/identity/Azure.Identity/CHANGELOG.md | 5 ++ .../Azure.Identity/src/Azure.Identity.csproj | 4 +- .../src/AzureArcManagedIdentitySource.cs | 37 ++++++++++ .../ManagedIdentityCredentialArcLiveTests.cs | 1 + .../tests/ManagedIdentityCredentialTests.cs | 74 +++++++++++++++++++ 6 files changed, 123 insertions(+), 6 deletions(-) diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 364166cc98678..507c2c2c8f6fa 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -152,13 +152,13 @@ - - + + - + @@ -379,7 +379,7 @@ - + 1.0.0-dev.20240410.2 diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 49f853c35971b..800e85ea630bc 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -1,5 +1,10 @@ # Release History +## 1.11.4 (2024-06-11) + +### Bugs Fixed +- Managed identity bug fixes + ## 1.11.3 (2024-05-07) ### Bugs Fixed diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index 858379b84b2b9..ed8e56cc41f43 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.11.3 + 1.11.4 - 1.11.2 + 1.11.3 Microsoft Azure Identity;$(PackageCommonTags) $(RequiredTargetFrameworks) $(NoWarn);3021;AZC0011 diff --git a/sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs b/sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs index 42f1f06a36794..fcd1654e51c40 100644 --- a/sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs +++ b/sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs @@ -88,7 +88,9 @@ protected override async ValueTask HandleResponseAsync(bool async, { throw new AuthenticationFailedException(InvalidChallangeErrorMessage); } + string filePath = splitChallenge[1]; + ValidatePath(filePath); var authHeaderValue = "Basic " + File.ReadAllText(splitChallenge[1]); using Request request = CreateRequest(context.Scopes); @@ -112,5 +114,40 @@ protected override async ValueTask HandleResponseAsync(bool async, return await base.HandleResponseAsync(async, context, message, cancellationToken).ConfigureAwait(false); } + + private void ValidatePath(string filePath) + { + // check that the file ends with '.key' + if (!filePath.EndsWith(".key")) + { + throw new AuthenticationFailedException("The secret key file failed validation. File name is invalid."); + } + // if the current platform is windows check that the file is in the path %ProgramData%\AzureConnectedMachineAgent\Tokens + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + var expectedPath = Path.Combine(programData, "AzureConnectedMachineAgent", "Tokens"); + if (!filePath.StartsWith(expectedPath)) + { + throw new AuthenticationFailedException("The secret key file failed validation. File path is invalid."); + } + } + + // if the current platform is linux check that the file is in the path /var/opt/azcmagent/tokens + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + var expectedPath = Path.Combine("/", "var", "opt", "azcmagent", "tokens"); + if (!filePath.StartsWith(expectedPath)) + { + throw new AuthenticationFailedException("The secret key file failed validation. File path is invalid."); + } + } + + // Check that the file length is no larger than 4096 bytes + if (new FileInfo(filePath).Length > 4096) + { + throw new AuthenticationFailedException("The secret key file failed validation. File is too large."); + } + } } } diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialArcLiveTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialArcLiveTests.cs index 73b0dacb68e56..a9d32df6b972b 100644 --- a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialArcLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialArcLiveTests.cs @@ -18,6 +18,7 @@ public ManagedIdentityCredentialArcLiveTests(bool isAsync) : base(isAsync) [NonParallelizable] [Test] + [LiveOnly(Reason = "path validation fails in playback mode")] public async Task ValidateSystemAssignedIdentity() { if (string.IsNullOrEmpty(TestEnvironment.ArcEnable)) diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs index 5253768da832f..f2a8995e6c2de 100644 --- a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs @@ -853,6 +853,80 @@ public async Task VerifyAuthenticationFailedExceptionsAreDeferredToGetToken(Dict await Task.CompletedTask; } + [Test] + public void VerifyArcIdentitySourceFilePathValidation_DoesNotEndInDotKey() + { + using var environment = new TestEnvVar( + new() + { + { "MSI_ENDPOINT", null }, + { "MSI_SECRET", null }, + { "IDENTITY_ENDPOINT", "https://identity.constoso.com" }, + { "IMDS_ENDPOINT", "https://imds.constoso.com" }, + { "IDENTITY_HEADER", null }, + { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } + }); + + var mockTransport = new MockTransport(request => + { + var response = new MockResponse(401); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + response.AddHeader("WWW-Authenticate", "file=c:\\ProgramData\\AzureConnectedMachineAgent\\Tokens\\secret.foo"); + } + else + { + response.AddHeader("WWW-Authenticate", "file=/var/opt/azcmagent/tokens/secret.foo"); + } + return response; + }); + var options = new TokenCredentialOptions() { Transport = mockTransport }; + options.Retry.MaxDelay = TimeSpan.Zero; + var pipeline = CredentialPipeline.GetInstance(options); + + ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(null, pipeline)); + + var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + Assert.That(ex.Message, Does.Contain("File name is invalid.")); + } + + [Test] + public void VerifyArcIdentitySourceFilePathValidation_FilePathInvalid() + { + using var environment = new TestEnvVar( + new() + { + { "MSI_ENDPOINT", null }, + { "MSI_SECRET", null }, + { "IDENTITY_ENDPOINT", "https://identity.constoso.com" }, + { "IMDS_ENDPOINT", "https://imds.constoso.com" }, + { "IDENTITY_HEADER", null }, + { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } + }); + + var mockTransport = new MockTransport(request => + { + var response = new MockResponse(401); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + response.AddHeader("WWW-Authenticate", "file=c:\\ProgramData\\bugus\\AzureConnectedMachineAgent\\Tokens\\secret.key"); + } + else + { + response.AddHeader("WWW-Authenticate", "file=/var/opt/bogus/azcmagent/tokens/secret.key"); + } + return response; + }); + var options = new TokenCredentialOptions() { Transport = mockTransport }; + options.Retry.MaxDelay = TimeSpan.Zero; + var pipeline = CredentialPipeline.GetInstance(options); + + ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(null, pipeline)); + + var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + Assert.That(ex.Message, Does.Contain("File path is invalid.")); + } + private static IEnumerable ResourceAndClientIds() { yield return new TestCaseData(new object[] { null, false });