diff --git a/sdk/identity/Azure.Identity/assets.json b/sdk/identity/Azure.Identity/assets.json
index ea01c6b3b2d85..a93ca15ca276f 100644
--- a/sdk/identity/Azure.Identity/assets.json
+++ b/sdk/identity/Azure.Identity/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/identity/Azure.Identity",
- "Tag": "net/identity/Azure.Identity_7f050cb3f3"
+ "Tag": "net/identity/Azure.Identity_f0e02fe424"
}
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/Integration.Identity.Common.csproj b/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/Integration.Identity.Common.csproj
new file mode 100644
index 0000000000000..3dc20577717f2
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/Integration.Identity.Common.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/ManagedIdentityTests.cs b/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/ManagedIdentityTests.cs
new file mode 100644
index 0000000000000..45ef41859932b
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Common/ManagedIdentityTests.cs
@@ -0,0 +1,22 @@
+using Azure.Identity;
+using Azure.Storage.Blobs;
+using Azure.Core;
+
+namespace Integration.Identity.Common;
+
+public static class ManagedIdentityTests
+{
+ public static void AuthToStorage()
+ {
+ string resourceId = Environment.GetEnvironmentVariable("IDENTITY_WEBAPP_USER_DEFINED_IDENTITY")!;
+ string account1 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_1")!;
+ string account2 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_2")!;
+
+ var credential1 = new ManagedIdentityCredential();
+ var credential2 = new ManagedIdentityCredential(new ResourceIdentifier(resourceId));
+ var client1 = new BlobServiceClient(new Uri($"https://{account1}.blob.core.windows.net/"), credential1);
+ var client2 = new BlobServiceClient(new Uri($"https://{account2}.blob.core.windows.net/"), credential2);
+ client1.GetBlobContainers().ToList();
+ client2.GetBlobContainers().ToList();
+ }
+}
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Function1.cs b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Function1.cs
new file mode 100644
index 0000000000000..a28f57ccbde1e
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Function1.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Integration.Identity.Common;
+
+namespace Integration.Identity.Func
+{
+ public static class Function1
+ {
+ [FunctionName("Function1")]
+ public static async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
+ ILogger log)
+ {
+ log.LogInformation("C# HTTP trigger function processed a request.");
+
+ try
+ {
+ ManagedIdentityTests.AuthToStorage();
+ return new OkObjectResult("Successfully acquired a token from ManagedIdentityCredential");
+ }
+ catch (Exception ex)
+ {
+ return new BadRequestObjectResult(ex.ToString());
+ }
+ }
+ }
+}
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Integration.Identity.Func.csproj b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Integration.Identity.Func.csproj
new file mode 100644
index 0000000000000..822d40d898bcf
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Integration.Identity.Func.csproj
@@ -0,0 +1,21 @@
+
+
+ net6.0
+ v4
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.json b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.json
new file mode 100644
index 0000000000000..c264e8ca80b19
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "appInsights1": {
+ "type": "appInsights"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.local.json b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.local.json
new file mode 100644
index 0000000000000..5a956e8592682
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/Properties/serviceDependencies.local.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "appInsights1": {
+ "type": "appInsights.sdk"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/host.json b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/host.json
new file mode 100644
index 0000000000000..d13723c98ce65
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/host.json
@@ -0,0 +1,10 @@
+{
+ "version": "2.0",
+ "logging": {
+ "fileLoggingMode": "always",
+ "logLevel": {
+ "Function.MyFunction": "Information",
+ "default": "None"
+ }
+ }
+}
diff --git a/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/local.settings.json b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/local.settings.json
new file mode 100644
index 0000000000000..bf70960ee259c
--- /dev/null
+++ b/sdk/identity/Azure.Identity/integration/Integration.Identity.Func/local.settings.json
@@ -0,0 +1,7 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "AzureWebJobsStorage": "",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet"
+ }
+}
\ No newline at end of file
diff --git a/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs b/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs
index 0e792196f251a..470bee3938dee 100644
--- a/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs
+++ b/sdk/identity/Azure.Identity/integration/WebApp/Controllers/TestController.cs
@@ -4,6 +4,7 @@
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.AspNetCore.Mvc;
+using Integration.Identity.Common;
namespace WebApp.Controllers
{
@@ -16,18 +17,9 @@ public class TestController : ControllerBase
[HttpGet(Name = "GetTest")]
public IActionResult Get()
{
- string resourceId = Environment.GetEnvironmentVariable("IDENTITY_WEBAPP_USER_DEFINED_IDENTITY")!;
- string account1 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_1")!;
- string account2 = Environment.GetEnvironmentVariable("IDENTITY_STORAGE_NAME_2")!;
-
- var credential1 = new ManagedIdentityCredential();
- var credential2 = new ManagedIdentityCredential(new ResourceIdentifier(resourceId));
- var client1 = new BlobServiceClient(new Uri($"https://{account1}.blob.core.windows.net/"), credential1);
- var client2 = new BlobServiceClient(new Uri($"https://{account2}.blob.core.windows.net/"), credential2);
try
{
- var results = client1.GetBlobContainers().ToList();
- results = client2.GetBlobContainers().ToList();
+ ManagedIdentityTests.AuthToStorage();
return Ok("Successfully acquired a token from ManagedIdentityCredential");
}
catch (Exception ex)
diff --git a/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj b/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj
index 8460903024cfe..9a7894b5d4ba9 100644
--- a/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj
+++ b/sdk/identity/Azure.Identity/integration/WebApp/Integration.Identity.WebApp.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/sdk/identity/Azure.Identity/integration/nuget.config b/sdk/identity/Azure.Identity/integration/nuget.config
deleted file mode 100644
index 9ac17c067abd9..0000000000000
--- a/sdk/identity/Azure.Identity/integration/nuget.config
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs
index a760018dc9a4d..472b8d9e529d0 100644
--- a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs
+++ b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs
@@ -39,5 +39,6 @@ public class IdentityTestEnvironment : TestEnvironment
public string ServicePrincipalCertificatePemPath => GetOptionalVariable("IDENTITY_SP_CERT_PEM") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem");
public string ServicePrincipalSniCertificatePath => GetOptionalVariable("IDENTITY_SP_CERT_SNI") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx");
public string IdentityTestWebName => GetRecordedVariable("IDENTITY_WEBAPP_NAME");
+ public string IdentityTestAzFuncName => GetRecordedVariable("IDENTITY_FUNCTION_NAME");
}
}
diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialIntegrationTests.cs
similarity index 50%
rename from sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs
rename to sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialIntegrationTests.cs
index 8a7d7ed4e3ed3..4fb5562adc23a 100644
--- a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialWebAppTests.cs
+++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialIntegrationTests.cs
@@ -11,19 +11,17 @@
namespace Azure.Identity.Tests
{
- public class ManagedIdentityCredentialWebAppTests : IdentityRecordedTestBase
+ public class ManagedIdentityCredentialIntegrationTests : IdentityRecordedTestBase
{
private HttpPipeline _pipeline;
- private Uri _testEndpoint;
- public ManagedIdentityCredentialWebAppTests(bool isAsync) : base(isAsync)
+ public ManagedIdentityCredentialIntegrationTests(bool isAsync) : base(isAsync)
{
}
[SetUp]
public void Setup() {
var options = new TokenCredentialOptions();
- _testEndpoint = new Uri($"https://{TestEnvironment.IdentityTestWebName}.azurewebsites.net/test");
_pipeline = HttpPipelineBuilder.Build(InstrumentClientOptions(options), Array.Empty(), Array.Empty(), new ResponseClassifier());
}
@@ -31,10 +29,26 @@ public void Setup() {
[SyncOnly]
// This test leverages the test app found in Azure.Identity\integration\WebApp
// It validates that ManagedIdentityCredential can acquire a token in an actual Azure Web App environment
- public async Task CallTestWebApp()
+ public async Task CallIntegrationTestWebApp()
{
+ var testEndpoint = new Uri($"https://{TestEnvironment.IdentityTestWebName}.azurewebsites.net/test");
Request request = _pipeline.CreateRequest();
- request.Uri.Reset(_testEndpoint);
+ request.Uri.Reset(testEndpoint);
+ Response response = await _pipeline.SendRequestAsync(request, default);
+
+ Assert.AreEqual((int)HttpStatusCode.OK, response.Status);
+ Assert.AreEqual("Successfully acquired a token from ManagedIdentityCredential", response.Content.ToString(), response.Content.ToString());
+ }
+
+ [RecordedTest]
+ [SyncOnly]
+ // This test leverages the test app found in Azure.Identity\integration\Integration.Identity.Func
+ // It validates that ManagedIdentityCredential can acquire a token in an actual Azure Web App environment
+ public async Task CallIntegrationTestAzFunction()
+ {
+ var testEndpoint = new Uri($"https://{TestEnvironment.IdentityTestAzFuncName}.azurewebsites.net/api/function1");
+ Request request = _pipeline.CreateRequest();
+ request.Uri.Reset(testEndpoint);
Response response = await _pipeline.SendRequestAsync(request, default);
Assert.AreEqual((int)HttpStatusCode.OK, response.Status);
diff --git a/sdk/identity/test-resources-post.ps1 b/sdk/identity/test-resources-post.ps1
index cc8637d9a4e3e..d9fc316cffbb5 100644
--- a/sdk/identity/test-resources-post.ps1
+++ b/sdk/identity/test-resources-post.ps1
@@ -9,11 +9,21 @@ if ($null -ne $Env:AGENT_WORKFOLDER) {
}
az login --service-principal -u $DeploymentOutputs['IDENTITY_CLIENT_ID'] -p $DeploymentOutputs['IDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['IDENTITY_TENANT_ID']
az account set --subscription $DeploymentOutputs['IDENTITY_SUBSCRIPTION_ID']
+
+# Deploy the webapp
dotnet publish "$webappRoot/WebApp/Integration.Identity.WebApp.csproj" -o "$workingFolder/Pub" /p:EnableSourceLink=false
Compress-Archive -Path "$workingFolder/Pub/*" -DestinationPath "$workingFolder/Pub/package.zip" -Force
az webapp deploy --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --name $DeploymentOutputs['IDENTITY_WEBAPP_NAME'] --src-path "$workingFolder/Pub/package.zip"
+
+# clean up
Remove-Item -Force -Recurse "$workingFolder/Pub"
-if ($null -eq $Env:AGENT_WORKFOLDER) {
- Remove-Item -Force -Recurse "$webappRoot/%AGENT_WORKFOLDER%"
-}
+
+# Deploy the function app
+dotnet publish "$webappRoot/Integration.Identity.Func/Integration.Identity.Func.csproj" -o "$workingFolder/Pub" /p:EnableSourceLink=false
+Compress-Archive -Path "$workingFolder/Pub/*" -DestinationPath "$workingFolder/Pub/package.zip" -Force
+az functionapp deployment source config-zip -g $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] -n $DeploymentOutputs['IDENTITY_FUNCTION_NAME'] --src "$workingFolder/Pub/package.zip"
+
+# clean up
+Remove-Item -Force -Recurse "$workingFolder/Pub"
+
az logout
\ No newline at end of file
diff --git a/sdk/identity/test-resources.bicep b/sdk/identity/test-resources.bicep
index b175d52a5e5ee..7162aa11b24c8 100644
--- a/sdk/identity/test-resources.bicep
+++ b/sdk/identity/test-resources.bicep
@@ -9,6 +9,9 @@ param baseName string = resourceGroup().name
@description('The location of the resource. By default, this is the same as the resource group.')
param location string = resourceGroup().location
+param runtime string = 'node'
+var functionWorkerRuntime = runtime
+
//See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var blobContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') //Storage Blob Data Contributor
var websiteContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') //Website Contributor
@@ -28,6 +31,16 @@ resource blobRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
}
}
+resource blobRoleFunc 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: sa
+ name: guid(resourceGroup().id, blobContributor, 'azfunc')
+ properties: {
+ principalId: azfunc.identity.principalId
+ roleDefinitionId: blobContributor
+ principalType: 'ServicePrincipal'
+ }
+}
+
resource blobRole2 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: sa2
name: guid(resourceGroup().id, blobContributor, usermgdid.id)
@@ -76,18 +89,76 @@ resource farm 'Microsoft.Web/serverfarms@2021-03-01' = {
name: '${baseName}_asp'
location: location
sku: {
- name: 'F1'
- tier: 'Free'
- size: 'F1'
- family: 'F'
- capacity: 0
+ name: 'B1'
+ tier: 'Bassic'
+ size: 'B1'
+ family: 'B'
+ capacity: 1
}
properties: { }
kind: 'app'
}
+resource azfunc 'Microsoft.Web/sites@2021-03-01' = {
+ name: '${baseName}func'
+ location: location
+ kind: 'functionapp'
+ identity: {
+ type: 'SystemAssigned, UserAssigned'
+ userAssignedIdentities: {
+ '${usermgdid.id}' : { }
+ }
+ }
+ properties: {
+ enabled: true
+ serverFarmId: farm.id
+ httpsOnly: true
+ keyVaultReferenceIdentity: 'SystemAssigned'
+ siteConfig: {
+ alwaysOn: true
+ netFrameworkVersion: 'v6.0'
+ http20Enabled: true
+ minTlsVersion: '1.2'
+ appSettings: [
+ {
+ name: 'IDENTITY_STORAGE_NAME_1'
+ value: sa.name
+ }
+ {
+ name: 'IDENTITY_STORAGE_NAME_2'
+ value: sa2.name
+ }
+ {
+ name: 'IDENTITY_WEBAPP_USER_DEFINED_IDENTITY'
+ value: usermgdid.id
+ }
+ {
+ name: 'AzureWebJobsStorage'
+ value: 'DefaultEndpointsProtocol=https;AccountName=${sa.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${sa.listKeys().keys[0].value}'
+ }
+ {
+ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
+ value: 'DefaultEndpointsProtocol=https;AccountName=${sa.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${sa.listKeys().keys[0].value}'
+ }
+ {
+ name: 'WEBSITE_CONTENTSHARE'
+ value: toLower('${baseName}-func')
+ }
+ {
+ name: 'FUNCTIONS_EXTENSION_VERSION'
+ value: '~4'
+ }
+ {
+ name: 'FUNCTIONS_WORKER_RUNTIME'
+ value: 'dotnet'
+ }
+ ]
+ }
+ }
+}
+
resource web 'Microsoft.Web/sites@2021-03-01' = {
- name: '${baseName}-webapp'
+ name: '${baseName}webapp'
location: location
kind: 'app'
identity: {
@@ -127,7 +198,26 @@ resource web 'Microsoft.Web/sites@2021-03-01' = {
}
}
+resource scmweb 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01' = {
+ kind: 'app'
+ parent: web
+ name: 'scm'
+ properties: {
+ allow: true
+ }
+}
+
+resource scmfunc 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01' = {
+ kind: 'functionapp'
+ parent: azfunc
+ name: 'scm'
+ properties: {
+ allow: true
+ }
+}
+
output IDENTITY_WEBAPP_NAME string = web.name
output IDENTITY_WEBAPP_USER_DEFINED_IDENTITY string = usermgdid.id
output IDENTITY_STORAGE_NAME_1 string = sa.name
output IDENTITY_STORAGE_NAME_2 string = sa2.name
+output IDENTITY_FUNCTION_NAME string = azfunc.name