The world’s leading publication for data science, AI, and ML professionals.

How to secure Azure Functions with Azure AD, Key Vault and VNETs

Secure Azure Functions with Azure AD, Key Vault, VNETs and firewall rules. Then connect to Azure SQL with Managed Identity of Function.

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.

Secure Azure Functions - introduction
  1. Secure Azure Functions – introduction

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.

B0. Init function security
B0. Init function security

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.

B1B3. Request-response security
B1B3. Request-response security

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.

Process data in SQLDB security
  1. Process data in SQLDB security

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

    1. Init: Secure Storage account of Azure Function
  • 1/3. Request-respone: Secure access to Azure Function
    1. Process data: Secure access to SQLDB from Azure Function

Pattern with security is also depicted below.

C. Azure Function pattern with security
C. Azure Function pattern with security

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.


Related Articles