Skip to content

Commit 605cbc8

Browse files
authored
Warn on unsupported managed identity configuration (Azure#27966)
1 parent 478ddc9 commit 605cbc8

File tree

6 files changed

+86
-31
lines changed

6 files changed

+86
-31
lines changed

sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,17 @@ public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions optio
3737
throw new AuthenticationFailedException(IdentityEndpointInvalidUriError);
3838
}
3939

40-
return new AzureArcManagedIdentitySource(options.Pipeline, endpointUri, options.ClientId);
40+
return new AzureArcManagedIdentitySource(endpointUri, options);
4141
}
4242

43-
private AzureArcManagedIdentitySource(CredentialPipeline pipeline, Uri endpoint, string clientId) : base(pipeline)
43+
private AzureArcManagedIdentitySource(Uri endpoint, ManagedIdentityClientOptions options) : base(options.Pipeline)
4444
{
4545
_endpoint = endpoint;
46-
_clientId = clientId;
46+
_clientId = options.ClientId;
47+
if (!string.IsNullOrEmpty(_clientId) || null != options.ResourceIdentifier)
48+
{
49+
AzureIdentityEventSource.Singleton.UserAssignedManagedIdentityNotSupported("Azure Arc");
50+
}
4751
}
4852

4953
protected override Request CreateRequest(string[] scopes)

sdk/identity/Azure.Identity/src/AzureIdentityEventSource.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ internal sealed class AzureIdentityEventSource : AzureEventSource
3535
private const int TenantIdDiscoveredAndUsedEvent = 18;
3636
internal const int AuthenticatedAccountDetailsEvent = 19;
3737
internal const int UnableToParseAccountDetailsFromTokenEvent = 20;
38+
private const int UserAssignedManagedIdentityNotSupportedEvent = 21;
39+
private const int ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent = 22;
3840
internal const string TenantIdDiscoveredAndNotUsedEventMessage = "A token was request for a different tenant than was configured on the credential, but the configured value was used since multi tenant authentication has been disabled. Configured TenantId: {0}, Requested TenantId {1}";
3941
internal const string TenantIdDiscoveredAndUsedEventMessage = "A token was requested for a different tenant than was configured on the credential, and the requested tenant id was used to authenticate. Configured TenantId: {0}, Requested TenantId {1}";
4042
internal const string AuthenticatedAccountDetailsMessage = "Client ID: {0}. Tenant ID: {1}. User Principal Name: {2} Object ID: {3}";
4143
internal const string Unavailable = "<not available>";
4244
internal const string UnableToParseAccountDetailsFromTokenMessage = "Unable to parse account details from the Access Token";
45+
internal const string UserAssignedManagedIdentityNotSupportedMessage = "User assigned managed identities are not supported in the {0} environment.";
46+
internal const string ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedMessage = "Service Fabric user assigned managed identity ClientId or ResourceId is not configurable at runtime.";
4347

4448
private AzureIdentityEventSource() : base(EventSourceName) { }
4549

@@ -321,5 +325,23 @@ internal void UnableToParseAccountDetailsFromToken()
321325
WriteEvent(UnableToParseAccountDetailsFromTokenEvent);
322326
}
323327
}
328+
329+
[Event(UserAssignedManagedIdentityNotSupportedEvent, Level = EventLevel.Warning, Message = UserAssignedManagedIdentityNotSupportedMessage)]
330+
public void UserAssignedManagedIdentityNotSupported(string environment)
331+
{
332+
if (IsEnabled(EventLevel.Warning, EventKeywords.All))
333+
{
334+
WriteEvent(UserAssignedManagedIdentityNotSupportedEvent, environment);
335+
}
336+
}
337+
338+
[Event(ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent, Level = EventLevel.Warning, Message =ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedMessage)]
339+
public void ServiceFabricManagedIdentityRuntimeConfigurationNotSupported()
340+
{
341+
if (IsEnabled(EventLevel.Warning, EventKeywords.All))
342+
{
343+
WriteEvent(ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent);
344+
}
345+
}
324346
}
325347
}

sdk/identity/Azure.Identity/src/CloudShellManagedIdentitySource.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace Azure.Identity
1313
internal class CloudShellManagedIdentitySource : ManagedIdentitySource
1414
{
1515
private readonly Uri _endpoint;
16-
private readonly string _clientId;
1716
private const string MsiEndpointInvalidUriError = "The environment variable MSI_ENDPOINT contains an invalid Uri.";
1817

1918
public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions options)
@@ -36,38 +35,31 @@ public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions optio
3635
throw new AuthenticationFailedException(MsiEndpointInvalidUriError, ex);
3736
}
3837

39-
return new CloudShellManagedIdentitySource(options.Pipeline, endpointUri, options.ClientId);
38+
return new CloudShellManagedIdentitySource(endpointUri, options);
4039
}
4140

42-
private CloudShellManagedIdentitySource(CredentialPipeline pipeline, Uri endpoint, string clientId) : base(pipeline)
41+
private CloudShellManagedIdentitySource(Uri endpoint, ManagedIdentityClientOptions options) : base(options.Pipeline)
4342
{
4443
_endpoint = endpoint;
45-
_clientId = clientId;
44+
if (!string.IsNullOrEmpty(options.ClientId) || null == options.ResourceIdentifier)
45+
{
46+
AzureIdentityEventSource.Singleton.UserAssignedManagedIdentityNotSupported("Cloud Shell");
47+
}
4648
}
4749

4850
protected override Request CreateRequest(string[] scopes)
4951
{
5052
// covert the scopes to a resource string
5153
string resource = ScopeUtilities.ScopesToResource(scopes);
52-
5354
Request request = Pipeline.HttpPipeline.CreateRequest();
54-
5555
request.Method = RequestMethod.Post;
56-
5756
request.Headers.Add(HttpHeader.Common.FormUrlEncodedContentType);
58-
5957
request.Uri.Reset(_endpoint);
60-
6158
request.Headers.Add("Metadata", "true");
6259

6360
var bodyStr = $"resource={Uri.EscapeDataString(resource)}";
64-
65-
if (!string.IsNullOrEmpty(_clientId))
66-
{
67-
bodyStr += $"&{Constants.ManagedIdentityClientId}={Uri.EscapeDataString(_clientId)}";
68-
}
69-
7061
ReadOnlyMemory<byte> content = Encoding.UTF8.GetBytes(bodyStr).AsMemory();
62+
7163
request.Content = RequestContent.Create(content);
7264
return request;
7365
}

sdk/identity/Azure.Identity/src/Credentials/ManagedIdentityCredential.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public ManagedIdentityCredential(ResourceIdentifier resourceId, TokenCredentialO
6767
_clientId = resourceId.ToString();
6868
}
6969

70-
internal ManagedIdentityCredential(string clientId, CredentialPipeline pipeline)
71-
: this(new ManagedIdentityClient(pipeline, clientId))
70+
internal ManagedIdentityCredential(string clientId, CredentialPipeline pipeline, bool preserveTransport = false)
71+
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { Pipeline = pipeline, ClientId = clientId, PreserveTransport = preserveTransport }))
7272
{
7373
_clientId = clientId;
7474
}

sdk/identity/Azure.Identity/src/ServiceFabricManagedIdentitySource.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ private ServiceFabricManagedIdentitySource(CredentialPipeline pipeline, Uri endp
6363
_identityHeaderValue = identityHeaderValue;
6464
_clientId = options.ClientId;
6565
_resourceId = options.ResourceIdentifier?.ToString();
66+
if (!string.IsNullOrEmpty(options.ClientId) || null != options.ResourceIdentifier)
67+
{
68+
AzureIdentityEventSource.Singleton.ServiceFabricManagedIdentityRuntimeConfigurationNotSupported();
69+
}
6670
}
6771

6872
protected override Request CreateRequest(string[] scopes)

sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics.Tracing;
67
using System.IO;
78
using System.Text;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using System.Web;
1112
using Azure.Core;
13+
using Azure.Core.Diagnostics;
1214
using Azure.Core.TestFramework;
1315
using Azure.Identity.Tests.Mock;
1416
using Microsoft.AspNetCore.Http;
17+
using Microsoft.Diagnostics.Runtime.Interop;
1518
using NUnit.Framework;
1619

1720
namespace Azure.Identity.Tests
@@ -82,13 +85,14 @@ public async Task VerifyImdsRequestWithResourceIdMockAsync()
8285
Assert.IsTrue(query.Contains("api-version=2018-02-01"));
8386
Assert.IsTrue(query.Contains($"resource={Uri.EscapeDataString(ScopeUtilities.ScopesToResource(MockScopes.Default))}"));
8487
Assert.IsTrue(request.Headers.TryGetValue("Metadata", out string metadataValue));
85-
Assert.That(Uri.UnescapeDataString(query), Does.Contain($"{Constants.ManagedIdentityResourceId}={_expectedResourceId}"));
88+
Assert.That(Uri.UnescapeDataString(query), Does.Contain($"{Constants.ManagedIdentityResourceId}={_expectedResourceId}"));
8689
Assert.AreEqual("true", metadataValue);
8790
}
8891

8992
[NonParallelizable]
9093
[Test]
91-
public async Task VerifyServiceFabricRequestWithResourceIdMockAsync()
94+
[TestCaseSource(nameof(ResourceAndClientIds))]
95+
public async Task VerifyServiceFabricRequestWithResourceIdMockAsync(string clientId, bool includeResourceIdentifier)
9296
{
9397
using var environment = new TestEnvVar(
9498
new()
@@ -101,12 +105,22 @@ public async Task VerifyServiceFabricRequestWithResourceIdMockAsync()
101105
{ "IDENTITY_SERVER_THUMBPRINT", "thumbprint" }
102106
});
103107

108+
List<string> messages = new();
109+
using AzureEventSourceListener listener = new AzureEventSourceListener(
110+
(_, message) => messages.Add(message),
111+
EventLevel.Warning);
112+
104113
var response = CreateMockResponse(200, ExpectedToken);
105114
var mockTransport = new MockTransport(response);
106115
var options = new TokenCredentialOptions { Transport = mockTransport };
107116
var pipeline = CredentialPipeline.GetInstance(options);
108117

109-
ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(new ResourceIdentifier(_expectedResourceId), pipeline, true));
118+
ManagedIdentityCredential credential = (clientId, includeResourceIdentifier) switch
119+
{
120+
(Item1: null, Item2: true) => InstrumentClient(new ManagedIdentityCredential(new ResourceIdentifier(_expectedResourceId), pipeline, true)),
121+
(Item1: not null, Item2: false) => InstrumentClient(new ManagedIdentityCredential(clientId, pipeline, true)),
122+
_ => InstrumentClient(new ManagedIdentityCredential(clientId: null, pipeline, true))
123+
};
110124

111125
AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default));
112126

@@ -119,7 +133,14 @@ public async Task VerifyServiceFabricRequestWithResourceIdMockAsync()
119133
Assert.AreEqual(request.Uri.Host, "169.254.169.254");
120134
Assert.AreEqual(request.Uri.Path, "/metadata/identity/oauth2/token");
121135
Assert.IsTrue(query.Contains("api-version=2018-02-01"));
122-
Assert.That(query, Does.Contain($"{Constants.ManagedIdentityResourceId}={Uri.EscapeDataString(_expectedResourceId)}"));
136+
if (includeResourceIdentifier)
137+
{
138+
Assert.That(query, Does.Contain($"{Constants.ManagedIdentityResourceId}={Uri.EscapeDataString(_expectedResourceId)}"));
139+
}
140+
if (clientId != null || includeResourceIdentifier)
141+
{
142+
Assert.That(messages, Does.Contain(AzureIdentityEventSource.ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedMessage));
143+
}
123144
}
124145

125146
[NonParallelizable]
@@ -240,7 +261,7 @@ public async Task VerifyAppService2017RequestWithClientIdAndMockAsync([Values(nu
240261

241262
var response = CreateMockResponse(200, ExpectedToken);
242263
var mockTransport = new MockTransport(response);
243-
var options = new TokenCredentialOptions() {Transport = mockTransport};
264+
var options = new TokenCredentialOptions() { Transport = mockTransport };
244265

245266
ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(clientId, options));
246267

@@ -328,7 +349,7 @@ public async Task VerifyAppService2019RequestMockAsync()
328349
[Test]
329350
public async Task AllAppServiceEnvVarsSetSelects2019Api()
330351
{
331-
using var environment = new TestEnvVar(new() { { "MSI_ENDPOINT", "https://mock.msi.endpoint/" }, { "MSI_SECRET", "mock-msi-secret" }, { "IDENTITY_ENDPOINT", "https://identity.endpoint/" }, { "IDENTITY_HEADER", "mock-identity-header" }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
352+
using var environment = new TestEnvVar(new() { { "MSI_ENDPOINT", "https://mock.msi.endpoint/" }, { "MSI_SECRET", "mock-msi-secret" }, { "IDENTITY_ENDPOINT", "https://identity.endpoint/" }, { "IDENTITY_HEADER", "mock-identity-header" }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
332353

333354
var response = CreateMockResponse(200, ExpectedToken);
334355
var mockTransport = new MockTransport(response);
@@ -386,7 +407,7 @@ public async Task VerifyAppService2019RequestWithResourceIdMockAsync()
386407

387408
var response = CreateMockResponse(200, ExpectedToken);
388409
var mockTransport = new MockTransport(response);
389-
var options = new TokenCredentialOptions() {Transport = mockTransport};
410+
var options = new TokenCredentialOptions() { Transport = mockTransport };
390411
ManagedIdentityCredential credential =
391412
InstrumentClient(new ManagedIdentityCredential(new ResourceIdentifier(resourceId), options));
392413

@@ -444,6 +465,11 @@ public async Task VerifyCloudShellMsiRequestWithClientIdMockAsync(string clientI
444465
{
445466
using var environment = new TestEnvVar(new() { { "MSI_ENDPOINT", "https://mock.msi.endpoint/" }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
446467

468+
List<string> messages = new();
469+
using AzureEventSourceListener listener = new AzureEventSourceListener(
470+
(_, message) => messages.Add(message),
471+
EventLevel.Warning);
472+
447473
var response = CreateMockResponse(200, ExpectedToken);
448474
var mockTransport = new MockTransport(response);
449475
var options = new TokenCredentialOptions() { Transport = mockTransport };
@@ -467,7 +493,7 @@ public async Task VerifyCloudShellMsiRequestWithClientIdMockAsync(string clientI
467493
Assert.IsTrue(body.Contains($"resource={Uri.EscapeDataString(ScopeUtilities.ScopesToResource(MockScopes.Default))}"));
468494
if (clientId != null)
469495
{
470-
Assert.IsTrue(body.Contains($"{Constants.ManagedIdentityClientId}=mock-client-id"));
496+
Assert.That(messages, Does.Contain(string.Format(AzureIdentityEventSource.UserAssignedManagedIdentityNotSupportedMessage, "Cloud Shell")));
471497
}
472498
Assert.IsTrue(request.Headers.TryGetValue("Metadata", out string actMetadata));
473499
Assert.AreEqual("true", actMetadata);
@@ -510,7 +536,7 @@ public async Task VerifyMsiUnavailableOnIMDSRequestFailedExcpetion()
510536

511537
[NonParallelizable]
512538
[Test]
513-
public async Task VerifyMsiUnavailableOnIMDSGatewayErrorResponse([Values(502, 504)]int statusCode)
539+
public async Task VerifyMsiUnavailableOnIMDSGatewayErrorResponse([Values(502, 504)] int statusCode)
514540
{
515541
using var server = new TestServer(context =>
516542
{
@@ -658,7 +684,7 @@ public async Task VerifyClientAuthenticateReturnsErrorResponse()
658684
});
659685
var errorMessage = "Some error happened";
660686
var mockTransport = new MockTransport(request => CreateErrorMockResponse(404, errorMessage));
661-
var options = new TokenCredentialOptions { Transport = mockTransport};
687+
var options = new TokenCredentialOptions { Transport = mockTransport };
662688
options.Retry.MaxDelay = TimeSpan.Zero;
663689
var pipeline = CredentialPipeline.GetInstance(options);
664690

@@ -684,6 +710,13 @@ public async Task VerifyAuthenticationFailedExceptionsAreDeferredToGetToken(Dict
684710
await Task.CompletedTask;
685711
}
686712

713+
private static IEnumerable<TestCaseData> ResourceAndClientIds()
714+
{
715+
yield return new TestCaseData(new object[] { null, false });
716+
yield return new TestCaseData(new object[] { "mock-client-id", false });
717+
yield return new TestCaseData(new object[] { null, true });
718+
}
719+
687720
private static IEnumerable<TestCaseData> ExceptionalEnvironmentConfigs()
688721
{
689722
// AppServiceV2017ManagedIdentitySource should throw
@@ -696,7 +729,7 @@ private static IEnumerable<TestCaseData> ExceptionalEnvironmentConfigs()
696729
yield return new TestCaseData(new Dictionary<string, string>() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", "http::@/bogusuri" }, { "IMDS_ENDPOINT", "mockvalue" }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
697730

698731
// ServiceFabricManagedIdentitySource should throw
699-
yield return new TestCaseData(new Dictionary<string, string>() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", "http::@/bogusuri" }, { "IDENTITY_HEADER", "mockvalue" }, { "IDENTITY_SERVER_THUMBPRINT", "mockvalue"}, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
732+
yield return new TestCaseData(new Dictionary<string, string>() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", "http::@/bogusuri" }, { "IDENTITY_HEADER", "mockvalue" }, { "IDENTITY_SERVER_THUMBPRINT", "mockvalue" }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
700733

701734
// ImdsManagedIdentitySource should throw
702735
yield return new TestCaseData(new Dictionary<string, string>() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "IDENTITY_SERVER_THUMBPRINT", "null" }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", "http::@/bogusuri" } });

0 commit comments

Comments
 (0)