azure-cliazure-rest-apiazure-dns

"az network dns zone export" only exports the first 100 recordsets


Setting this question up to self-answer because I lost too much time solving it, and I want to make sure the answer is put out there for future sufferers to find and avoid the same rabbit-hole I went down...


Issue

I'm trying to use the az network dns zone export command from the Az Cli in a PowerShell script to export the configuration from an Azure DNS Zone, but for some reason it's only exporting the first 100 records if I use az login to connect as a Service Principal with a custom IAM role assigned.

E.g.:

$ErrorActionPreference = "Stop";
Set-StrictMode -Version "Latest";

# see https://github.com/Azure/azure-cli/issues/26052
az config unset core.allow_broker

# log out any existing user
az logout

# log back in as the spn
az login --service-principal `
    --username "my-client-id" `
    --password "my-client-secret" `
    --tenant   "my-tenant-id"

# export the dns zone (only the first 100 records get exported :-(
az network dns zone export --resource-group "my-dns-rg" --name "mydnszone.com"

If I run the az network dns zone export command with my personal account logged in (which is an Owner on My Subscription) it works fine and exports all 575 records, so the problem appears to be permission-related, but it's confusing because the first 100 records are exported as the Service Principal so it has at least some permissions to read them.

Environment

My environment setup is as follows:


Solution

  • tl;dr

    Permissions required to export all pages of records via the az network dns zone export command are:

    Operation Description
    Microsoft.Network/dnszones/read Get the DNS zone, in JSON format. The zone properties include tags, etag, numberOfRecordSets, and maxNumberOfRecordSets. Note that this command does not retrieve the record sets contained within the zone.
    Microsoft.Network/dnszones/recordsets/read Gets DNS record sets across types
    Microsoft.Network/dnszones/all/read Gets DNS record sets across types

    Summary

    Confusingly, no error messages are shown if the Microsoft.Network/dnsZones/recordsets/read provider operation is present but the Microsoft.Network/dnszones/all/read provider operation is missing - instead the "Record Sets - List All By Dns Zone" endpoint returns pagination responses but with zero records included in the data.

    More details

    Running the az network dns zone export command with the --debug flag shows it first makes a call to the Record Sets - List By Dns Zone Azure REST endpoint - e.g.

    https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/my-dns-rg/providers/Microsoft.Network/dnsZones/mydnszone.com/recordsets?api-version=2023-07-01-preview
    

    If the current account has the Microsoft.Network/dnszones/recordsets/read provider operation this returns a response in the format:

    {
        "nextLink": "https://management.azure.com:443/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx/resourceGroups/my-dns-rg/providers/Microsoft.Network/dnszones/mydnszone.com/ALL?api-version=2023-07-01-preview&$skipToken=xxxxxxxxxxxxx",
        "value": [
          ... first 100 recordets from the dns zone ...
        ]
      }
    

    (If there are less than 100 records the nextLink field is missing from the response).

    Note that the nextLink ends in /ALL which is the Record Sets - List All By Dns Zone endpoint, so subsequent calls made by the Az Cli to read paginated data hit this endpoint instead of the original "Record Sets - List By Dns Zone".

    Unfortunately, this endpoint requires a different provider operation - namely Microsoft.Network/dnszones/all/read - if this is missing the response is in the following format:

     {
        "nextLink": "https://management.azure.com:443/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx/resourceGroups/my-dns-rg/providers/Microsoft.Network/dnszones/mydnszone.com/ALL?api-version=2023-07-01-preview&$skipToken=xxxxxxxxxxxxx",
        "value": []
      }
    

    Note that pagination still works. For a zone with 575 records the nextLink redirects to a chain of 5 additional pages after the initial request, which is the correct number for a zone with 575 records, but the requests ending in /ALL contain "value": [].

    Epilogue

    When trying to create a minimal custom IAM role for exporting dns zones, be sure to add all the provider operations in the "tl;dr" section at the top of this answer.