Azure Functions can be of great benefit to your automation flows! They can get data and perform business logic on request. They can even connect to your on-premises environment on demand.
And the best thing? Azure Functions can be reached over the Internet! š§ Or is that not always a ābest thingā?
In my previous blog post(s), I demonstrated how I use Azure Functions for automation tasks. Now, itās time to investigate how to properly secure them. Neglecting the security of your Azure Functions can be very risky!
That immediately brings me to the content of this blog post (which you probably can guess already). Iām going to talk about how to secure your Azure Functions so that they only benefit you or the users you want to make it available for! And not that bully on the other side of the world who wants to mess up your infrastructure.
Also, on this blog, youāll find the āDeploy to Azureā button deploying a basic Azure Function for you, which will be used as an example of the hardening process.
During this post, Ā youāll see this icon š¬ which indicates that action is required so you get the full benefit out of this post.
And Iāve introduced the š which indicates that the subsequent part explains the technical details in the context of this post.
Note: The screenshots in this post are taken from the moment the configurational settings where actual. Changes from Azure which might occur in the future are not taken into account.
Deploying the basics
You can deploy an Azure function with its default settings or use the ādeploy to Azureā button below:
š¬ Deploy an Azure function with default settings to Azure or use the button below and let me do it for you š
When youāve deployed everything you should end up with the following resources:

This deployment file deploys a basic Azure function with the required resources from Azure which are needed for this blogpost. Please note the following settings as mentioned below:
- Service plan Premium V2 (Because we want to implement private networking to enhance security)
- App insights for storing telemetry, logging, and diagnostics from the function
- Storage account which runs the Azure function
- Function app which is the actual function app where the code is executed
- HTTP Trigger which is called to trigger the actual function, gather a response, and return the response to the requester
Dealing with all security considerations
Now that we have the Azure Function app with the default settings, itās time to review all settings and implement some best practices to ensure a proper level of security!
Some of the security controls we need to check, and should have our attention, are:
- Public Runtime keys from Storage account; by default, Azure Functions are deployed with security keys (encryption and decryption keys) which are stored on a storage account. If not explicitly configured this might expose the Azure Function keys to the public internet.
- Configuration; by default Azure Functions get created with default configuration settings. You may want to change some settings to harden your Azure Functions resources for a higher level of security.
- Public network exposure; by default Azure Functions are deployed with public network exposure (although Functions are protected with a security key, there are some additional things we can do to improve security on this level)
Now, letās examine above settings, and let me explain the potential risks for each configurable parameter.
Public Runtime Keys
As youāve probably already seen, the Azure Function ā¦.gets deployed with a Storage account. Before I show you all the details, let’s start with the deployed infrastructure. In my example I have the infrastructure deployed as shown below;

Storage account
The storage account contains critical data for the storage account to operate such as the āsecretsā and details about the last run of the Azure Functions.
On the storage account we can find the containers and items as shown below;

If you check these files you would find content as described below (Iām not blurring or hashing it to show you proper examples)
{
"masterKey": {
"name": "master",
"value": "CfDJ8AAAAAAAAAAAAAAAAAAAAABHABA6RyX9cqECspxJDnPnIe1GOezhjcPCFchVHUZ_rxgzH0bKSlTE3vYNSVAkSY-_EFtI4MYcrIXBv8zOOQ5F1sLNhRPJq4TjicXJXLFsAJ6NKxByAtpMu6ZvCm96AOnoR1lN62TsFp9-s2SZB4dX7UvUVdjhEDLs67b0UiOB9A",
"encrypted": true
},
"functionKeys": [
{
"name": "default",
"value": "CfDJ8AAAAAAAAAAAAAAAAAAAAAAo3gqzjH8da27Sx5_J451R867nNxqCgP7TWSm7ATIUk8_2dGPi8ufeONOd0N2-pIYzH4Y__gaM1DbbeHOIwB_ZidZKjGVNppbNUI6WonUomUYVy2g63BU3wCcn0b-RrVgHeOiB4-KdjQovI-xz8bUytkHENAgo7cHKR9WogrXKnQ",
"encrypted": true
}
],
"systemKeys": [],
"hostName": "bpastest.azurewebsites.net",
"instanceId": "98a93a96b65fbe45d39b5d2f02b44b0a",
"source": "runtime",
"decryptionKeyId": "MACHINEKEY_DecryptionKey=oRRCtfIgJcz9m7j5Oj8Cnlj415XkleXleSCl5X/XO+I=;"
}
You donāt want to expose this information to the outside world especially if you are running the storage account with public anonymous access enabled.
Remediation
The best solution in this case (to secure your Azure Function keys) would be to deploy an Azure Key Vault and store the keys therein.
š¬ Follow the steps below to remediate
- Deploy an Azure Key Vault (for learning purposes default settings are fine. If you are dealing with this in production environments, use private endpoints!)
- Create a secret alternative for the master key as for the default key
- If not enabled yet, enable the managed identity on the Azure Function
- This identity now needs to have the right permissions on the Key vault to get the newly created secret values. Copy the principal ID and assign the permissions on the Key Vault (Secret: GET, LIST, SET, DELETE)
- Now we need to update the Azure Function so it no longer fetches the keys from the Storage Account but fetches them from the Azure Key vault. Add the environment variables defined below to the Azure Function
Key | Value |
AzureWebJobsSecretStorageKeyVaultUri | <FQDN of your keyvault> in my example: https://bpas.vault.azure.net/ |
AzureWebJobsSecretStorageType | keyvault |
In the end you will end up with:

š¬ Now restart your function and check the KeyVault. There should be new secrets in there:

Well done! š the first issue is mitigated and keys for the function runtime are securely stored in the Azure KeyVault. Also, consider how you want your keys to be linked to your functions. Do you want to use one key to access all functions? Or do you delegate some of your functions to other business units and create a key per function for this?
It’s up to you to decide! As long as you take this topic serious.

Configuration
By default, some configuration settings might expose your Azure Function to the internet with suboptimal security settings . Let’s walk through those settings, and let me explain what they do and which option you should choose to harden your Azure Function(s).
These settings can be found underneath

š See the summary below for this deployment type. Please note; that some settings differentiate between runtime, stack, or platform. Follow the pattern, not the actual values if you are considering these settings.
Key | Expected value | Rationale |
Stack | PowerShell Core | Define the stack of your runtime code (whether itās Python, PowerShell or C#) |
Version | PowerShell 7.4 | Stick with the version of your stack which supports your development. But, stay aware of potential security risks. Define a standard like ālatest minus oneā |
Platform | 64 Bit | For the best memory utilization and support stay with the latest development platform and stick with 64-bit. Only fall back to 32 if you need to support legacy libraries/code. |
Managed Pipeline | Integrated | For modern apps always choose integrated. Classic is mostly used for legacy and older apps. |
SCM Basic Auth | On | You can leave this on if you either push directly to your Azure Functions by publishing profiles. Otherwise, configure it to be āoffā |
FTP Basic Auth | Off | No question about this, disable this if you donāt utilize FTP deployments. Rather choose between a pipeline deployment or SCM. |
HTTP Version | 2.0 | Choose the latest supported HTTP version if your app supports it. Way better support for modern workloads and security controls. |
Web Sockets | Off | If you donāt need to implement things like SignalR or any other client-to-server two-way communication protocol. If itās not needed, disable it! |
Always On | On | If you ask me, the warming up can sometimes conflict with whatever or whoever is calling your function. |
TLS Inbound Version | 1.3 | Take 1.3 if your app/workload can support it. For me, this is an āalways take the latestā approach. |
Minimum TLS Cipher suite | TLS_RSA_WITH_AES_256_CBC_SHA | TLS_RSA_WITH_AES_128_CBC_SHA is excluded in this one. 256 should be broadly supported and enhance your security. If you need the AES_128 because of legacy controls utilize the AES_128. Otherwise, stick with AES_256 |
Functions Admin Isolation | Depends per environment | Test/Dev this is acceptable I would say to have it āOffā For production environments I would configure it to be āOnā Requests to your function should be done outside of the admin panel through validated requests. |
Remote debugging | Off | Off for production environments! Dev itās fine to have it open to simulate actual behavior. But donāt put this on for production workloads. Attaching a remote debugger to a production environment makes the entire environment stale when a debug breakpoint is hit. |
Client Certificate Mode | Ignore | HTTP 2.0 is enabled, so Client Certificate Mode should be ignored as there is a correlation between these two settings. |
If you follow the settings above, your function is already way more secure than without them. Although some settings may differentiate per workload it hopefully provides you with some valuable insight on when to make certain configuration changes for the purpose of better security.
Networking
Before we can discuss the networking configuration of an Azure Function, we need to have a virtual network in place.
š¬ Deploy a virtual network with default settings
- Address range can be for instance 10.0.0.0/8
- No subnet delegation(s) should be in place for the first subnet
- Deploy a second subnet with serverfarms delegation
Now letās configure private networking for Azure Functions!
- Go to the Azure Functions āPrivate endpoint connectionsā
- Enter the networking details to your own standard like ānameā and ānetwork interface nameā
- Ā Connect the private endpoint to your Function App (which is found underneath āsitesā)
- Select your newly created virtual network as the source for the private endpoint
- The private DNS configuration can be left as is for now (we wonāt be focusing too much on private DNS in this post)
š¬ Now configure the Function outbound network to the delegated subnet

ā ļø This helps for the function to utilize this VNET to request the content it needs to operate! This VNET now supports the fetching of the content securely from the Azure Function to the Storage Account!
Now the function has a private endpoint and can communicate over it to connect securely to other resources in the same VNET.
š¬ Next is the storage account which hosts parts of our function. Let’s not leave that one untouched! š
- Open the storage account networking and configure the same settings for networking as you did for the Azure Function
- Configure the sub-resource as shown below
- And configure the Virtual Network
- DNS can be default (same as we did for the Azure Function)
If everything is deployed the Storage account will now be available over the private endpoint! But letās review some additional things here, as thereās still an option to connect to the storage account over public endpoints!
š¬ Disable the public endpoint on the Storage Account

But⦠what about the KeyVault? Correct! That one also needs to have a private endpoint to complete the chain of services we are using to run the Function!
š¬ Configure the KeyVault to support private endpoints
- Same as before: configure the network settings
- Configure the sub-resource as shown below
- And configure the Virtual Network
- DNS can be default (same as we did for the Azure Function)
Now let’s check the Function again! As we are now secured and configured š

Oops! It seems like something is going wrong!
So whatās happening?
Weāve removed the access for ourselves as well, the Function can no longer reach the resources as itās not routing over the VNET to find its required resources. To get this to work we need to tell the Function to query its resources over the VNET and use the private endpoints instead!
Add the following environment variables to the Azure Function to get it operational again;
Key | Expected value | Rationale |
WEBSITE_CONTENTOVERVNET | 1 | Tells the Azure function to fetch the content that is located on the Storage account over the VNET |
WEBSITE_DNS_SERVER | 168.63.129.16 | Tells the Azure function to query the DNS server in the VNET to find all the private endpoint registrations |
There we have it!
A working Azure Function, hidden from the internet (going to the function now shows the image below)

This means that testing the Function from the Azure portal itself will also fail as these calls are client-side oriented. If you want to test this ,run the function from the portal (you will see something like this:)

š” Now ad your client IP to the allowed IP list on the Functions networking tab to allow communication to the Azure Function

Try the call again from the portal or your browser.
Your client will get a 200OK with the response

As will the portal

Summary
Well, well, well! Look what we’ve accomplished together! š
We started with an Azure Function that was basically screaming “HACK ME!” to every script kiddie on the internet, and we’ve transformed it into a security fortress that would make even the most paranoid security architect shed a tear of joy.
Let’s recap our journey from “security nightmare” to “security dream”:
š Keys Locked Away Like Crown Jewels
Remember those runtime keys just sitting there in a storage account, accessible to anyone with a web browser? Yeah, those are now safely tucked away in Azure Key Vault, encrypted and protected by managed identities. Your Function Keys are now more secure than your grandmother’s secret cookie recipe!
āļø Configuration Hardened Like a Tank
We went through every single configuration setting with a fine-tooth comb. From TLS 1.3 to disabling unnecessary features like FTP (seriously, who still uses FTP?), your Function is now configured like it’s protecting state secrets. Because let’s be honest, your business logic probably IS a state secret! š
š Network Security That Would Make a Firewall Jealous
The piĆØce de rĆ©sistance – private endpoints everywhere! Your function, storage account, and Key Vault are now having their conversations in a private VNET, away from the prying eyes of the internet. It’s like they’re whispering secrets in a soundproof room while the rest of the world is locked outside.
šÆ The Reality Check
Sure, we hit some bumps along the way (looking at you, subnet delegation requirements, which sometimes can be confusingš), but that’s the reality of proper security – it’s not always straightforward, but it’s always worth it. The fact that your Function now returns a 403 when accessed from the internet isn’t a bug, it’s a feature!
š” The Bottom Line
Your Azure Functions have gone from being the digital equivalent of leaving your front door wide open with a sign saying “Free Stuff Inside!” to being a properly secured, enterprise-grade solution that would make any security team proud.
And the best part? You can still test and develop normally by adding your IP to the allowed list when needed. Security doesn’t have to lead to productivity suicide!
So there you have it – your Azure Functions are now locked down tighter than a submarine’s hatch. Sleep well knowing that your functions are secure, your data is protected, and that bully on the other side of the world will have to find someone else’s infrastructure to mess with! š
Remember: Security isn’t a destination, it’s a journey. Keep those configurations updated, monitor those logs, and never stop being paranoid about security – because in this business, paranoia is just good sense! š”ļø