Signing in to Azure from bash
Sometimes I need a zero-install way to interact with Azure. I have no specific Azure utilities at hand, no Python, no nothing. Usually, Azure management is done using PowerShell, the az cli or, if you want raw REST calls, the armclient. But for my customer, even can be too much ceremony.
So the question was how can I get going with purely bash
, cURL
and jq
for JSON parsing?
If you’re running inside a VM, with Managed Identity enabled, you can easily fetch a token. But unfortunately the VM wasn’t authorized to hit the resource I care about.
Next stop service principals. Problem is customer’s AD admin team running a tough regime, and don’t hand out service principals.
So ultimately, how can I get my actual AAD user identity avail in the shell? In the end, all I need is a bearer token.
Let’s dive right in:
A few variables first
I want to authN against ‘my’ Azure AD tenant, and want to hit the Azure ARM REST API:
#!/bin/bash
aadTenant="chgeuerfte.onmicrosoft.com"
resource="https://management.azure.com/"
Doing a device login
For the full user login, i.e. device authN, here’s what happens under the hood: The code needs to fetch a device code, and then use that code to poll and validate whether the user authenticated. Quick hint: If you wanna snoop on cURL’s requests with something like fiddler, you should add this --proxy http://127.0.0.1:8888/ --insecure
to the calls.
#!/bin/bash
# --proxy http://127.0.0.1:8888/ --insecure \
deviceResponse="$(curl \
--silent \
--request POST \
--data-urlencode "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
--data-urlencode "resource=${resource}" \
"https://login.microsoftonline.com/${aadTenant}/oauth2/devicecode?api-version=1.0")"
device_code="$(echo "${deviceResponse}" | jq -r ".device_code")"
sleep_duration="$(echo "${deviceResponse}" | jq -r ".interval")"
access_token=""
while [ "${access_token}" == "" ]
do
tokenResponse="$(curl \
--silent \
--request POST \
--data-urlencode "grant_type=device_code" \
--data-urlencode "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
--data-urlencode "resource=${resource}" \
--data-urlencode "code=${device_code}" \
"https://login.microsoftonline.com/common/oauth2/token")"
if [ "$(echo "${tokenResponse}" | jq -r ".error")" == "authorization_pending" ]; then
echo "$(echo "${deviceResponse}" | jq -r ".message")"
sleep "${sleep_duration}"
else
access_token="$(echo "${tokenResponse}" | jq -r ".access_token")"
echo "User authenticated"
fi
done
echo "${access_token}"
Using a service principal
Assuming we have a ‘real’ service principal, we can do this:
#!/bin/bash
resource="https://management.azure.com/"
aadTenant="chgeuerfte.onmicrosoft.com"
SAMPLE_SP_APPID="*** put your service principal application ID here ***"
SAMPLE_SP_KEY="*** put your service principal application secret here ***"
access_token="$(curl \
--silent \
--request POST \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${SAMPLE_SP_APPID}" \
--data-urlencode "client_secret=${SAMPLE_SP_KEY}" \
--data-urlencode "resource=${resource}" \
"https://login.microsoftonline.com/${aadTenant}/oauth2/token" | \
jq -r ".access_token")"
Using managed VM identity (running inside an Azure VM)
#!/bin/bash
resource="https://management.azure.com/"
access_token="$(curl -s -H Metadata:true \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=${resource}" | \
jq -r ".access_token")"
Fetch the subscription ID, from the Azure VM’s instance metadata endpoint
#!/bin/bash
subscriptionId="$(curl -s -H Metadata:true \
"http://169.254.169.254/metadata/instance?api-version=2017-08-01" | \
jq -r ".compute.subscriptionId")"
Invoke the ARM API, for example with a listing of resource groups
#!/bin/bash
subscriptionId="724467b5-bee4-484b-bf13-d6a5505d2b51"
# --proxy http://127.0.0.1:8888/ --insecure \
curl --silent --get \
--header "Authorization: Bearer ${access_token}" \
"https://management.azure.com/subscriptions/${subscriptionId}/resourcegroups?api-version=2018-05-01" | \
jq -r ".value[].name"
Fetching a secret from Azure KeyVault using a managed identity
This little script demonstrates how to fetch a secret from an Azure KeyVault, using a managed identity on an Azure VM. Just adapt key_vault_name
and secret_name
accordingly, and of course ensure that the managed identity can actually read the secret.
#!/bin/bash
get_secret_from_keyvault() {
local key_vault_name=${1}
local secret_name=${2}
resource="https://vault.azure.net"
access_token="$(curl -s -H Metadata:true \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&bypass_cache=true&resource=${resource}" | \
jq -r ".access_token")"
apiVersion="7.0"
#
# Fetch the latest version
#
secretVersion="$(curl -s -H "Authorization: Bearer ${access_token}" \
"https://${key_vault_name}.vault.azure.net/secrets/${secret_name}/versions?api-version=${apiVersion}" | \
jq -r ".value | sort_by(.attributes.created) | .[-1].id")"
#
# Fetch the actual secret's value
#
secret="$(curl -s -H "Authorization: Bearer ${access_token}" \
"${secretVersion}?api-version=${apiVersion}" | \
jq -r ".value" )"
echo "${secret}"
}
echo "The secret is $(get_secret_from_keyvault "chgeuerkeyvault" "secret1")"
Shutdown a VM, quite radically (skip graceful shutdown, just turn it off)
The skipShutdown=true
below is useful in STONITH scenarios.
#!/bin/bash
...
subscriptionId="..."
resourceGroup="myrg"
vmName="somevm"
curl \
--silent \
--include \
--request POST \
--header "Authorization: Bearer ${access_token}" \
--header "Content-Length: 0" \
"https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Compute/virtualMachines/${vmName}/powerOff?skipShutdown=true&api-version=2019-03-01"
Thanks for reading, if you liked it, I’d appreciate a retweet.