Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/resourcemanager/azure-resourcemanager/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/resourcemanager/azure-resourcemanager",
"Tag": "java/resourcemanager/azure-resourcemanager_ac929eccb7"
"Tag": "java/resourcemanager/azure-resourcemanager_b40f5da6f5"
}
23 changes: 23 additions & 0 deletions sdk/resourcemanager/azure-resourcemanager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,29 @@
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.1</version> <!-- {x-version-update;org.codehaus.mojo:build-helper-maven-plugin;external_dependency} -->
<executions>
<!-- Makes files under src/samples/resources available to samples (which run as tests). -->
<execution>
<id>add-samples-resources</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/samples/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {

try {
// Create a web app with a system-assigned managed identity; that identity is used to reach SQL.
// HTTPS-only is enforced; minimum TLS 1.2 and FTPS-only are already the App Service defaults.
WebApp app = azureResourceManager.webApps()
.define(appName)
.withRegion(Region.US_WEST)
.withNewResourceGroup(rgName)
.withNewWindowsPlan(PricingTier.STANDARD_S1)
.withJavaVersion(JavaVersion.JAVA_11)
.withWebContainer(WebContainer.TOMCAT_9_0_NEWEST)
.withHttpsOnly(true)
.withSystemAssignedManagedServiceIdentity()
.create();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,40 @@
package com.azure.resourcemanager.appservice.samples;

import com.azure.core.credential.TokenCredential;
import com.azure.core.exception.HttpResponseException;
import com.azure.core.http.policy.HttpLogDetailLevel;
import com.azure.core.management.Region;
import com.azure.core.management.profile.AzureProfile;
import com.azure.core.models.AzureCloud;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.resourcemanager.AzureResourceManager;
import com.azure.resourcemanager.appservice.models.ConnectionStringType;
import com.azure.resourcemanager.appservice.models.JavaVersion;
import com.azure.resourcemanager.appservice.models.PricingTier;
import com.azure.resourcemanager.appservice.models.RuntimeStack;
import com.azure.resourcemanager.appservice.models.WebApp;
import com.azure.resourcemanager.appservice.models.WebContainer;
import com.azure.resourcemanager.authorization.models.BuiltInRole;
import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils;
import com.azure.resourcemanager.samples.SampleUtils;
import com.azure.resourcemanager.storage.models.StorageAccount;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* Azure App Service sample for connecting a web app to a storage account.
* - Create a storage account
* - Create a web app
* - Add the storage account connection string to the web app settings
* Azure App Service sample for connecting a web app to a storage account without secrets (passwordless).
* - Create a storage account with shared-key access disabled
* - Create a Node.js web app with a system-assigned managed identity
* - Grant that identity the Storage Blob Data Contributor role on the account
* - Expose only the blob endpoint (no account key or connection string) to the web app
* - Deploy a small Node.js app that reads/writes a blob using that managed identity
* <p>
* The deployed app authenticates to Blob storage with {@code DefaultAzureCredential}, which picks up the web app's
* managed identity. Its source is under {@code src/samples/resources/appservice/mi-blob-verify}.
*/
public final class ConnectWebAppToStorageAccount {

Expand All @@ -39,30 +54,57 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {
final String containerName = SampleUtils.randomResourceName(azureResourceManager, "jcontainer", 20);

try {
// Create a storage account for the web app to use.
// Create a storage account with shared-key access disabled, so data-plane access requires
// Microsoft Entra credentials (passwordless) rather than an account key.
StorageAccount storageAccount = azureResourceManager.storageAccounts()
.define(storageName)
.withRegion(Region.JAPAN_EAST)
.withNewResourceGroup(rgName)
.disableSharedKeyAccess()
.create();

String accountKey = storageAccount.getKeys().get(0).value();
String connectionString = String.format("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s",
storageAccount.name(), accountKey);

// Create a web app and store the storage connection string in its settings.
// Create a Node.js web app with a system-assigned managed identity and expose only the blob endpoint.
// HTTPS-only is enforced here; minimum TLS 1.2 and FTPS-only are already the App Service defaults.
WebApp app = azureResourceManager.webApps()
.define(appName)
.withRegion(Region.JAPAN_EAST)
.withExistingResourceGroup(rgName)
.withNewWindowsPlan(PricingTier.STANDARD_S1)
.withJavaVersion(JavaVersion.JAVA_11)
.withWebContainer(WebContainer.TOMCAT_9_0_NEWEST)
.withConnectionString("storage.connectionString", connectionString, ConnectionStringType.CUSTOM)
.withAppSetting("storage.containerName", containerName)
.withNewLinuxPlan(PricingTier.STANDARD_S1)
.withBuiltInImage(RuntimeStack.NODEJS_22_LTS)
.withHttpsOnly(true)
.withSystemAssignedManagedServiceIdentity()
.withAppSetting("STORAGE_BLOB_ENDPOINT", storageAccount.endPoints().primary().blob())
.withAppSetting("STORAGE_CONTAINER_NAME", containerName)
.create();

// Grant the web app's managed identity data-plane access to blobs (least-privilege, passwordless).
azureResourceManager.accessManagement()
.roleAssignments()
.define(SampleUtils.randomUuid(azureResourceManager))
.forObjectId(app.systemAssignedManagedServiceIdentityPrincipalId())
.withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR)
.withResourceScope(storageAccount)
.create();

// Deploy the small Node.js app that touches a blob using the web app's managed identity.
// A freshly created web app may briefly have an initial deployment in progress, so retry on HTTP 409.
File appPackage = nodeAppPackage();
for (int i = 0;; i++) {
try {
app.zipDeploy(appPackage);
break;
} catch (HttpResponseException e) {
if (i >= 5 || e.getResponse() == null || e.getResponse().getStatusCode() != 409) {
throw e;
}
ResourceManagerUtils.sleep(Duration.ofSeconds(30));
}
}

System.out.println("Connected web app " + app.name() + " to storage account " + storageAccount.name());
// To verify manually, browse to https://<app>.azurewebsites.net/ once role assignment has propagated;
// the app returns {"ok":true,...} when it can read/write a blob using its managed identity.
System.out.println("Verify passwordless access at: https://" + app.defaultHostname() + "/");
return true;
} finally {
azureResourceManager.resourceGroups().beginDeleteByName(rgName);
Expand Down Expand Up @@ -92,6 +134,37 @@ public static void main(String[] args) {
}
}

/**
* Builds a zip package (server.js + package.json) for the small Node.js verification app from the sample
* resources. The verifier has no npm dependencies, so App Service deploys it without a build step.
*
* @return the zip package file
*/
private static File nodeAppPackage() {
String[] files = { "server.js", "package.json" };
try {
File zipFile = File.createTempFile("mi-blob-verify", ".zip");
zipFile.deleteOnExit();
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
byte[] buffer = new byte[4096];
for (String file : files) {
zos.putNextEntry(new ZipEntry(file));
try (InputStream is = ConnectWebAppToStorageAccount.class
.getResourceAsStream("/appservice/mi-blob-verify/" + file)) {
int read;
while ((read = is.read(buffer)) > 0) {
zos.write(buffer, 0, read);
}
}
zos.closeEntry();
Comment thread
weidongxu-microsoft marked this conversation as resolved.
}
}
return zipFile;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private ConnectWebAppToStorageAccount() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {
String privateImage = azureRegistry.loginServerUrl() + "/samples/tomcat:latest";

// Create a Linux web app that pulls its image from the private registry.
// HTTPS-only is enforced; minimum TLS 1.2 and FTPS-only are already the App Service defaults.
WebApp app = azureResourceManager.webApps()
.define(appName)
.withRegion(region)
Expand All @@ -60,6 +61,7 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {
.withPrivateRegistryImage(privateImage, "https://" + azureRegistry.loginServerUrl())
.withCredentials(acrCredentials.username(), acrCredentials.accessKeys().get(AccessKeyType.PRIMARY))
.withAppSetting("PORT", "8080")
.withHttpsOnly(true)
.create();

System.out.println("Deployed image " + privateImage + " to web app " + app.defaultHostname());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {

try {
// Create a web app running Tomcat on a Windows plan.
// HTTPS-only is enforced; minimum TLS 1.2 and FTPS-only are already the App Service defaults.
WebApp app = azureResourceManager.webApps()
.define(appName)
.withRegion(Region.US_EAST)
.withNewResourceGroup(rgName)
.withNewWindowsPlan(PricingTier.STANDARD_S1)
.withJavaVersion(JavaVersion.JAVA_11)
.withWebContainer(WebContainer.TOMCAT_9_0_NEWEST)
.withHttpsOnly(true)
.create();

// Create a staging deployment slot that inherits the production configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ public static boolean runSample(AzureResourceManager azureResourceManager) {

try {
// Create a web app with a new app service plan.
// HTTPS-only is enforced; minimum TLS 1.2 and FTPS-only are already the App Service defaults.
WebApp app = azureResourceManager.webApps()
.define(appName)
.withRegion(Region.US_WEST)
.withNewResourceGroup(rgName)
.withNewWindowsPlan(PricingTier.STANDARD_S1)
.withHttpsOnly(true)
.create();

// Purchase an Azure-managed domain.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private static WebApp createWebApp(AzureResourceManager azureResourceManager, St
.withExistingLinuxPlan(plan)
.withExistingResourceGroup(rgName)
.withBuiltInImage(RuntimeStack.JAVA_17_JAVA17)
.withHttpsOnly(true)
.create();
}

Expand Down
Loading
Loading