A zonal service or resource "supports AZs, and can be deployed to a specific, self-selected availability zone, to achieve more stringent latency or performance requirements."
A zonal resource's ARM JSON representation contains a zones
array. For example, let's look at a (zonal) IP address:
{
"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/...",
"type": "Microsoft.Network/publicIPAddresses",
"name": "ipproductionweb",
"resourceGroup": "webfrontend",
"location": "westeurope",
"zones": [
"1"
],
...
}
checkZonePeers
API, which gives you a mapping table.checkZonePeers
API work?checkZonePeers
API lets you retrieve a mapping table, which tells you how other subscriptions call an AZ, which your subscription knows under a certain name.westeurope
region) is 11111111-1111-1111-1111-111111111111
. We ask the API:11111111-1111-1111-1111-111111111111
, and I am interested how two other subscription IDs (22222222-2222-2222-2222-222222222222
and 33333333-3333-3333-3333-333333333333
), call 'my' AZs in westeurope
...POST /subscriptions/11111111-1111-1111-1111-111111111111/providers/Microsoft.Resources/checkZonePeers?api-version=2020-01-01
Host: management.azure.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1V...
Content-Type: application/json
{
"location": "westeurope",
"subscriptionIds": [
"/subscriptions/22222222-2222-2222-2222-222222222222",
"/subscriptions/33333333-3333-3333-3333-333333333333"
]
}
{
"subscriptionId": "11111111-1111-1111-1111-111111111111",
"location": "westeurope",
"availabilityZonePeers": [
{
"availabilityZone": "1",
"peers": [
{ "subscriptionId": "22222222-2222-2222-2222-222222222222", "availabilityZone": "1" },
{ "subscriptionId": "33333333-3333-3333-3333-333333333333", "availabilityZone": "3" }
]
},
{
"availabilityZone": "2",
"peers": [
{ "subscriptionId": "22222222-2222-2222-2222-222222222222", "availabilityZone": "3" },
{ "subscriptionId": "33333333-3333-3333-3333-333333333333", "availabilityZone": "2" }
]
},
{
"availabilityZone": "3",
"peers": [
{ "subscriptionId": "22222222-2222-2222-2222-222222222222", "availabilityZone": "2" },
{ "subscriptionId": "33333333-3333-3333-3333-333333333333", "availabilityZone": "1" }
]
}
]
}
Subscription | AZ X | AZ Y | AZ Z |
11111111-1111-1111-1111-111111111111 | 1 | 2 | 3 |
22222222-2222-2222-2222-222222222222 | 1 | 3 | 2 |
33333333-3333-3333-3333-333333333333 | 3 | 2 | 1 |
11111111-...
) is AZ "1" as well in 22222222...
, but it's called AZ "3" in 33333333-...
.Authorization
header?checkZonePeers
API that you are authorized to access all of them. Unfortunately, the API only accepts a single bearer token in the HTTP Authorization header. How can we supply the other tokens? For such tenant-boundary-spanning API calls, the Azure Resource Manager API has a custom HTTP header, called x-ms-authorization-auxiliary
, which can hold up to three 'auxiliary' tokens.Authorization: Bearer ...
, you can put three additional AAD tokens from other AAD tenants into the x-ms-authorization-auxiliary
.AuxiliaryTokensInvalidUserIdentity
error, indicating that Authentication failed for auxiliary token: The '1' auxiliary tokens are from the client(s) 'live.com#foobar@hotmail.com' which are different from the client of primary identity 'chgeuer@microsoft.com'.
Microsoft.Resources/AvailabilityZonePeering
feature for your subsaz feature show \
--namespace Microsoft.Resources \
--name AvailabilityZonePeering \
--subscription chgeuer-work \
| jq .properties.state
"Registered"
, make sure you register for the feature in all relevant subscriptions:az feature register \
--namespace Microsoft.Resources \
--name AvailabilityZonePeering
bash
, curl
and jq
)Azure Subscription ID |
Azure Active Directory Tenant ID
|
11111111-1111-1111-1111-111111111111 | aadaadaa-1111-1111-1111-111111111111 |
22222222-2222-2222-2222-222222222222 | aadaadaa-2222-2222-2222-222222222222 |
33333333-3333-3333-3333-333333333333 | aadaadaa-3333-3333-3333-333333333333 |
access_token_1 Issuer: iss="https://sts.windows.net/aadaadaa-1111-1111-1111-111111111111/"
access_token_1 Subject: sub="djwNuWxvH-6cUIHWIwRBjVanUvsrG5Ty6eJMqcK722U"
access_token_2 Issuer: iss="https://sts.windows.net/aadaadaa-2222-2222-2222-222222222222/"
access_token_2 Subject: sub="djwNuWxvH-6cUIHWIwRBjVanUvsrG5Ty6eJMqcK722U"
access_token_3 Issuer: iss="https://sts.windows.net/aadaadaa-3333-3333-3333-333333333333/"
access_token_3 Subject: sub="djwNuWxvH-6cUIHWIwRBjVanUvsrG5Ty6eJMqcK722U"
As you can see, the Subject (sub
) is the same in all of them (as mentioned earlier, this is needed)...
#!/bin/bash
aad1="aadaadaa-1111-1111-1111-111111111111"
sub1="11111111-1111-1111-1111-111111111111"
aad2="aadaadaa-2222-2222-2222-222222222222"
sub2="22222222-2222-2222-2222-222222222222"
aad3="aadaadaa-3333-3333-3333-333333333333"
sub3="33333333-3333-3333-3333-333333333333"
function deviceLogin {
local tenant="$1" ;
local resource="$2" ;
deviceResponse="$(curl \
--silent \
--request POST \
--data-urlencode "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
--data-urlencode "scope=${resource}" \
"https://login.microsoftonline.com/${tenant}/oauth2/v2.0/devicecode")" ;
device_code="$(echo "${deviceResponse}" | jq -r ".device_code")" ;
sleep_duration="$(echo "${deviceResponse}" | jq -r ".interval")" ;
access_token="" ;
if [[ $(grep --ignore-case Microsoft /proc/version) ]]; then
# On WSL, copy response code to clipboard, and launch user's web browser
echo "$( echo "${deviceResponse}" | jq -r ".user_code" )" | iconv -f utf-8 -t utf-16le | clip.exe
cmd.exe /C "start $( echo "${deviceResponse}" | jq -r ".verification_uri" )"
fi
while [[ "${access_token}" == "" ]]
do
tokenResponse="$(curl \
--silent \
--request POST \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
--data-urlencode "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
--data-urlencode "device_code=${device_code}" \
"https://login.microsoftonline.com/{aadTenant}/oauth2/v2.0/token")" ;
if [ "$(echo "${tokenResponse}" | jq -r ".error")" == "authorization_pending" ]; then
>&2 echo "$(echo "${deviceResponse}" | jq -r ".message")" ;
sleep "${sleep_duration}" ;
else
access_token="$(echo "${tokenResponse}" | jq -r ".access_token")" ;
fi
done ;
echo "${access_token}"
}
function showToken {
local name="$1" ;
local access_token="$2" ;
claims="$( jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "${access_token}" )"
echo "${name} Issuer: iss=$( echo "${claims}" | jq .iss )"
# echo "${name} Audience: aud=$( echo "${claims}" | jq .aud )"
echo "${name} Subject: sub=$( echo "${claims}" | jq .sub )"
}
echo "Please login to the tenants using the same user ID..."
arm_api="https://management.azure.com/.default"
access_token_1="$( deviceLogin "${aad1}" "${arm_api}" )"
access_token_2="$( deviceLogin "${aad2}" "${arm_api}" )"
access_token_3="$( deviceLogin "${aad3}" "${arm_api}" )"
#
# Show selected contents of the access tokens, like issuer and subject.
#
showToken "access_token_1" "${access_token_1}"
showToken "access_token_2" "${access_token_2}"
showToken "access_token_3" "${access_token_3}"
location="westeurope"
checkZonePeersBody="$( echo "{}" \
| jq --arg x "${location}" '.location=$x' \
| jq '.subscriptionIds=[]' \
| jq --arg x "/subscriptions/${sub2}" '.subscriptionIds[.subscriptionIds | length] |= .+ $x' \
| jq --arg x "/subscriptions/${sub3}" '.subscriptionIds[.subscriptionIds | length] |= .+ $x' \
)"
curl \
--silent \
--request POST \
--header "Authorization: Bearer ${access_token_1}" \
--header "x-ms-authorization-auxiliary: Bearer ${access_token_2}, Bearer ${access_token_3}" \
--header "Content-Type: application/json" \
--url "https://management.azure.com/subscriptions/${sub1}/providers/Microsoft.Resources/checkZonePeers?api-version=2020-01-01" \
--data "${checkZonePeersBody}" \
| jq "."
In case you enjoyed this article, leave a comment, give me a shoutout via e-mail (chgeuer at microsoft), or ping me on Twitter (@chgeuer)...
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.