Skip to content

Commit 98b0c07

Browse files
authored
Added support to Microsoft.Extensions.Azure for configuring managedIdentityObjectId (Azure#46909)
1 parent 2c437c9 commit 98b0c07

File tree

4 files changed

+120
-22
lines changed

4 files changed

+120
-22
lines changed

eng/Packages.Data.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
<PackageReference Update="Azure.MixedReality.Authentication" version= "1.2.0" />
121121
<PackageReference Update="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0-beta.2" />
122122
<PackageReference Update="Azure.Monitor.Query" Version="1.1.0" />
123-
<PackageReference Update="Azure.Identity" Version="1.12.0" />
123+
<PackageReference Update="Azure.Identity" Version="1.13.1" />
124124
<PackageReference Update="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
125125
<PackageReference Update="Azure.Security.KeyVault.Keys" Version="4.6.0" />
126126
<PackageReference Update="Azure.Security.KeyVault.Certificates" Version="4.6.0" />
@@ -261,7 +261,7 @@
261261
<ItemGroup Condition="('$(IsTestProject)' == 'true') OR ('$(IsTestSupportProject)' == 'true') OR ('$(IsPerfProject)' == 'true') OR ('$(IsStressProject)' == 'true') OR ('$(IsSamplesProject)' == 'true')">
262262
<PackageReference Update="ApprovalTests" Version="3.0.22" />
263263
<PackageReference Update="ApprovalUtilities" Version="3.0.22" />
264-
<PackageReference Update="Azure.Identity" Version="1.12.0" />
264+
<PackageReference Update="Azure.Identity" Version="1.13.1" />
265265
<PackageReference Update="Azure.Messaging.EventGrid" Version="4.17.0" />
266266
<PackageReference Update="Azure.Messaging.EventHubs.Processor" Version="5.11.3" />
267267
<PackageReference Update="Azure.Messaging.ServiceBus" Version="7.18.2" />

sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
### Features Added
66

7+
- Added support for constructing a `ManagedIdentityCredential` from config by setting the `managedIdentityObjectId` key.
8+
79
### Breaking Changes
810

911
### Bugs Fixed
1012

1113
### Other Changes
1214

15+
- Updated dependency `Azure.Identity` to version `1.13.1`.
16+
1317
## 1.7.6 (2024-10-04)
1418

1519
### Other Changes

sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ internal static TokenCredential CreateCredential(IConfiguration configuration)
9797
var clientId = configuration["clientId"];
9898
var tenantId = configuration["tenantId"];
9999
var resourceId = configuration["managedIdentityResourceId"];
100+
var objectId = configuration["managedIdentityObjectId"];
100101
var clientSecret = configuration["clientSecret"];
101102
var certificate = configuration["clientCertificate"];
102103
var certificateStoreName = configuration["clientCertificateStoreName"];
@@ -114,16 +115,26 @@ internal static TokenCredential CreateCredential(IConfiguration configuration)
114115

115116
if (string.Equals(credentialType, "managedidentity", StringComparison.OrdinalIgnoreCase))
116117
{
117-
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(resourceId))
118+
int idCount = 0;
119+
idCount += string.IsNullOrWhiteSpace(clientId) ? 0 : 1;
120+
idCount += string.IsNullOrWhiteSpace(resourceId) ? 0 : 1;
121+
idCount += string.IsNullOrWhiteSpace(objectId) ? 0 : 1;
122+
123+
if (idCount > 1)
118124
{
119-
throw new ArgumentException("Cannot specify both 'clientId' and 'managedIdentityResourceId'");
125+
throw new ArgumentException("Only one of either 'clientId', 'managedIdentityResourceId', or 'managedIdentityObjectId' can be specified for managed identity.");
120126
}
121127

122128
if (!string.IsNullOrWhiteSpace(resourceId))
123129
{
124130
return new ManagedIdentityCredential(new ResourceIdentifier(resourceId));
125131
}
126132

133+
if (!string.IsNullOrWhiteSpace(objectId))
134+
{
135+
return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedObjectId(objectId));
136+
}
137+
127138
return new ManagedIdentityCredential(clientId);
128139
}
129140

@@ -215,6 +226,11 @@ internal static TokenCredential CreateCredential(IConfiguration configuration)
215226

216227
// TODO: More logging
217228

229+
if (!string.IsNullOrWhiteSpace(objectId))
230+
{
231+
throw new ArgumentException("Managed identity 'objectId' is only supported when the credential type is 'managedidentity'.");
232+
}
233+
218234
if (additionallyAllowedTenantsList != null
219235
|| !string.IsNullOrWhiteSpace(tenantId)
220236
|| !string.IsNullOrWhiteSpace(clientId)

sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ public void CreatesDefaultAzureCredential(
278278
[Values(true, false)] bool additionalTenants,
279279
[Values(true, false)] bool clientId,
280280
[Values(true, false)] bool tenantId,
281+
[Values(true, false)] bool objectId,
281282
[Values(true, false)] bool resourceId)
282283
{
283284
List<KeyValuePair<string, string>> configEntries = new();
@@ -299,10 +300,16 @@ public void CreatesDefaultAzureCredential(
299300
{
300301
configEntries.Add(new KeyValuePair<string, string>("managedIdentityResourceId", resourceIdValue));
301302
}
303+
if (objectId)
304+
{
305+
configEntries.Add(new KeyValuePair<string, string>("managedIdentityObjectId", "objectId"));
306+
}
307+
302308
IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configEntries).Build();
303309

304310
// if both clientId and resourceId set, we expect an ArgumentException
305-
if (clientId && resourceId)
311+
// We also expect an exception if objectId is set for DefaultAzureCredential, as it is only supported for ManagedIdentityCredential
312+
if ((clientId && resourceId) || objectId)
306313
{
307314
Assert.Throws<ArgumentException>(() => ClientFactory.CreateCredential(configuration));
308315
return;
@@ -336,20 +343,27 @@ public void CreatesDefaultAzureCredential(
336343
Assert.AreEqual("tenantId", pwshCredential.TenantId);
337344
}
338345

339-
// TODO: Since these can't build with project reference, we have to comment them out for now.
340-
// When we resolve https://github.com/Azure/azure-sdk-for-net/issues/45806, we can add them back.
341-
//if (clientId)
342-
//{
343-
// Assert.AreEqual("clientId", miCredential.Client.ClientId);
344-
//}
345-
//if (resourceId)
346-
//{
347-
// Assert.AreEqual(resourceIdValue, miCredential.Client.ResourceIdentifier.ToString());
348-
//}
346+
string managedIdentityId;
347+
int idType;
348+
ReflectIdAndType(miCredential, out managedIdentityId, out idType);
349+
if (clientId)
350+
{
351+
Assert.AreEqual("clientId", managedIdentityId);
352+
Assert.AreEqual(1, idType); // 1 is the value for ClientId
353+
}
354+
if (resourceId)
355+
{
356+
Assert.AreEqual(resourceIdValue.ToString(), managedIdentityId);
357+
Assert.AreEqual(2, idType); // 2 is the value for ResourceId
358+
}
359+
if (objectId)
360+
{
361+
Assert.AreEqual("objectId", managedIdentityId);
362+
Assert.AreEqual(3, idType); // 3 is the value for ObjectId
363+
}
349364
}
350365

351366
[Test]
352-
[Ignore("This test is failing, ignore it to pass CI. Tracking this in https://github.com/Azure/azure-sdk-for-net/issues/45806")]
353367
public void CreatesManagedServiceIdentityCredentialsWithClientId()
354368
{
355369
IConfiguration configuration = GetConfiguration(
@@ -362,14 +376,15 @@ public void CreatesManagedServiceIdentityCredentialsWithClientId()
362376
Assert.IsInstanceOf<ManagedIdentityCredential>(credential);
363377
var managedIdentityCredential = (ManagedIdentityCredential)credential;
364378

365-
var client = (ManagedIdentityClient)typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential);
366-
var clientId = typeof(ManagedIdentityClient).GetProperty("ClientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client);
379+
string clientId;
380+
int idType;
381+
ReflectIdAndType(managedIdentityCredential, out clientId, out idType);
367382

368383
Assert.AreEqual("ConfigurationClientId", clientId);
384+
Assert.AreEqual(1, idType); // 1 is the value for ClientId
369385
}
370386

371387
[Test]
372-
[Ignore("This test is failing, ignore it to pass CI. Tracking this in https://github.com/Azure/azure-sdk-for-net/issues/45806")]
373388
public void CreatesManagedServiceIdentityCredentials()
374389
{
375390
IConfiguration configuration = GetConfiguration(
@@ -381,10 +396,12 @@ public void CreatesManagedServiceIdentityCredentials()
381396
Assert.IsInstanceOf<ManagedIdentityCredential>(credential);
382397
var managedIdentityCredential = (ManagedIdentityCredential)credential;
383398

384-
var client = (ManagedIdentityClient)typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential);
385-
var clientId = typeof(ManagedIdentityClient).GetProperty("ClientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client);
399+
string clientId;
400+
int idType;
401+
ReflectIdAndType(managedIdentityCredential, out clientId, out idType);
386402

387403
Assert.Null(clientId);
404+
Assert.AreEqual(0, idType); // 0 is the value for SystemAssigned
388405
}
389406

390407
[Test]
@@ -400,9 +417,33 @@ public void CreatesManagedServiceIdentityCredentialsWithResourceId()
400417
Assert.IsInstanceOf<ManagedIdentityCredential>(credential);
401418
var managedIdentityCredential = (ManagedIdentityCredential)credential;
402419

403-
var resourceId = (string)typeof(ManagedIdentityCredential).GetField("_clientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential);
420+
string resourceId;
421+
int idType;
422+
ReflectIdAndType(managedIdentityCredential, out resourceId, out idType);
404423

405424
Assert.AreEqual("ConfigurationResourceId", resourceId);
425+
Assert.AreEqual(2, idType); // 2 is the value for ResourceId
426+
}
427+
428+
[Test]
429+
public void CreatesManagedServiceIdentityCredentialsWithObjectId()
430+
{
431+
IConfiguration configuration = GetConfiguration(
432+
new KeyValuePair<string, string>("managedIdentityObjectId", "ConfigurationObjectId"),
433+
new KeyValuePair<string, string>("credential", "managedidentity")
434+
);
435+
436+
var credential = ClientFactory.CreateCredential(configuration);
437+
438+
Assert.IsInstanceOf<ManagedIdentityCredential>(credential);
439+
var managedIdentityCredential = (ManagedIdentityCredential)credential;
440+
441+
string objectId;
442+
int idType;
443+
ReflectIdAndType(managedIdentityCredential, out objectId, out idType);
444+
445+
Assert.AreEqual("ConfigurationObjectId", objectId);
446+
Assert.AreEqual(3, idType); // 3 is the value for ObjectId
406447
}
407448

408449
[Test]
@@ -419,6 +460,34 @@ public void CreatesManagedServiceIdentityCredentialsThrowsWhenResourceIdAndClien
419460
Throws.InstanceOf<ArgumentException>().With.Message.Contains("managedIdentityResourceId"));
420461
}
421462

463+
[Test]
464+
public void CreatesManagedServiceIdentityCredentialsThrowsWhenClientIdAndObjectIdSpecified()
465+
{
466+
IConfiguration configuration = GetConfiguration(
467+
new KeyValuePair<string, string>("managedIdentityObjectId", "ConfigurationObjectId"),
468+
new KeyValuePair<string, string>("clientId", "ConfigurationClientId"),
469+
new KeyValuePair<string, string>("credential", "managedidentity")
470+
);
471+
472+
Assert.That(
473+
() => ClientFactory.CreateCredential(configuration),
474+
Throws.InstanceOf<ArgumentException>().With.Message.Contains("managedIdentityResourceId"));
475+
}
476+
477+
[Test]
478+
public void CreatesManagedServiceIdentityCredentialsThrowsWhenResourceIdAndObjectIdSpecified()
479+
{
480+
IConfiguration configuration = GetConfiguration(
481+
new KeyValuePair<string, string>("managedIdentityObjectId", "ConfigurationObjectId"),
482+
new KeyValuePair<string, string>("managedIdentityResourceId", "ConfigurationResourceId"),
483+
new KeyValuePair<string, string>("credential", "managedidentity")
484+
);
485+
486+
Assert.That(
487+
() => ClientFactory.CreateCredential(configuration),
488+
Throws.InstanceOf<ArgumentException>().With.Message.Contains("managedIdentityResourceId"));
489+
}
490+
422491
[Test]
423492
public void CreatesWorkloadIdentityCredentialsWithOptions()
424493
{
@@ -561,5 +630,14 @@ private IConfiguration GetConfiguration(params KeyValuePair<string, string>[] it
561630
{
562631
return new ConfigurationBuilder().AddInMemoryCollection(items).Build();
563632
}
633+
634+
private static void ReflectIdAndType(ManagedIdentityCredential managedIdentityCredential, out string clientId, out int idType)
635+
{
636+
var managedIdentityClient = typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential);
637+
var managedIdentityClientOptions = managedIdentityClient.GetType().GetField("_options", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityClient);
638+
var managedIdentityId = managedIdentityClientOptions.GetType().GetProperty("ManagedIdentityId").GetValue(managedIdentityClientOptions);
639+
clientId = (string)typeof(ManagedIdentityId).GetField("_userAssignedId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityId);
640+
idType = (int)typeof(ManagedIdentityId).GetField("_idType", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityId);
641+
}
564642
}
565643
}

0 commit comments

Comments
 (0)