Azure IP Downloader v1 - 夜莺博客

Azure IP Downloader v1

原文:Azure IP Downloader v1 — theDXT (Daniel Keer)

If you have ever tried to build a firewall allow-list for Microsoft 365, Microsoft Teams, or the Azure platform, you already know the problem: Microsoft does not publish a stable set of IP addresses. The ranges behind Azure, Exchange Online, SharePoint Online, Teams, and the service platform itself change constantly. The published JSON file is refreshed on a weekly cadence, prefixes are added and removed, and address space is re-homed between service tags without any announcement. Hard-coding a static list into a firewall policy is a reliable way to break Microsoft Teams calling for one unlucky group of users three weeks later.

This post is a walkthrough of a small PowerShell script — Azure IP Downloader v1 — that grabs the JSON file for the Azure IP Ranges and Service Tags – Public Cloud, filters it down to the regions you care about, and writes plain text files that you can feed into a firewall, a web proxy, a load balancer, or an automation pipeline. The script was written by Daniel Keer (theDXT) and published on GitHub; below I go through what it does, what the JSON actually contains, and how to make it a little more robust for production use.

Why You Need the Download at All

Microsoft publishes the service tag data specifically so that customers can automate their own network configuration instead of copying numbers out of a web page. Inside Azure, the data is consumed natively: a Network Security Group or Azure Firewall rule can reference a tag such as AzureCloud, Storage, Sql, AzureActiveDirectory or a regional variant like AzureCloud.canadacentral, and the platform resolves the current prefixes for you.

Anything outside Azure has no idea what a service tag is. An on-premises firewall, a Palo Alto, a Sophos XG, an upstream router ACL, a Squid or Zscaler proxy, a Cloudflare WAF rule, a NetBox IPAM prefix list — all of them speak CIDR and nothing else. The published JSON is the bridge between the two worlds, and it is also the backend for the Microsoft 365 IP Address and URL Web Service that many endpoint tools already use.

The format has been stable for years, which is why a script like this one keeps working long after the blog post that introduced it. You only have to update the download URL, because Microsoft rotates the file name every week.

What's Inside the JSON File

The file is a single JSON document with three top-level keys:

  • changeNumber — a monotonically increasing integer. Store it alongside your downloaded copy so you can tell whether the file has actually changed before you redeploy a firewall rule.
  • cloud — which cloud the file describes. Public is what most people want; the US Government, China and Germany clouds each publish their own file from their own URL.
  • values[] — an array in which every element describes one service tag.

Each entry in values[] contains a name (the service tag such as AzureCloud.canadacentral or ExchangeOnline), an id that is simply a hash-style combination of name, region and platform, and a properties object with the following fields:

  • changeNumber — when this particular service tag last changed.
  • region — the Azure region, for example canadacentral, eastus2 or westeurope. Global services leave this empty.
  • regionId — the numeric region identifier.
  • platform — Azure, Windows or Azure, Windows depending on which platform the tag applies to.
  • systemService — the parent service, such as Azure, Exchange or Storage.
  • addressPrefixes — the actual array of CIDR blocks, mixing IPv4 and IPv6.
  • networkFeatures — extra feature-related identifiers that usually do not matter for firewall work.

That addressPrefixes array is the only thing the script actually needs. Everything else is metadata you may want for logging or change detection.

Where to Get the Current JSON URL

The download URL is not a stable API endpoint. Microsoft hosts the file on download.microsoft.com behind a per-release GUID path, and the file name ends with the publication date — for example ServiceTags_Public_20201228.json. To find the current link, open the Microsoft Download Center page for Azure IP Ranges and Service Tags – Public Cloud and copy the JSON link from the download list:

https://www.microsoft.com/en-us/download/details.aspx?id=56519

Because the URL changes, treat it as a parameter rather than a constant. If you want fully unattended runs, reverse-engineer the URL from that page on each execution, or store the GUID in a configuration file that you update after each release. The script below takes the simple route and keeps it as a variable at the top, which is exactly the right trade-off for a script that runs on demand.

The Script

Here is the script as originally published, with the variables you will want to change at the top:

# Azure IP Downloader v1 - Daniel Keer (theDXT)
# grabs the JSON file for the Azure IP Ranges and Service Tags - Public Cloud
# allows for filtering and downloads the IPs into one big file
# also makes a file just for IPv4 and IPv6
# the URI from MS will need to be replaced as that may change
# change the variables as needed

# save location
$exportlocation = "C:\temp\"

# region filter
$regionFilter = "canada"

# download the JSON file from MS
$MSjsonDL = Invoke-WebRequest -Uri "https://download.microsoft.com/download/7/1/D/71D86715-5596-4529-9B13-DA13A5DE5B63/ServiceTags_Public_20201228.json"

# getting date
$time = get-date -f yyyy_MMM_dd_hhmm_tt

# convert to PS object
$MSjsonOBJ = ConvertFrom-Json $MSjsonDL

# select the values
$properties = $MSjsonOBJ.values.properties

# filter to pick only specific regions and null for ones that don't have regions
$regions = $properties | where-object { $_.region -match $regionFilter -OR $_.region -eq "" }

# save files using reg ex to filter ipv4 and ipv6 to their own files if needed
$regions.addressPrefixes | out-file "$exportlocation\$($time)_filtered_region_$($regionFilter)_all_IPs.txt"

$regions.addressPrefixes -match '.' | out-file "$exportlocation\$($time)_filtered_region_$($regionFilter)_v4_IPs.txt"

$regions.addressPrefixes -match '\:' | out-file "$exportlocation\$($time)_filtered_region_$($regionFilter)_v6_IPs.txt"

Walking Through the Script

1. Variables and output location

$exportlocation is where the text files land — a local path such as C:\temp\ works fine, and a UNC path or a mapped drive works just as well if you want the output to go straight onto a shared network location that your firewall team picks up. $regionFilter is a regular expression, not an exact string match, which is why "canada" matches both canadacentral and canadaeast in one pass.

2. Downloading the file

Invoke-WebRequest pulls the JSON into memory. There is no need to write it to disk first, but if you want an audit trail of exactly which release you consumed, add -OutFile and keep the raw file alongside the extracted text. Note that the URL is hard-coded, so update it whenever Microsoft publishes a new release.

3. Converting and selecting

ConvertFrom-Json turns the response into a PowerShell object, and $MSjsonOBJ.values.properties uses property enumeration to collect the properties member of every element in the values array into a single flat collection. That is the part of the script that does the most work with the fewest keystrokes.

4. Filtering by region

The Where-Object clause keeps entries whose region matches the filter, plus any entry with an empty region. The second half matters: global service tags such as AzureActiveDirectory, AzureCloud itself, and several Microsoft 365 tags have no region value at all. If you drop them, you silently lose part of the service — which is exactly the kind of omission that produces "Teams works but the login page does not" tickets.

5. Writing the output

The final three lines write the same data three ways: everything, IPv4 only, IPv6 only. The IPv6 split uses a regex matching a literal colon, and since PowerShell's -match on an array returns only the elements that match, piping to Out-File produces a clean filtered list. The IPv4 line uses -match '.', which technically matches any string containing at least one character. In practice it behaves as "everything that is not IPv6", but it is worth reading that line twice before you trust it in a new script — it is a stylistic shortcut rather than an explicit IPv4 test.

The Output Files

Three files are produced per run, all named with the timestamp pattern yyyy_MMM_dd_hhmm_tt so that successive runs never overwrite one another:

  • …_all_IPs.txt — every prefix for the filtered region set, IPv4 and IPv6 mixed. This is the file most firewall vendors expect when you bulk-import an address group.
  • …_v4_IPs.txt — IPv4 prefixes only, suitable for platforms with no IPv6 support or for a IPv4-only NAT policy.
  • …_v6_IPs.txt — IPv6 prefixes only, useful when you are building a separate dual-stack rule set.

Depending on which service tags you kept with your region filter, the all-IPs file can run to thousands of lines, so plan your firewall object limits accordingly — many platforms cap the number of entries per address group, and Azure's full public list is well past the limits of some older appliances.

A Slightly More Robust Version

The original script is perfectly usable as-is. If you are going to run it unattended, a few small changes make it easier to live with: stop on error instead of continuing with a partial download, accept the URL as a parameter, create the output folder, deduplicate the prefixes, and use an explicit IPv4 regex instead of '.'.

param(
    [string]$ExportLocation = "C:\temp\azure-ip",
    [string]$RegionFilter   = "canada",
    [string]$JsonUrl        = "https://download.microsoft.com/download/7/1/D/71D86715-5596-4529-9B13-DA13A5DE5B63/ServiceTags_Public_20201228.json"
)

$ErrorActionPreference = "Stop"

$stamp = Get-Date -Format "yyyy_MMM_dd_hhmm_tt"
$work  = Join-Path $ExportLocation $stamp
New-Item -ItemType Directory -Path $work -Force | Out-Null

$raw = (Invoke-WebRequest -Uri $JsonUrl -UseBasicParsing).Content
$obj = $raw | ConvertFrom-Json

Write-Host "changeNumber : $($obj.changeNumber)"
Write-Host "cloud        : $($obj.cloud)"

$regions = $obj.values.properties | Where-Object {
    $_.region -match $RegionFilter -or $_.region -eq ""
}

$all = $regions.addressPrefixes | Sort-Object -Unique
$v4  = $all | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}/' }
$v6  = $all | Where-Object { $_ -match ':' }

$all | Out-File (Join-Path $work "all_IPs.txt") -Encoding ascii
$v4  | Out-File (Join-Path $work "v4_IPs.txt")  -Encoding ascii
$v6  | Out-File (Join-Path $work "v6_IPs.txt")  -Encoding ascii

Write-Host "written to $work"

Two details are worth calling out. Sort-Object -Unique removes prefixes that appear under more than one service tag, which is common — the same /24 can be listed for several tags, and duplicate entries waste slots in a firewall address group. And the explicit IPv4 regex anchors on a dotted quad followed by a slash, so it cannot accidentally pick up an IPv6 prefix that happens to contain a dot in its embedded IPv4 notation, which is a genuine possibility with ::ffff:x.x.x.x style entries.

Running It on a Schedule

Because the list changes weekly, the real value comes from automation rather than from manual runs. Windows Task Scheduler can call the script on a weekly trigger:

$action  = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Get-AzureIPs.ps1"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 6am
Register-ScheduledTask -TaskName "Azure IP Ranges Download" `
    -Action $action -Trigger $trigger -RunLevel Highest

Schedule it after Microsoft's own publication window, and have the task copy the resulting text files to whatever location your firewall automation reads from. If you already have a library of small operational scripts, the pattern is the same one used for other maintenance jobs — see Windows Updates PowerShell Script for a comparable structure with logging and error handling.

Practical Uses for the IP Lists

  • On-premises firewall allow-lists — permit outbound 443 to the Teams and Exchange Online prefixes so that call quality traffic bypasses TLS inspection. Most vendors will import a plain list of CIDR blocks directly.
  • Web proxy bypass — the same list, loaded as a transparent-proxy exclusion set or a PAC file source.
  • Router and switch ACLs — on MikroTik RouterOS, address lists are the natural landing zone for imported prefixes; the process of building and referencing an address list from a firewall filter chain is covered in MikroTik RouterOS Firewall: Chains, Filters and Address Lists.
  • Cloud egress rules — when a non-Azure workload needs to reach Azure services, the regional prefixes become the source for NSG or cloud firewall rules.
  • Monitoring and change detection — keep yesterday's file and diff it against today's. The changeNumber alone tells you whether anything moved, and a diff tells you which service gained or lost address space.

Pitfalls to Watch For

  • The URL expires. A new file name is published each release, and the old GUID path can disappear. If your scheduled job suddenly produces a 404, this is almost always the cause.
  • Region filters are regex, not equality. "canada" intentionally matches two regions; "central" would match dozens of unrelated regions across the world. Anchor your pattern when you need precision.
  • Do not discard empty regions. Global tags carry the endpoints that authentication, licensing and several Microsoft 365 services depend on.
  • Platform matters. The same address range can appear under a tag for Windows and for Azure with different properties. Filter on the property you actually need if you are targeting a single platform.
  • Prefix counts grow. Full public tag sets for a broad region filter can exceed the object limits on older firewalls. Import per service tag instead of one giant list, or keep only the tags you truly need.
  • This is not a licence to over-permit. Allowing all of AzureCloud means allowing a very large slice of Microsoft's public infrastructure. Narrow to the specific service tags your users require.

Wrapping Up

Azure IP Downloader v1 does one job and does it well: pull the service tag JSON, filter it, and hand you clean text files. The whole script is under twenty meaningful lines, which makes it easy to audit and easy to extend. The important habits around it are the ones that make any automation reliable — treat the download URL as configuration rather than a constant, keep the global (empty-region) tags in your filter, run it on a weekly schedule instead of on demand, and store the changeNumber so you can prove which release of the data is currently deployed to your firewalls.

If you work with the Azure side of the same estate, Azure AD Connect 2.0 Won't Start covers a common identity-hybrid failure that shows up in the same environments. The download page for the current JSON release is here: Azure IP Ranges and Service Tags – Public Cloud.