infrasdoc
Azure · Storage · Runbook

Storage account network lockdown & re-allow pipeline

Three commands, run in order: find storage accounts with public network access enabled but a Deny default rule, export the result to a CSV on your machine, then add two new public IP addresses to the allow list on every account in that file.

Tool Azure CLI Requires jq Steps 4 Output selected-network-storageaccounts.csv

Before you start

  • Sign in and select the right subscription: az login and az account set --subscription "SUBSCRIPTION_NAME"
  • jq must be installed locally — the CSV export in Step 2 pipes JSON through it.
  • Commands are written for a POSIX shell (bash/zsh). On Windows, run them in WSL or Git Bash, or adapt the line continuations for PowerShell.
  • Run each step from the same working directory so Step 3 can find the CSV that Step 2 writes.
01

Find publicly reachable accounts that are locked down by default

Lists every storage account whose public network access is Enabled while its network rule set's default action is Deny — i.e. reachable in principle, but only via explicit allow rules. Printed as a table for a quick visual check.

bash
az storage account list \
  --query "[?publicNetworkAccess=='Enabled' && networkRuleSet.defaultAction=='Deny'].{StorageAccountName:name, ResourceGroup:resourceGroup, Location:location, PublicNetworkAccess:publicNetworkAccess, DefaultAction:networkRuleSet.defaultAction}" \
  --output table

Expect: a table with columns StorageAccountName, ResourceGroup, Location, PublicNetworkAccess, DefaultAction. An empty table means no accounts currently match the filter.

02

Export the matching accounts to a local CSV

Re-runs the same filter, keeps just the two fields you need for the next step, and pipes the JSON through jq to write a CSV to your current directory.

→Saves to selected-network-storageaccounts.csv in the directory you run this from. Move or reference it accordingly before Step 3.
bash
az storage account list \
  --query "[?publicNetworkAccess=='Enabled' && networkRuleSet.defaultAction=='Deny'].{ResourceGroup:resourceGroup,StorageAccountName:name}" \
  --output json | jq -r '["ResourceGroupName","StorageAccountName"], (.[] | [.ResourceGroup,.StorageAccountName]) | @csv' > selected-network-storageaccounts.csv

Expect: a two-column CSV — ResourceGroupName,StorageAccountName — with one header row plus one row per matching account.

03

Add the two new public IPs to each account's allow list

Reads the CSV from Step 2 and, for every listed account, adds each of the two new public IP addresses as an allowed network rule — no need to touch accounts by hand.

⚠Replace 203.0.113.10 and 203.0.113.20 with your actual public IP addresses before running. Adding a network rule doesn't change defaultAction — traffic from anywhere else is still denied.
bash
# The two new public IPs to allow through the storage firewall
NEW_IPS=("203.0.113.10" "203.0.113.20")

# Skip the header row, strip quotes, loop over ResourceGroup,StorageAccountName
while IFS=, read -r ResourceGroup StorageAccountName; do
  for ip in "${NEW_IPS[@]}"; do
    az storage account network-rule add \
      --resource-group "$ResourceGroup" \
      --account-name "$StorageAccountName" \
      --ip-address "$ip"
  done
done < <(tail -n +2 selected-network-storageaccounts.csv | tr -d '"')

Expect: one JSON confirmation per network-rule add call — two per storage account. Verify with az storage account network-rule list --account-name <name> --resource-group <rg>.

04

Full script: allow the IPs and lock to selected networks

A standalone .sh version of Step 3 with progress logging built in. It also explicitly sets --default-action Deny with --bypass AzureServices on each account, so the firewall stays closed to everything except the IPs you just added and Azure's own services.

⚠Update the CSV_FILE path and the ALLOWED_IPS array for your environment before running. This script re-applies --default-action Deny on every account it touches — confirm that's the desired end state, not just an allow-list addition.
update-storage-network-rules.sh
#!/bin/bash

CSV_FILE="$HOME/storageaccountscopy.csv"   # set to your CSV path

ALLOWED_IPS=(
  "203.0.113.10"
  "203.0.113.20"
)

tail -n +2 "$CSV_FILE" | while IFS=, read -r ResourceGroupName StorageAccountName
do
  ResourceGroupName=$(echo "$ResourceGroupName" | xargs)
  StorageAccountName=$(echo "$StorageAccountName" | xargs)

  echo "Updating storage account: $StorageAccountName in resource group: $ResourceGroupName"

  for IP in "${ALLOWED_IPS[@]}"
  do
    echo "  Adding IP rule: $IP"

    az storage account network-rule add \
      --resource-group "$ResourceGroupName" \
      --account-name "$StorageAccountName" \
      --ip-address "$IP"
  done

  echo "  Setting default action to Deny / selected networks"
  az storage account update \
    --resource-group "$ResourceGroupName" \
    --name "$StorageAccountName" \
    --default-action Deny \
    --bypass AzureServices

  echo "Done with $StorageAccountName"
  echo "--------------------------------------"
done

Expect: console output logging each account as it's processed, two IP-rule confirmations and one az storage account update confirmation per account, ending in a divider line.