diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index df417b1aaa29..1b9da916ed13 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -120,7 +120,7 @@ - + @@ -261,7 +261,7 @@ - + diff --git a/sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md b/sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md index 839e24420542..91af0e3de3a6 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md +++ b/sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md @@ -4,12 +4,16 @@ ### Features Added +- Added support for constructing a `ManagedIdentityCredential` from config by setting the `managedIdentityObjectId` key. + ### Breaking Changes ### Bugs Fixed ### Other Changes +- Updated dependency `Azure.Identity` to version `1.13.1`. + ## 1.7.6 (2024-10-04) ### Other Changes diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs index 0646bb2d3fcd..c0030882c893 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs @@ -97,6 +97,7 @@ internal static TokenCredential CreateCredential(IConfiguration configuration) var clientId = configuration["clientId"]; var tenantId = configuration["tenantId"]; var resourceId = configuration["managedIdentityResourceId"]; + var objectId = configuration["managedIdentityObjectId"]; var clientSecret = configuration["clientSecret"]; var certificate = configuration["clientCertificate"]; var certificateStoreName = configuration["clientCertificateStoreName"]; @@ -114,9 +115,14 @@ internal static TokenCredential CreateCredential(IConfiguration configuration) if (string.Equals(credentialType, "managedidentity", StringComparison.OrdinalIgnoreCase)) { - if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(resourceId)) + int idCount = 0; + idCount += string.IsNullOrWhiteSpace(clientId) ? 0 : 1; + idCount += string.IsNullOrWhiteSpace(resourceId) ? 0 : 1; + idCount += string.IsNullOrWhiteSpace(objectId) ? 0 : 1; + + if (idCount > 1) { - throw new ArgumentException("Cannot specify both 'clientId' and 'managedIdentityResourceId'"); + throw new ArgumentException("Only one of either 'clientId', 'managedIdentityResourceId', or 'managedIdentityObjectId' can be specified for managed identity."); } if (!string.IsNullOrWhiteSpace(resourceId)) @@ -124,6 +130,11 @@ internal static TokenCredential CreateCredential(IConfiguration configuration) return new ManagedIdentityCredential(new ResourceIdentifier(resourceId)); } + if (!string.IsNullOrWhiteSpace(objectId)) + { + return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedObjectId(objectId)); + } + return new ManagedIdentityCredential(clientId); } @@ -215,6 +226,11 @@ internal static TokenCredential CreateCredential(IConfiguration configuration) // TODO: More logging + if (!string.IsNullOrWhiteSpace(objectId)) + { + throw new ArgumentException("Managed identity 'objectId' is only supported when the credential type is 'managedidentity'."); + } + if (additionallyAllowedTenantsList != null || !string.IsNullOrWhiteSpace(tenantId) || !string.IsNullOrWhiteSpace(clientId) diff --git a/sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs b/sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs index 5a62e7cdfac2..1f38862eb0d3 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs @@ -278,6 +278,7 @@ public void CreatesDefaultAzureCredential( [Values(true, false)] bool additionalTenants, [Values(true, false)] bool clientId, [Values(true, false)] bool tenantId, + [Values(true, false)] bool objectId, [Values(true, false)] bool resourceId) { List> configEntries = new(); @@ -299,10 +300,16 @@ public void CreatesDefaultAzureCredential( { configEntries.Add(new KeyValuePair("managedIdentityResourceId", resourceIdValue)); } + if (objectId) + { + configEntries.Add(new KeyValuePair("managedIdentityObjectId", "objectId")); + } + IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configEntries).Build(); // if both clientId and resourceId set, we expect an ArgumentException - if (clientId && resourceId) + // We also expect an exception if objectId is set for DefaultAzureCredential, as it is only supported for ManagedIdentityCredential + if ((clientId && resourceId) || objectId) { Assert.Throws(() => ClientFactory.CreateCredential(configuration)); return; @@ -336,20 +343,27 @@ public void CreatesDefaultAzureCredential( Assert.AreEqual("tenantId", pwshCredential.TenantId); } - // TODO: Since these can't build with project reference, we have to comment them out for now. - // When we resolve https://github.com/Azure/azure-sdk-for-net/issues/45806, we can add them back. - //if (clientId) - //{ - // Assert.AreEqual("clientId", miCredential.Client.ClientId); - //} - //if (resourceId) - //{ - // Assert.AreEqual(resourceIdValue, miCredential.Client.ResourceIdentifier.ToString()); - //} + string managedIdentityId; + int idType; + ReflectIdAndType(miCredential, out managedIdentityId, out idType); + if (clientId) + { + Assert.AreEqual("clientId", managedIdentityId); + Assert.AreEqual(1, idType); // 1 is the value for ClientId + } + if (resourceId) + { + Assert.AreEqual(resourceIdValue.ToString(), managedIdentityId); + Assert.AreEqual(2, idType); // 2 is the value for ResourceId + } + if (objectId) + { + Assert.AreEqual("objectId", managedIdentityId); + Assert.AreEqual(3, idType); // 3 is the value for ObjectId + } } [Test] - [Ignore("This test is failing, ignore it to pass CI. Tracking this in https://github.com/Azure/azure-sdk-for-net/issues/45806")] public void CreatesManagedServiceIdentityCredentialsWithClientId() { IConfiguration configuration = GetConfiguration( @@ -362,14 +376,15 @@ public void CreatesManagedServiceIdentityCredentialsWithClientId() Assert.IsInstanceOf(credential); var managedIdentityCredential = (ManagedIdentityCredential)credential; - var client = (ManagedIdentityClient)typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential); - var clientId = typeof(ManagedIdentityClient).GetProperty("ClientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client); + string clientId; + int idType; + ReflectIdAndType(managedIdentityCredential, out clientId, out idType); Assert.AreEqual("ConfigurationClientId", clientId); + Assert.AreEqual(1, idType); // 1 is the value for ClientId } [Test] - [Ignore("This test is failing, ignore it to pass CI. Tracking this in https://github.com/Azure/azure-sdk-for-net/issues/45806")] public void CreatesManagedServiceIdentityCredentials() { IConfiguration configuration = GetConfiguration( @@ -381,10 +396,12 @@ public void CreatesManagedServiceIdentityCredentials() Assert.IsInstanceOf(credential); var managedIdentityCredential = (ManagedIdentityCredential)credential; - var client = (ManagedIdentityClient)typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential); - var clientId = typeof(ManagedIdentityClient).GetProperty("ClientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client); + string clientId; + int idType; + ReflectIdAndType(managedIdentityCredential, out clientId, out idType); Assert.Null(clientId); + Assert.AreEqual(0, idType); // 0 is the value for SystemAssigned } [Test] @@ -400,9 +417,33 @@ public void CreatesManagedServiceIdentityCredentialsWithResourceId() Assert.IsInstanceOf(credential); var managedIdentityCredential = (ManagedIdentityCredential)credential; - var resourceId = (string)typeof(ManagedIdentityCredential).GetField("_clientId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential); + string resourceId; + int idType; + ReflectIdAndType(managedIdentityCredential, out resourceId, out idType); Assert.AreEqual("ConfigurationResourceId", resourceId); + Assert.AreEqual(2, idType); // 2 is the value for ResourceId + } + + [Test] + public void CreatesManagedServiceIdentityCredentialsWithObjectId() + { + IConfiguration configuration = GetConfiguration( + new KeyValuePair("managedIdentityObjectId", "ConfigurationObjectId"), + new KeyValuePair("credential", "managedidentity") + ); + + var credential = ClientFactory.CreateCredential(configuration); + + Assert.IsInstanceOf(credential); + var managedIdentityCredential = (ManagedIdentityCredential)credential; + + string objectId; + int idType; + ReflectIdAndType(managedIdentityCredential, out objectId, out idType); + + Assert.AreEqual("ConfigurationObjectId", objectId); + Assert.AreEqual(3, idType); // 3 is the value for ObjectId } [Test] @@ -419,6 +460,34 @@ public void CreatesManagedServiceIdentityCredentialsThrowsWhenResourceIdAndClien Throws.InstanceOf().With.Message.Contains("managedIdentityResourceId")); } + [Test] + public void CreatesManagedServiceIdentityCredentialsThrowsWhenClientIdAndObjectIdSpecified() + { + IConfiguration configuration = GetConfiguration( + new KeyValuePair("managedIdentityObjectId", "ConfigurationObjectId"), + new KeyValuePair("clientId", "ConfigurationClientId"), + new KeyValuePair("credential", "managedidentity") + ); + + Assert.That( + () => ClientFactory.CreateCredential(configuration), + Throws.InstanceOf().With.Message.Contains("managedIdentityResourceId")); + } + + [Test] + public void CreatesManagedServiceIdentityCredentialsThrowsWhenResourceIdAndObjectIdSpecified() + { + IConfiguration configuration = GetConfiguration( + new KeyValuePair("managedIdentityObjectId", "ConfigurationObjectId"), + new KeyValuePair("managedIdentityResourceId", "ConfigurationResourceId"), + new KeyValuePair("credential", "managedidentity") + ); + + Assert.That( + () => ClientFactory.CreateCredential(configuration), + Throws.InstanceOf().With.Message.Contains("managedIdentityResourceId")); + } + [Test] public void CreatesWorkloadIdentityCredentialsWithOptions() { @@ -561,5 +630,14 @@ private IConfiguration GetConfiguration(params KeyValuePair[] it { return new ConfigurationBuilder().AddInMemoryCollection(items).Build(); } + + private static void ReflectIdAndType(ManagedIdentityCredential managedIdentityCredential, out string clientId, out int idType) + { + var managedIdentityClient = typeof(ManagedIdentityCredential).GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityCredential); + var managedIdentityClientOptions = managedIdentityClient.GetType().GetField("_options", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityClient); + var managedIdentityId = managedIdentityClientOptions.GetType().GetProperty("ManagedIdentityId").GetValue(managedIdentityClientOptions); + clientId = (string)typeof(ManagedIdentityId).GetField("_userAssignedId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityId); + idType = (int)typeof(ManagedIdentityId).GetField("_idType", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(managedIdentityId); + } } }