From 449251dfeab2a886343112e0296a01e71b296cc6 Mon Sep 17 00:00:00 2001 From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:35:00 -0700 Subject: [PATCH] Required feature update --- .../AzureArcManagedIdentitySource.cs | 94 ++++++++++++++++--- .../MsalErrorMessage.cs | 2 + .../ManagedIdentityTests/AzureArcTests.cs | 7 +- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs index 8389c97570..b33afbe6e5 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs @@ -12,6 +12,7 @@ using Microsoft.Identity.Client.Extensibility; using Microsoft.Identity.Client.Http; using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Client.PlatformsCommon.Shared; using Microsoft.Identity.Client.Utils; namespace Microsoft.Identity.Client.ManagedIdentity @@ -110,19 +111,7 @@ protected override async Task HandleResponseAsync( var splitChallenge = challenge.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); - if (splitChallenge.Length != 2) - { - _requestContext.Logger.Error("[Managed Identity] The WWW-Authenticate header for Azure arc managed identity is not an expected format."); - - var exception = MsalServiceExceptionFactory.CreateManagedIdentityException( - MsalError.ManagedIdentityRequestFailed, - MsalErrorMessage.ManagedIdentityInvalidChallenge, - null, - ManagedIdentitySource.AzureArc, - null); - - throw exception; - } + ValidateSplitChallenge(splitChallenge); var authHeaderValue = "Basic " + File.ReadAllText(splitChallenge[1]); @@ -138,5 +127,84 @@ protected override async Task HandleResponseAsync( return await base.HandleResponseAsync(parameters, response, cancellationToken).ConfigureAwait(false); } + + private void ValidateSplitChallenge(string[] splitChallenge) + { + if (splitChallenge.Length != 2) + { + throw CreateManagedIdentityException( + MsalError.ManagedIdentityRequestFailed, + MsalErrorMessage.ManagedIdentityInvalidChallenge); + } + + _requestContext.Logger.Verbose(() => $"[Managed Identity] Challenge is valid. FilePath: {splitChallenge[1]}"); + + if (DesktopOsHelper.IsWindows()) + { + if (!IsValidWindowsPath(splitChallenge[1])) + { + throw CreateManagedIdentityException( + MsalError.ManagedIdentityRequestFailed, + MsalErrorMessage.ManagedIdentityInvalidFile); + } + + _requestContext.Logger.Verbose(() => "[Managed Identity] Windows path is valid."); + } + else if (DesktopOsHelper.IsLinux()) + { + if (!IsValidLinuxPath(splitChallenge[1])) + { + throw CreateManagedIdentityException( + MsalError.ManagedIdentityRequestFailed, + MsalErrorMessage.ManagedIdentityInvalidFile); + } + + _requestContext.Logger.Verbose(() => "[Managed Identity] Linux path is valid."); + } + else + { + throw CreateManagedIdentityException( + MsalError.ManagedIdentityRequestFailed, + MsalErrorMessage.ManagedIdentityPlatformNotSupported); + } + + var length = new FileInfo(splitChallenge[1]).Length; + + if ((!File.Exists(splitChallenge[1]) || (length) > 4096)) + { + _requestContext.Logger.Error($"[Managed Identity] File does not exist or is greater than 4096 bytes. File exists: {File.Exists(splitChallenge[1])}. Length of file: {length}"); + throw CreateManagedIdentityException( + MsalError.ManagedIdentityRequestFailed, + MsalErrorMessage.ManagedIdentityInvalidFile); + } + + _requestContext.Logger.Verbose(() => "[Managed Identity] File exists and is less than 4096 bytes."); + } + + private MsalException CreateManagedIdentityException(string errorCode, string errorMessage) + { + return MsalServiceExceptionFactory.CreateManagedIdentityException( + errorCode, + errorMessage, + null, + ManagedIdentitySource.AzureArc, + null); + } + + private bool IsValidLinuxPath(string path) + { + string linuxPath = "/var/opt/azcmagent/tokens/"; + + return path.StartsWith(linuxPath, StringComparison.OrdinalIgnoreCase) && + path.EndsWith(".key", StringComparison.OrdinalIgnoreCase); + } + + private bool IsValidWindowsPath(string path) + { + string expandedExpectedPath = Environment.ExpandEnvironmentVariables("%ProgramData%\\AzureConnectedMachineAgent\\Tokens\\"); + + return path.StartsWith(expandedExpectedPath, StringComparison.OrdinalIgnoreCase) && + path.EndsWith(".key", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs b/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs index ab7477b920..fdbbc32602 100644 --- a/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs +++ b/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs @@ -419,6 +419,8 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName) public const string ManagedIdentityEndpointInvalidUriError = "[Managed Identity] The environment variable {0} contains an invalid Uri {1} in {2} managed identity source."; public const string ManagedIdentityNoChallengeError = "[Managed Identity] Did not receive expected WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint."; public const string ManagedIdentityInvalidChallenge = "[Managed Identity] The WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint did not match the expected format."; + public const string ManagedIdentityInvalidFile = "[Managed Identity] The file on the file path in the WWW-Authenticate header is not secure."; + public const string ManagedIdentityPlatformNotSupported = "[Managed Identity] The platform is not supported by Azure Arc. Azure Arc only supports Windows and Linux."; public const string ManagedIdentityUserAssignedNotSupported = "[Managed Identity] User assigned identity is not supported by the {0} Managed Identity. To authenticate with the system assigned identity omit the client id in ManagedIdentityApplicationBuilder.Create()."; public const string ManagedIdentityUserAssignedNotConfigurableAtRuntime = "[Managed Identity] Service Fabric user assigned managed identity ClientId or ResourceId is not configurable at runtime."; public const string CombinedUserAppCacheNotSupported = "Using a combined flat storage, like a file, to store both app and user tokens is not supported. Use a partitioned token cache (for ex. distributed cache like Redis) or separate files for app and user token caches. See https://aka.ms/msal-net-token-cache-serialization ."; diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AzureArcTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AzureArcTests.cs index d2bedff0ee..6697f76df9 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AzureArcTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AzureArcTests.cs @@ -12,6 +12,7 @@ using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.Identity.Test.Common.Core.Mocks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute.Core; using static Microsoft.Identity.Test.Common.Core.Helpers.ManagedIdentityTestUtil; namespace Microsoft.Identity.Test.Unit.ManagedIdentityTests @@ -78,8 +79,10 @@ await mi.AcquireTokenForManagedIdentity("scope") } } - [TestMethod] - public async Task AzureArcAuthHeaderInvalidAsync() + [DataTestMethod] + [DataRow("somefile=filename", MsalErrorMessage.ManagedIdentityInvalidChallenge)] + [DataRow("path/filename", MsalErrorMessage.ManagedIdentityInvalidFile)] + public async Task AzureArcAuthHeaderInvalidAsync(string filename, string errorMessage) { using (new EnvVariableContext()) using (var httpManager = new MockHttpManager(isManagedIdentity: true))