Secure your Azure Storage Accounts with restrictions based on public IP addresses

Hosting data in the cloud is easy. Click, click and done - you're ready to go and put all of your sensitive information in the cloud. But, don't forget about securing that data.

In recent years, data breaches have become something of a phrase you see in every organization's map of fears and risks. Not always was the focus on security as intense as it is now. Looking at the significant data breaches the last couple of years, it's evident that the stakes are getting higher and the malicious actors are better than ever before. Just take a look at recent announcements from Microsoft in the security space; it's incredible.

One step to help mitigate the risk of someone else grabbing the data from your storage accounts in Azure is to secure the access to them not only by roles or connection strings but also by originating IP addresses or virtual networks.

If you're heading into the cloud with your data, and that cloud is Azure - then this article is possibly a good fit for you. We'll explore how to configure an Azure Storage Account, so it's not accessible from public IP addresses (except for the ones we want to exclude from the block).

Pre-requisites

To follow along with the steps I do, please ensure the following:

  • You really should have a test- or dev-subscription in Azure to try things before you try them in your production cloud.
  • Install the Azure CLI, which is my tool of choice.

Additional notes

  • IP-based restrictions apply to public IP addresses only, not private/internal ones.
  • Switching these configuration values takes immediate effect. You will get access denied after you've applied the policies unless you've allowed your IP range to access the account.

Configure restrictions to Azure Storage Accounts based on IP addresses

First, I want to list all my storage accounts using the Azure CLI. I'm querying through the CLI to get the name, resource group name and the value of the Default Action for networks:

az storage account list --query [].{Name:name,Group:resourceGroup,RuleSet:networkRuleSet.defaultAction} -o table

I've redacted the real names in my results below, but you get the gist:

Name                      Group                           RuleSet
------------------------  ------------------------------  ---------
redactedName1             redactedGroup1                  Allow
redactedName2             redactedGroup2                  Allow
redactedName3             redactedGroup3                  Allow

In this case, we can see that the Allow flag is set for the network rule set policy.

Change the network policy default rule

By default, an Azure Storage Account has this flag set to Allow, but in our case, we want to restrict access to EVERYTHING, except the sources that we trust. To do this, we have to change this flag first to Deny, and that will yield your Azure Storage Account inaccessible until you've granted something access.

Warning: Please bear the above remarks in mind. If you change this flag, your Azure Storage Account will not be reachable from your services, code or tools until you've correctly configured it.

Steps:

  1. Figure out which storage account you want to change this setting for (see previous commands a bit further up)
  2. Change the default-action flag of your storage account:
 az storage account update -n "redactedName1" -g "redactedGroup1" --default-action Deny

3. You'll get a JSON response once the command completes, and to verify the setting, run the command to list the storage accounts again, including the value for default action:

az storage account list --query [].{Name:name,Group:resourceGroup,RuleSet:networkRuleSet.defaultAction} -o table

This should show you that the DefaultAction for this storage account has now been set to Deny as such:

Name                      Group                           RuleSet
------------------------  ------------------------------  ---------
redactedName1             redactedGroup1                  Deny 
redactedName2             redactedGroup2                  Allow
redactedName3             redactedGroup3                  Allow

Great, we've locked down the access of this storage account so nobody can access it. But what good does that do if our services or endpoints also cannot access them, or even the internal Azure services aren't allowed to access the storage account? We'll need to take care of that next!

Configure allowed IP addresses or IP-ranges

To enable specific addresses to access our Azure Storage Account and be passed through, we need to configure some IP Rules.

See any existing IP rules with this command:

az storage account network-rule list -g redactedGroup1 -n redactedName1

If there are no rules (which is likely, if you just turned the Deny mode on), then it will look something like this:

{
	"ipRules": [],
	"virtualNetworkRules": []
}

Now, to allow some IP ranges through, we'll need to enable a network rule for them, which can be done like this:

az storage account network-rule add -g redactedGroup1 -n redactedName1 --ip-address "123.2.1.15"

Once you've put either an IP range or a specific IP address on the allowed list, the results will look something like this if you run the az storage account network-rule list command again as per above:

az storage account network-rule list -g redactedGroup1 -n redactedName1
{
  "ipRules": [
    {
      "action": "Allow",
      "ipAddressOrRange": "123.2.1.15"
    }
  ],
  "virtualNetworkRules": []
}

Great, this far we have:

  • Locked down access to the Azure Storage Account, so it has Deny flag for all public traffic by default
  • Made an exception/bypass for a specific endpoint which can access the Storage Account

Next, we'll need to ensure that Azure's internal services also can access the storage account (unless there's some specific reason why we wouldn't want to do that).

Ensure Azure's internal services can access the Storage Account

You can manage a couple of exceptions to the rules we just created. These are specifically for the Azure-services to function correctly. We can configure whether they should have access or not, to our storage account.

If you just turned on the Deny mode, it's likely that there's no bypass exception in place, and it might be a good idea to consider adding that for Azure services to access the Storage Account properly. Here's how to add a few of the default services, Logging, Metrics, and AzureServices:

az storage account update -g redactedGroup1 -n redactedName1 --bypass Logging Metrics AzureServices

Now, we can validate this change with the below command:

az storage account show -g redactedGroup1 -n redactedName1 --query networkRuleSet
{
  "bypass": "Logging, Metrics, AzureServices",
  "defaultAction": "Deny",
  "ipRules": [
    {
      "action": "Allow",
      "ipAddressOrRange": "123.2.1.15"
    }
  ],
  "virtualNetworkRules": []
}

Great, now we have completed and verified the following configuration:

  • Locked down access to the Azure Storage Account, so it has Deny flag for all public traffic by default
  • Made an exception/bypass for a specific endpoint which can access the Storage Account
  • Enabled internal Azure services to access the storage account with the help of the above bypass we just did

Let's find out for sure if our configuration is holding up.

Validate and test connections to the Azure Storage Account

Proving that access to the storage account is blocked is a vital part of the process. If we've managed to configure our storage accounts correctly with the restrictions we desire, we shouldn't be able to access them regardless of whether we have correct authentication keys and credentials or not.

In the case of my configurations, I have not allowed my IP address access, nor any other IP address other than the services which immediately requires access. Hence, I should get error messages when I'm trying to connect from my device or any other device or mechanism which isn't listed in the trusted IP ranges.

Verifying restricted access with Azure Storage Explorer

Opening the Azure Storage Explorer and listing the storage accounts, and then trying to access the one I just locked down will show this error message:

This request is not authorized to perform this operation. Azure Table Storage.

The error message returned is:

Unable to retrieve child resources.

Details: This request is not authorized to perform this operation.

Great! Mission accomplished. I cannot access the table storage account at all right now, even though I'm using Admin credentials (which, by the way, you should never do).

To verify that again, I added my IP address as an allowed address, and voila I can work with my data again.

Edit 2018-10-08: Example of configuring security during creation

After publishing this post, I got a comment about whether this was possible to do while creating the storage account instead of doing it post-creation. This is also an easy task with the Azure CLI:

az storage account create -n tobistoragedemo123 -g mystoragedemo --bypass AzureServices Logging Metrics --default-action Deny --https-only true

This command will create a storage account in my mystoragedemo resource group, add a bypass rule for AzureServices, Logging, and Metrics, then set the Default-Action to Deny and finally ensure that this storage account always enforces https.

Summary

Security in the cloud is essential. Just as important as it is when you do things on-premises. By default, Azure Storage Accounts doesn't come with any restrictions on the accessibility from networks or public IP addresses, which means that if someone gets hold of a connection string, SAS token or access key to the storage account, they could quite easily extract, modify or delete all of your data.

Putting in place a plan for security, including restrictions on virtual networks or directly on IP ranges as I mentioned in this article, we can strengthen our security and hopefully sleep better at night.

There's a lot more to a genuine and thorough security hardening for any service in Azure, including Storage Accounts, but this is a no-brainer and something which should be in place if there's no real need to allow the entire world to send requests to your storage accounts.

I'm looking forward to hearing your thought on this topic. Drop a comment or an e-mail if you want to reach out. Happy clouding.