diff --git a/azure-functions/README.md b/azure-functions/README.md index 0626db9..47f9dc8 100644 --- a/azure-functions/README.md +++ b/azure-functions/README.md @@ -7,12 +7,10 @@ products: - azure-clis - azure-functions name: Azure Functions sample scripts -url-fragment: +url-fragment: description: These scripts demonstrate how to create and manage Azure Functions resources using the Azure CLI. --- -# Azure Functions - -## Azure CLI sample scripts +# Azure Functions: Azure CLI sample scripts These end-to-end Azure CLI scripts help you learn how to provision and manage the Azure resources required by Azure Functions. You must use the [Azure Functions Core Tools][func-core-tools] to create actual Azure Functions code projects from the command line on your local computer and deploy code to these Azure resources. @@ -20,6 +18,40 @@ For a complete end-to-end example of developing and deploying from the command l The scripts in this directory demonstrate working with [Azure Functions][func-home] using the [Azure CLI reference commands][azure-cli]. +## Prerequisites + +- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio). + +- Use the Bash environment in [Azure Cloud Shell](https://learn.microsoft.com/azure/cloud-shell/overview). Cloud Shell has the Azure CLI and required tools like `jq` preinstalled. You can [open Cloud Shell in a new window](https://shell.azure.com). + +- If you prefer to run the scripts locally, [install the Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) (version 2.64 or later) and sign in with [az login](https://learn.microsoft.com/cli/azure/reference-index#az-login). You also need [jq](https://jqlang.github.io/jq/download/) installed for the Flex Consumption scripts that parse JSON output. + +## Run a script + +Each script is self-contained and uses random identifiers to create uniquely named resources. To run a script: + +1. Open Azure Cloud Shell or your local Bash terminal signed in to Azure. +1. Navigate to the folder that contains the script you want to run. +1. Run the script directly: + + ```bash + bash .sh + ``` + +The scripts set variables at the top for resource names, locations, and SKUs. You can modify these before running. + +## Clean up resources + +Every script creates resources in a new resource group. After you're done, you can delete all the resources created by a script by deleting its resource group: + +```azurecli +az group delete --name $resourceGroup --yes --no-wait +``` + +Each script also includes a commented-out cleanup command at the end that you can uncomment to enable automatic cleanup. + +## Sample scripts + | Script | Description | | ------ | ----------- | |**Create a function app**|| @@ -32,6 +64,9 @@ The scripts in this directory demonstrate working with [Azure Functions][func-ho |[create-function-app-connect-to-cosmos-db.sh][af-6]| Creates a function app in a [Flex Consumption plan][plan-flex] and connects it to Azure Cosmos DB using managed identity and RBAC. | |[connect-azure-openai-resources.sh][af-7]| Creates a function app in a [Flex Consumption plan][plan-flex] and connects it to Azure OpenAI using managed identity. | |[functions-cli-mount-files-storage-linux.sh][af-8]| Creates a Linux function app and mounts an Azure Files share, which lets you leverage existing data or machine learning models in your functions. | +|**Secure networking**|| +|[create-function-app-vnet-storage.sh][af-10]| Creates a function app in a [Flex Consumption plan][plan-flex] with VNet integration and restricts the storage account behind private endpoints so it's only accessible from inside the virtual network. | +|[create-function-app-private-endpoint.sh][af-11]| Creates a function app in a [Flex Consumption plan][plan-flex] with an inbound private endpoint, restricting the function app's HTTP endpoints to only be callable from inside the virtual network. | |**Deploy code**|| |[deploy-function-app-with-function-github-continuous.sh][af-9]| Creates a function app in a [Consumption plan][plan-consumption] and deploys code from a public GitHub repository. | @@ -45,6 +80,8 @@ The scripts in this directory demonstrate working with [Azure Functions][func-ho [af-7]: ./connect-azure-openai-resources/connect-azure-openai-resources.sh [af-8]: ./functions-cli-mount-files-storage-linux/functions-cli-mount-files-storage-linux.sh [af-9]: ./deploy-function-app-with-function-github-continuous/deploy-function-app-with-function-github-continuous.sh +[af-10]: ./create-function-app-vnet-storage/create-function-app-vnet-storage.sh +[af-11]: ./create-function-app-private-endpoint/create-function-app-private-endpoint.sh [func-home]: https://learn.microsoft.com/azure/azure-functions/ @@ -55,3 +92,85 @@ The scripts in this directory demonstrate working with [Azure Functions][func-ho [plan-consumption]: https://learn.microsoft.com/azure/azure-functions/consumption-plan [plan-premium]: https://learn.microsoft.com/azure/azure-functions/functions-premium-plan [plan-dedicated]: https://learn.microsoft.com/azure/azure-functions/dedicated-plan + +## CLI command reference + +The following table lists the Azure CLI commands used across these sample scripts. + +### Resource management + +| Command | Notes | +|---|---| +| [az group create](https://learn.microsoft.com/cli/azure/group#az-group-create) | Creates a resource group to contain all script resources. | +| [az group delete](https://learn.microsoft.com/cli/azure/group#az-group-delete) | Deletes a resource group and all contained resources. | + +### Storage + +| Command | Notes | +|---|---| +| [az storage account create](https://learn.microsoft.com/cli/azure/storage/account#az-storage-account-create) | Creates an Azure Storage account. | +| [az storage account show](https://learn.microsoft.com/cli/azure/storage/account#az-storage-account-show) | Gets storage account details, including the resource ID for role assignments. | +| [az storage account update](https://learn.microsoft.com/cli/azure/storage/account#az-storage-account-update) | Updates storage account properties, such as disabling public network access. | +| [az storage share create](https://learn.microsoft.com/cli/azure/storage/share#az-storage-share-create) | Creates a file share in Azure Files. | +| [az storage directory create](https://learn.microsoft.com/cli/azure/storage/directory#az-storage-directory-create) | Creates a directory in an Azure Files share. | + +### Function apps + +| Command | Notes | +|---|---| +| [az functionapp create](https://learn.microsoft.com/cli/azure/functionapp#az-functionapp-create) | Creates a function app in a Consumption, Flex Consumption, Premium, or Dedicated plan. | +| [az functionapp plan create](https://learn.microsoft.com/cli/azure/functionapp/plan#az-functionapp-plan-create) | Creates a Premium or App Service hosting plan for a function app. | +| [az functionapp config appsettings set](https://learn.microsoft.com/cli/azure/functionapp/config/appsettings#az-functionapp-config-appsettings-set) | Creates or updates application settings in a function app. | +| [az functionapp config appsettings delete](https://learn.microsoft.com/cli/azure/functionapp/config/appsettings#az-functionapp-config-appsettings-delete) | Removes application settings from a function app. | +| [az functionapp show](https://learn.microsoft.com/cli/azure/functionapp#az-functionapp-show) | Gets the details of a function app, including the resource ID. | + +### Identity and access + +| Command | Notes | +|---|---| +| [az identity create](https://learn.microsoft.com/cli/azure/identity#az-identity-create) | Creates a user-assigned managed identity. | +| [az identity show](https://learn.microsoft.com/cli/azure/identity#az-identity-show) | Gets the properties of a managed identity, including the client ID. | +| [az role assignment create](https://learn.microsoft.com/cli/azure/role/assignment#az-role-assignment-create) | Assigns an Azure RBAC role to a managed identity or user account. | +| [az ad signed-in-user show](https://learn.microsoft.com/cli/azure/ad/signed-in-user#az-ad-signed-in-user-show) | Gets the object ID of the current signed-in Azure account. | + +### Monitoring + +| Command | Notes | +|---|---| +| [az monitor app-insights component show](https://learn.microsoft.com/cli/azure/monitor/app-insights/component#az-monitor-app-insights-component-show) | Gets the Application Insights resource for a function app. | +| [az extension add](https://learn.microsoft.com/cli/azure/extension#az-extension-add) | Installs CLI extensions, such as the `application-insights` extension. | + +### Azure Cosmos DB + +| Command | Notes | +|---|---| +| [az cosmosdb create](https://learn.microsoft.com/cli/azure/cosmosdb#az-cosmosdb-create) | Creates an Azure Cosmos DB account. | +| [az cosmosdb show](https://learn.microsoft.com/cli/azure/cosmosdb#az-cosmosdb-show) | Gets account details, including the document endpoint. | +| [az cosmosdb sql database create](https://learn.microsoft.com/cli/azure/cosmosdb/sql/database#az-cosmosdb-sql-database-create) | Creates a database in a Cosmos DB account. | +| [az cosmosdb sql container create](https://learn.microsoft.com/cli/azure/cosmosdb/sql/container#az-cosmosdb-sql-container-create) | Creates a container in a Cosmos DB SQL database. | +| [az cosmosdb sql role assignment create](https://learn.microsoft.com/cli/azure/cosmosdb/sql/role/assignment#az-cosmosdb-sql-role-assignment-create) | Assigns a Cosmos DB data-plane RBAC role to a principal. | + +### Azure OpenAI + +| Command | Notes | +|---|---| +| [az cognitiveservices account create](https://learn.microsoft.com/cli/azure/cognitiveservices/account#az-cognitiveservices-account-create) | Creates an Azure OpenAI (Cognitive Services) resource. | + +### Networking + +| Command | Notes | +|---|---| +| [az network vnet create](https://learn.microsoft.com/cli/azure/network/vnet#az-network-vnet-create) | Creates a virtual network. | +| [az network vnet subnet create](https://learn.microsoft.com/cli/azure/network/vnet/subnet#az-network-vnet-subnet-create) | Creates a subnet, optionally with a delegation for Functions VNet integration. | +| [az network private-endpoint create](https://learn.microsoft.com/cli/azure/network/private-endpoint#az-network-private-endpoint-create) | Creates a private endpoint for a storage account or function app. | +| [az network private-dns zone create](https://learn.microsoft.com/cli/azure/network/private-dns/zone#az-network-private-dns-zone-create) | Creates a private DNS zone for private endpoint name resolution. | +| [az network private-dns link vnet create](https://learn.microsoft.com/cli/azure/network/private-dns/link/vnet#az-network-private-dns-link-vnet-create) | Links a private DNS zone to a virtual network. | +| [az network private-endpoint dns-zone-group create](https://learn.microsoft.com/cli/azure/network/private-endpoint/dns-zone-group#az-network-private-endpoint-dns-zone-group-create) | Configures a private endpoint to register DNS records in a private DNS zone. | +| [az resource update](https://learn.microsoft.com/cli/azure/resource#az-resource-update) | Updates a resource property, such as disabling public network access on a function app. | + +## Other resources + +- [Azure Functions documentation](https://learn.microsoft.com/azure/azure-functions/) +- [Azure Functions Core Tools reference](https://learn.microsoft.com/azure/azure-functions/functions-run-local) +- [Azure CLI documentation](https://learn.microsoft.com/cli/azure/) +- [Create your first function from the command line](https://learn.microsoft.com/azure/azure-functions/how-to-create-function-azure-cli) diff --git a/azure-functions/create-function-app-private-endpoint/create-function-app-private-endpoint.sh b/azure-functions/create-function-app-private-endpoint/create-function-app-private-endpoint.sh new file mode 100644 index 0000000..8df9a00 --- /dev/null +++ b/azure-functions/create-function-app-private-endpoint/create-function-app-private-endpoint.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# Passed validation in Cloud Shell on 3/1/2026 + +# This script creates a function app in a Flex Consumption plan and restricts +# inbound access using a private endpoint, so the function app's HTTP endpoints +# can only be called from inside the virtual network. +# Function app, storage account, and user identity names must be unique. + +# Variable block +let "randomIdentifier=$RANDOM*$RANDOM" +location="eastus" +resourceGroup="msdocs-azure-functions-rg-$randomIdentifier" +tag="create-function-app-private-endpoint" +storage="msdocsaccount$randomIdentifier" +userIdentity="msdocs-managed-identity-$randomIdentifier" +functionApp="msdocs-serverless-function-$randomIdentifier" +vnetName="msdocs-vnet-$randomIdentifier" +subnetPrivateEndpoints="subnet-private-endpoints" +skuStorage="Standard_LRS" +functionsVersion="4" +languageWorker="python" +languageVersion="3.11" + +# Install the Application Insights extension +az extension add --name application-insights + +# Create a resource group +echo "Creating $resourceGroup in $location..." +az group create --name $resourceGroup --location "$location" --tags $tag + +# Create a virtual network +echo "Creating $vnetName" +az network vnet create --name $vnetName --resource-group $resourceGroup --location "$location" \ + --address-prefix 10.0.0.0/16 + +# Create a subnet for private endpoints +echo "Creating $subnetPrivateEndpoints for private endpoints" +az network vnet subnet create --name $subnetPrivateEndpoints --resource-group $resourceGroup \ + --vnet-name $vnetName --address-prefix 10.0.1.0/24 + +# Create an Azure storage account in the resource group with key access disabled. +echo "Creating $storage" +az storage account create --name $storage --location "$location" --resource-group $resourceGroup \ + --sku $skuStorage --allow-blob-public-access false --allow-shared-key-access false + +# Create a user-assigned managed identity +echo "Creating $userIdentity" +output=$(az identity create --name $userIdentity --resource-group $resourceGroup --location $location \ + --query "{userId:id, principalId: principalId, clientId: clientId}" -o json) + +# Use jq to parse the output and assign the properties to variables +userId=$(echo $output | jq -r '.userId') +principalId=$(echo $output | jq -r '.principalId') +clientId=$(echo $output | jq -r '.clientId') + +# Get the storage ID and create a role assignment (Storage Blob Data Owner) for the identity +storageId=$(az storage account show --resource-group $resourceGroup --name $storage --query 'id' -o tsv) +az role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal \ + --role "Storage Blob Data Owner" --scope $storageId + +# Create the function app in a Flex Consumption plan +echo "Creating $functionApp" +az functionapp create --resource-group $resourceGroup --name $functionApp --flexconsumption-location $location \ + --runtime $languageWorker --runtime-version $languageVersion --storage-account $storage \ + --deployment-storage-auth-type UserAssignedIdentity --deployment-storage-auth-value $userIdentity + +# Create a role assignment (Monitoring Metrics Publisher) in Application Insights for the user identity +appInsights=$(az monitor app-insights component show --resource-group $resourceGroup \ + --app $functionApp --query "id" --output tsv) +az role assignment create --role "Monitoring Metrics Publisher" --assignee $principalId --scope $appInsights + +# Update app settings to use managed identities for host storage connections +clientId=$(az identity show --name $userIdentity --resource-group $resourceGroup \ + --query 'clientId' -o tsv) +az functionapp config appsettings set --name $functionApp --resource-group $resourceGroup \ + --settings AzureWebJobsStorage__accountName=$storage AzureWebJobsStorage__credential=managedidentity \ + AzureWebJobsStorage__clientId=$clientId \ + APPLICATIONINSIGHTS_AUTHENTICATION_STRING="ClientId=$clientId;Authorization=AAD" +az functionapp config appsettings delete --name $functionApp \ + --resource-group $resourceGroup --setting-names AzureWebJobsStorage + +# Create a private endpoint in the VNet for the function app. +# This gives the function app a private IP address inside the VNet. +functionAppId=$(az functionapp show --name $functionApp --resource-group $resourceGroup --query 'id' -o tsv) +echo "Creating private endpoint for $functionApp" +az network private-endpoint create --name "pe-$functionApp" \ + --resource-group $resourceGroup --vnet-name $vnetName --subnet $subnetPrivateEndpoints \ + --private-connection-resource-id $functionAppId \ + --group-id sites --connection-name "conn-functionapp" --location "$location" + +# Create a private DNS zone for Azure Functions and link it to the VNet. +# This allows clients inside the VNet to resolve the function app's hostname +# to its private IP address. +dnsZoneName="privatelink.azurewebsites.net" +echo "Creating private DNS zone $dnsZoneName" +az network private-dns zone create --resource-group $resourceGroup --name $dnsZoneName +az network private-dns link vnet create --resource-group $resourceGroup \ + --name "link-functionapp" --zone-name $dnsZoneName --virtual-network $vnetName \ + --registration-enabled false +az network private-endpoint dns-zone-group create --resource-group $resourceGroup \ + --endpoint-name "pe-$functionApp" --name "default" \ + --private-dns-zone $dnsZoneName --zone-name sites + +# Disable public network access on the function app. +# After this, the function app's HTTP endpoints are only reachable +# from inside the VNet through the private endpoint. +echo "Disabling public access on $functionApp" +az resource update --resource-group $resourceGroup --name $functionApp \ + --resource-type "Microsoft.Web/sites" \ + --set properties.publicNetworkAccess=Disabled + +# echo "Deleting all resources" +# az group delete --name $resourceGroup -y diff --git a/azure-functions/create-function-app-vnet-storage/create-function-app-vnet-storage.sh b/azure-functions/create-function-app-vnet-storage/create-function-app-vnet-storage.sh new file mode 100644 index 0000000..079febf --- /dev/null +++ b/azure-functions/create-function-app-vnet-storage/create-function-app-vnet-storage.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Passed validation in Cloud Shell on 3/1/2026 + +# This script creates a function app in a Flex Consumption plan with VNet integration +# and restricts the storage account behind private endpoints so it's only accessible +# from inside the virtual network. Uses managed identity for all connections. +# Function app, storage account, and user identity names must be unique. + +# Variable block +let "randomIdentifier=$RANDOM*$RANDOM" +location="eastus" +resourceGroup="msdocs-azure-functions-rg-$randomIdentifier" +tag="create-function-app-vnet-storage" +storage="msdocsaccount$randomIdentifier" +userIdentity="msdocs-managed-identity-$randomIdentifier" +functionApp="msdocs-serverless-function-$randomIdentifier" +vnetName="msdocs-vnet-$randomIdentifier" +subnetFunctions="subnet-functions" +subnetPrivateEndpoints="subnet-private-endpoints" +skuStorage="Standard_LRS" +functionsVersion="4" +languageWorker="python" +languageVersion="3.11" + +# Install the Application Insights extension +az extension add --name application-insights + +# Create a resource group +echo "Creating $resourceGroup in $location..." +az group create --name $resourceGroup --location "$location" --tags $tag + +# Create a virtual network with a default subnet +echo "Creating $vnetName" +az network vnet create --name $vnetName --resource-group $resourceGroup --location "$location" \ + --address-prefix 10.0.0.0/16 + +# Create a subnet for the function app with delegation to Microsoft.App/environments +# (required for Flex Consumption VNet integration) +echo "Creating $subnetFunctions with Microsoft.App/environments delegation" +az network vnet subnet create --name $subnetFunctions --resource-group $resourceGroup \ + --vnet-name $vnetName --address-prefix 10.0.1.0/24 \ + --delegations Microsoft.App/environments + +# Create a subnet for private endpoints +echo "Creating $subnetPrivateEndpoints for private endpoints" +az network vnet subnet create --name $subnetPrivateEndpoints --resource-group $resourceGroup \ + --vnet-name $vnetName --address-prefix 10.0.2.0/24 + +# Create an Azure storage account in the resource group with key access disabled. +echo "Creating $storage" +az storage account create --name $storage --location "$location" --resource-group $resourceGroup \ + --sku $skuStorage --allow-blob-public-access false --allow-shared-key-access false + +# Create a user-assigned managed identity +echo "Creating $userIdentity" +output=$(az identity create --name $userIdentity --resource-group $resourceGroup --location $location \ + --query "{userId:id, principalId: principalId, clientId: clientId}" -o json) + +# Use jq to parse the output and assign the properties to variables +userId=$(echo $output | jq -r '.userId') +principalId=$(echo $output | jq -r '.principalId') +clientId=$(echo $output | jq -r '.clientId') + +# Get the storage ID and create a role assignment (Storage Blob Data Owner) for the identity +storageId=$(az storage account show --resource-group $resourceGroup --name $storage --query 'id' -o tsv) +az role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal \ + --role "Storage Blob Data Owner" --scope $storageId + +# Get the function app subnet ID +functionSubnetId=$(az network vnet subnet show --name $subnetFunctions --resource-group $resourceGroup \ + --vnet-name $vnetName --query 'id' -o tsv) + +# Create the function app in a Flex Consumption plan with VNet integration. +# The --virtual-network-subnet-id parameter configures outbound VNet integration, +# which routes all outbound traffic from the function app through the VNet. +echo "Creating $functionApp with VNet integration" +az functionapp create --resource-group $resourceGroup --name $functionApp --flexconsumption-location $location \ + --runtime $languageWorker --runtime-version $languageVersion --storage-account $storage \ + --deployment-storage-auth-type UserAssignedIdentity --deployment-storage-auth-value $userIdentity \ + --virtual-network-subnet-id $functionSubnetId + +# Create a role assignment (Monitoring Metrics Publisher) in Application Insights for the user identity +appInsights=$(az monitor app-insights component show --resource-group $resourceGroup \ + --app $functionApp --query "id" --output tsv) +az role assignment create --role "Monitoring Metrics Publisher" --assignee $principalId --scope $appInsights + +# Update app settings to use managed identities for host storage connections +clientId=$(az identity show --name $userIdentity --resource-group $resourceGroup \ + --query 'clientId' -o tsv) +az functionapp config appsettings set --name $functionApp --resource-group $resourceGroup \ + --settings AzureWebJobsStorage__accountName=$storage AzureWebJobsStorage__credential=managedidentity \ + AzureWebJobsStorage__clientId=$clientId \ + APPLICATIONINSIGHTS_AUTHENTICATION_STRING="ClientId=$clientId;Authorization=AAD" +az functionapp config appsettings delete --name $functionApp \ + --resource-group $resourceGroup --setting-names AzureWebJobsStorage + +# Create private endpoints for the storage account (blob, queue, table). +# These allow the VNet-integrated function app to access the storage account +# over the private network after public access is disabled. +for subresource in blob queue table; do + echo "Creating private endpoint for storage $subresource" + az network private-endpoint create --name "pe-$storage-$subresource" \ + --resource-group $resourceGroup --vnet-name $vnetName --subnet $subnetPrivateEndpoints \ + --private-connection-resource-id $storageId \ + --group-id $subresource --connection-name "conn-$subresource" --location "$location" +done + +# Create private DNS zones and link them to the VNet so the function app +# resolves storage endpoints to their private IP addresses. +for zone in blob queue table; do + dnsZoneName="privatelink.$zone.core.windows.net" + echo "Creating private DNS zone $dnsZoneName" + az network private-dns zone create --resource-group $resourceGroup --name $dnsZoneName + az network private-dns link vnet create --resource-group $resourceGroup \ + --name "link-$zone" --zone-name $dnsZoneName --virtual-network $vnetName \ + --registration-enabled false + az network private-endpoint dns-zone-group create --resource-group $resourceGroup \ + --endpoint-name "pe-$storage-$zone" --name "default" \ + --private-dns-zone $dnsZoneName --zone-name $zone +done + +# Now that private endpoints and DNS are configured, disable public network access +# on the storage account. The function app can still reach storage through the VNet. +echo "Restricting $storage to private endpoint access only" +az storage account update --name $storage --resource-group $resourceGroup \ + --default-action Deny --public-network-access Disabled + +# echo "Deleting all resources" +# az group delete --name $resourceGroup -y