Secure Azure Functions with Azure AD, Key Vault and VNETs. Then connect to Azure SQL using firewall rules and Managed Identity of Function.
A. Azure Functions Security – Introduction
Azure Functions is a popular tool to create small snippets of code that can execute simple tasks. Azure Functions can be triggered using queue triggers, HTTP triggers or time triggers. A typical pattern of an Azure Function is as follows:
- Init: Retrieve state from Storage Account
- Request: Endpoint is called by another application/user
- Processing: Data is processed using other Azure resources
- Response: Result is replied to caller
Pattern is depicted below, in which data is retrieved from Azure SQL and returned to application/user.
In this blog, it is discussed how to secure Azure Functions. In this, Azure AD, Managed Identities, Key Vault, VNET and firewall rules are used. To create dedicated and isolated Azure Functions, it can also be decided to create a separate App Service Environment (ASE). However, an ASE can be difficult to manage and more costs are involved. Therefore, an ASE is not used in this blog and a regular App Service Plan is taken. Structure is as follows:
- B0. Init: Secure Storage account of Azure Function
- B1/B3. Request-response: Secure access to Azure Function
- B2. Process data: Secure access to Azure SQL from Azure Functions
- C. Conclusion
For details how to secure your Azure Data Factory pipeline, see this blog. Deploying a custom docker image as Azure Functions to run a Selenium web scraper is discussed in this blog.
B0. Init: Secure Storage Account of Azure Function
An Azure Function always has a supporting storage account attached. On this storage account, the code can be found on the file share and logging is written to the blob storage. In this blog, the Security account is secured as follows:
- Managed Identity (MI) of Azure Function is enabled and this MI is used to authenticate to an Azure Key Vault to get/set secrets
- Storage keys are stored in a key vault rather than app settings which is the default. Also, the Azure Function keys are stored in this key vault
- VNET integraton is enabled to Azure Function such that all outbound traffic flows through this VNET
- NSG is added that only allows outbound traffic to ports 443, 1433 and destination Azure WestEurope
- Only VNET of Azure Function can access the Key vault using firewall rules.
- (Optional) Only VNET of Azure Function can access the supporting storage account using firewall rules. Notice this scenario is offically not supported, see here and can break your app. When using this feature, it shall be explicitly tested after deployment in production if the app is still working, and if not, the app shall be redeployed.
Securing the storage account in the Azure Function pattern is depicted below.

The code of this step by found using the following below, script can also be found on my github.
# B0_1. Login
Clear-AzContext -Scope CurrentUser -Force Connect-AzAccount
# B0_2. Variables, take same id in all B0/B1B3/B2 scripts
$id = "3405"
$rg = "test-funappsec" + $id + "-rg"
$loc = "westeurope"
$funname = "test-funappsec" + $id + "-func"
$funstor = "testfunappsec" + $id + "stor"
$akv = "test-funappsec" + $id + "-akv"
$funplan = "test-funappsec" + $id + "-plan"
$vnet = "test-funappsec" + $id + "-vnet"
$nsg = "test-funappsec" + $id + "-nsg"
$subnet = "azurefunction"
$addressrange = "10.200.0.0"
# B0_3. Create resource group
az group create -n $rg -l $loc # B0_4. Create Storage account
az storage account create -n $funstor -g $rg --sku Standard_LRS # B0_5. Create VNET
az network vnet create -g $rg -n $vnet --address-prefix $addressrange/16 -l $loc # B0_6. create NSG
az network nsg create -g $rg -n $nsg # B0_7. Create firewall rules
# Only allow outbound to WE storage, port 443 and Azure WE
az network nsg rule create -g $rg --nsg-name $nsg -n allow_we_stor_sql --priority 100 --source-address-prefixes VirtualNetwork --source-port-ranges '*' --destination-address-prefixes Storage.WestEurope --destination-port-ranges '443' '1433' --access Allow --protocol '*' --description "Allow storage West Europe 443" --direction Outbound
az network nsg rule create -g $rg --nsg-name $nsg -n allow_azure_internal --priority 110 --source-address-prefixes VirtualNetwork --source-port-ranges '*' --destination-address-prefixes AzureCloud.WestEurope --destination-port-ranges '*' --access Allow --protocol '*' --description "Azure we" --direction Outbound
az network nsg rule create -g $rg --nsg-name $nsg -n deny_all_outbound --priority 130 --source-address-prefixes '*' --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges '*' --access Deny --protocol '*' --description "Deny all outbound" --direction Outbound # B0_8. Create subnet with NSG to VNET
az network vnet subnet create -g $rg --vnet-name $vnet -n $subnet --address-prefixes $addressrange/24 --network-security-group $nsg # B0_9. Turn on firewall
Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $rg -Name $funstor -DefaultAction Deny # B0_10. Set service endpoints for storage and web app to subnet Get-AzVirtualNetwork -ResourceGroupName $rg -Name $vnet | Set-AzVirtualNetworkSubnetConfig -Name $subnet -AddressPrefix $addressrange/24 -ServiceEndpoint "Microsoft.Storage", "Microsoft.Web", "Microsoft.Sql", "Microsoft.KeyVault" | Set-AzVirtualNetwork
# B0_11. Add firewall rules to Storage Account $subnetobject = Get-AzVirtualNetwork -ResourceGroupName $rg -Name $vnet | Get-AzVirtualNetworkSubnetConfig -Name $subnet Add-AzStorageAccountNetworkRule -ResourceGroupName $rg -Name $funstor -VirtualNetworkResourceId $subnetobject.Id # B0_12. Create Azure Function
az appservice plan create -n $funplan -g $rg --sku P1v2 --is-linux az functionapp create -g $rg --os-type Linux --plan $funplan --runtime python --name $funname --storage-account $funstor # B0_13. Turn on managed identity of Azure Function az webapp identity assign --name $funname --resource-group $rg # B0_14. Add VNET integration
az webapp vnet-integration add -g $rg -n $funname --vnet $vnet --subnet $subnet
# B0_15. Create key vault
az keyvault create --name $akv --resource-group $rg --location $loc # B0_16. Set policy such that Azure Function can read from AKV $objectid_funname = az functionapp identity show -n $funname -g $rg --query "principalId"
az keyvault set-policy -n $akv --secret-permissions set get list --object-id $objectid_funname
# B0_17. Set acl on key vault
az keyvault network-rule add -n $akv -g $rg --subnet $subnet --vnet-name $vnet
# B0_18. Get storage connection string and add to key vault $storageconnectionstring = az storage account show-connection-string -n $funstor --query "connectionString"
$keyref = az keyvault secret set -n storageconnectionstring --vault-name $akv --value $storageconnectionstring --query "id" $appkeyref = "@Microsoft.KeyVault(SecretUri=" + $keyref + ") " -replace '"',''
# B0_19. Set app settings of function
# Function gets function keys from AKV instead of storage account az functionapp config appsettings set --name $funname --resource-group $rg --settings AzureWebJobsSecretStorageKeyVaultConnectionString="" AzureWebJobsSecretStorageKeyVaultName=$akv AzureWebJobsSecretStorageType="keyvault" az functionapp config appsettings set --name $funname --resource-group $rg --settings AzureWebJobsStorage=$appkeyref az functionapp config appsettings set --name $funname --resource-group $rg --settings AzureWebJobsDashboard=$appkeyref # B0_20. Done
B1B3. Request-response: Secure access to Function
By default, the Azure Function key is used to authenticate requests to the Azure Function. Since an Azure Function runs in a web app, it is possible to enable Azure AD authentication for an Azure Function. In this blog, access to the Azure Function is secured as follows:
- Azure AD is enabled for Azure Function such that only objects in this Azure AD tenant can call the function. Object in Azure AD can be a user, Service Principal or Managed Identity
- (optional) Only whitelist particular objects in the Azure AD tenant that can acces the function. For instance, only the Managed Identity of an Azure Data Factory Instance can execute a Function (see also this [blog](http://(e.g. an ADFv2 managed identity))
- (optional) Add access restrictions in firewall rules of the Azure Function
Restricting access to the Azure Function is depicted below.

The code of this step by found using the following below, script can also be found on my github.
# B1B3_1. Login
Clear-AzContext -Scope CurrentUser -Force Connect-AzAccount
# B1B3_2. Variables, take same id in all B0/B1B3/B2 scripts
$id = "3405" # take same id in all B0/B1B3/B2 scripts $rg = "test-funappsec" + $id + "-rg"
$funname = "test-funappsec" + $id + "-func" # 0.2 connect to AAD
$Environment = "AzureCloud"
$aadConnection = Connect-AzureAD -AzureEnvironmentName $Environment # B1B3_3. Creat App registration
# step 2 is derived from https://devblogs.microsoft.com/azuregov/web-app-easy-auth-configuration-using-powershell/ $Password = [System.Convert]::ToBase64String($([guid]::NewGuid()).ToByteArray()) $startDate = Get-Date
$PasswordCredential = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordCredential $PasswordCredential.StartDate = $startDate $PasswordCredential.EndDate = $startDate.AddYears(10) $PasswordCredential.Value = $Password $identifier_url = "https://" + $funname + ".azurewebsites.net" [string[]]$reply_url = $identifier_url + "/.auth/login/aad/callback" $reqAAD = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess" $reqAAD.ResourceAppId = "00000002-0000-0000-c000-000000000000" $delPermission1 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList "311a71cc-e848-46a1-bdf8-97ff7156d8e6","Scope"
$reqAAD.ResourceAccess = $delPermission1 $appRegName = $funname + "_easyauth"
$appReg = New-AzureADApplication -DisplayName $appRegName -IdentifierUris $identifier_url -Homepage $identifier_url -ReplyUrls $reply_url -PasswordCredential $PasswordCredential -RequiredResourceAccess $reqAAD # B1B3_4. Add new AppRole object to app registration # step 3 is derived from https://gist.github.com/psignoret/45e2a5769ea78ae9991d1adef88f6637
$newAppRole = [Microsoft.Open.AzureAD.Model.AppRole]::new() $newAppRole.DisplayName = "Allow MSI SPN of ADFv2 to authenticate to Azure Function using its MSI" $newAppRole.Description = "Allow MSI SPN of ADFv2 to authenticate to Azure Function using its MSI"
$newAppRole.Value = "Things.Read.All"
$Id = [Guid]::NewGuid().ToString() $newAppRole.Id = $Id
$newAppRole.IsEnabled = $true $newAppRole.AllowedMemberTypes = "Application" $appRoles = $appReg.AppRoles
$appRoles += $newAppRole
$appReg | Set-AzureADApplication -AppRoles $appRoles # B1B3_5. Add app registration to web app $authResourceName = $funname + "/authsettings" $auth = Invoke-AzResourceAction -ResourceGroupName $rg -ResourceType Microsoft.Web/sites/config -ResourceName $authResourceName -Action list -ApiVersion 2016-08-01 -Force $auth.properties.enabled = "True" $auth.properties.unauthenticatedClientAction = "RedirectToLoginPage" $auth.properties.tokenStoreEnabled = "True" $auth.properties.defaultProvider = "AzureActiveDirectory" $auth.properties.isAadAutoProvisioned = "False" $auth.properties.clientId = $appReg.AppId $auth.properties.clientSecret = $Password $loginBaseUrl = $(Get-AzEnvironment -Name $environment).ActiveDirectoryAuthority $auth.properties.issuer = $loginBaseUrl + $aadConnection.Tenant.Id.Guid + "/" $auth.properties.allowedAudiences = @($identifier_url) New-AzResource -PropertyObject $auth.properties -ResourceGroupName $rg -ResourceType Microsoft.Web/sites/config -ResourceName $authResourceName -ApiVersion 2016-08-01 -Force # B1B3_6. Create SPN connected to app registration $SPN = New-AzADServicePrincipal -ApplicationId $appReg.AppId -DisplayName $appRegName
# B1B3_7. Set "User assignment required?" to true in SPN
# (optional, in case you want to whitelist AAD users) #Set-AzureADServicePrincipal -ObjectId $SPN.Id -AppRoleAssignmentRequired $true # B1B3_8. Set obj of ADFv2 as only authorized user to log in web app
# (optional, in case you want to whitelist AAD users) #$adfv2_resource = Get-AzDataFactoryV2 -ResourceGroupName $rg -Name $adfv2_name
#New-AzureADServiceAppRoleAssignment -ObjectId $adfv2_resource.Identity.PrincipalId -Id $newAppRole.Id -PrincipalId $adfv2_resource.Identity.PrincipalId -ResourceId $SPN.Id
# B1B3_9. Done
B2. Process: Secure access to SQLDB from Function
Azure SQL (aka SQLDB) is used to retrieve data from. In this blog, the data processing to Azure SQL is secured as follows:
- Managed Identity (MI) of Azure Function is enabled and this MI is used to authenticate to SQLDB
- MI of Azure Function is configured as external Azure AD object in SQLDB and is granted a reader role to retrieve data
- Only VNET of Azure Function can access SQLDB using firewall rules
Restricting access to SQLD from the Azure Function is depicted below.
The code of this step by found using the following below, script can also be found on my github.
# B2_1. Login
Clear-AzContext -Scope CurrentUser -Force Connect-AzAccount
# B2_2. Variables, take same id in all B0/B1B3/B2 scripts
$id = "3405" # take same id in all B0/B1B3/B2 scripts $rg = "test-funappsec" + $id + "-rg"
$rg_sql = "test-funappsec" + $id + "-rg"
$loc = "westeurope"
$funname = "test-funappsec" + $id + "-func" $vnet = "test-funappsec" + $id + "-vnet" $subnet = "azurefunction" $sqlserver = "test-funappsec" + $id + "-dbs" $sqldb = "test-funappsec" + $id + "-sqldb" $sqluser = "testfunappsec" + $id + "sqluser" $pass = "<<SQLDB password, use https://passwordsgenerator.net/>>" $aaduser = "<<your AAD email account>>" # B2_3. Create logical SQL server and SQLDB
az sql server create -l $loc -g $rg_sql -n $sqlserv -u sqluser -p $pass
az sql db create -g $rg_sql -s $sqlserver -n $sqldb --service-objective Basic --sample-name AdventureWorksLT # B2_4. Configure AAD access to logical SQL server
# Connect-AzureAD
Set-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName $rg_sql -ServerName $sqlserver -DisplayName $aaduser # B2_5. log in SQL with AAD (e.g. via portal query editor/SSMS/VSC) # Execute following SQL statement
#CREATE USER [<<your azure function name, equal to $funname>>] FROM EXTERNAL PROVIDER;
#EXEC sp_addrolemember [db_datareader], [<<your azure function name, equal to $funname>>];
# B2_6. point app settings to database
az functionapp config appsettings set --name $funname --resource-group $rg --settings sqlserver=$sqlserver sqldb=$sqldb # B2_7. Add firewall rules
$subnetobject = Get-AzVirtualNetwork -ResourceGroupName $rg -Name $vnet | Get-AzVirtualNetworkSubnetConfig -Name $subnet New-AzSqlServerVirtualNetworkRule -ResourceGroupName $rg_sql -ServerName $sqlserver -VirtualNetworkRuleName $subnet -VirtualNetworkSubnetId $subnetobject.Id # B2_8. Upload code Azure Function
# To create Azure Function in Python, see https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/azure-functions/functions-create-first-function-python.md
# Get code from __init__.py and requirements.txt from this git repo, then run command below func azure functionapp publish $funname
# B2_9. Done
C. Conclusion
An Azure Function is a popular tool to create small snippets of code that can execute simple tasks. In this blog, it is discussed how to secure a typical Azure Function as follows
-
- Init: Secure Storage account of Azure Function
- 1/3. Request-respone: Secure access to Azure Function
-
- Process data: Secure access to SQLDB from Azure Function
Pattern with security is also depicted below.

Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seeking professional advice. See our Reader Terms for details.