Access Azure API with a bearer token for impersonation

In the past, I wrote an article on how to get Azure service tags. Back then, I was not able to access the Rest API provided.

A service tag represents a group of IP address prefixes from a given Azure service.

This week I revisited the API and dived a little deeper into this call.

In this blog, I show you how to read service tags using the Azure Rest API and we learn how to cope with the bearer token if we want to access the Access API. I show it both in Postman and using C# code.

The call we need to execute for the service tags is this GET method:

GET
https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Network/locations/{location}/serviceTags?api-version=2020-04-01

We have to fill in the subscription Id GUID (which is available in your Azure subscription) and the region/location (eg. ‘westeurope’).

When I execute this, I get this exception message:

{"error":{"code":"AuthenticationFailed","message":"Authentication failed. The 'Authorization' header is missing."}}

OK, this means I have to authenticate against my Azure subscription. As documented, I have to make use of the ‘Azure Active Directory OAuth2 Flow’ because we have to do some impersonation.

It was not directly clear to me how to create a security context that I could pass on to this service tags call. Luckily, I got some help from this documentation. A bearer token is the solution.

How do we get an Azure bearer token?

It starts with executing this Azure CLI command:

az login
az ad sp create-for-rbac -n "testaccount"

This gives you a (new) service principal with an tennant, app id and password:

Note: You can choose your own name.

Note: Each time you execute this call, a new password is generated for the same service principal.

The result of this call is an ‘app’ being a contributer inside your subscription:

Note: You can take away the access rights by simply removing this app registration.

Now we have an Azure service principal, we can fill in all security information into this call body which will give us the needed bearer token:

POST
https://login.microsoftonline.com/{tenantid}/oauth2/token

Note: You have to fill in your tenantid again.

As seen in Postman, a bearer token is returned:

Note: The bearer token can only be used for a certain time span. It’s only valid for one hour or soo.

The ‘client_id’ has to be filled with the appId. The ‘client_secret’ is the password.

Now, let’s use it!

Again in Postman, we see the arrival of the service tags when you fill in the returned bearer token into the bearer token authorization header:

Unfortunately. I’m not using Postman in production…

How to pull this off in programming code?

Yes, I want to execute this call using C# code. This is the result:

using Newtonsoft.Json;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;

namespace ServiceTagsApp
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var subscriptionId = "{your subscriptionId}";

            //// Values from az ad sp create-for-rbac -n "testaccount" after az login is executed

            var appId = "{returned appId}";
            var password = "{returned password}";
            var tennant = "{returned tennant}";

            var bearer = GetBearer(tennant, appId, password);

            var location = "westeurope";

            var jsonString = GetTags(subscriptionId, bearer, location);

            var json = JsonConvert.DeserializeObject<dynamic>(jsonString);

            Console.WriteLine($"Type: {json.type.Value}");
            Console.WriteLine($"Change number: {json.changeNumber.Value}");
            Console.WriteLine($"Values count: {json.values.Count}");

            IEnumerable<dynamic> values = json.values;

            var apiManagement = values.First(x => x.id.Value.Contains("ApiManagement"));

            Console.WriteLine($"Api Management id: {apiManagement.id}");
            Console.WriteLine($"Api Management change number: {apiManagement.properties.changeNumber}");
            Console.WriteLine($"Api Management address prefixes count: {apiManagement.properties.addressPrefixes.Count}");

            Console.WriteLine("press a key to exit");
            Console.ReadKey();
        }

        private static string GetTags(string subscriptionId, string bearer, string location)
        {
            var url = $"https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Network/locations/{location}/serviceTags?api-version=2020-04-01";

            using var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
            var req = new HttpRequestMessage(HttpMethod.Get, url);

            using var res = client.SendAsync(req).Result;
            var jsonString = res.Content.ReadAsStringAsync().Result;

            return jsonString;
        }

        private static string GetBearer(string tennant, string appId, string password)
        {
            var nvc = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "client_credentials"),
                new KeyValuePair<string, string>("client_id", appId),
                new KeyValuePair<string, string>("client_secret", password),
                new KeyValuePair<string, string>("resource", "https://management.azure.com/")
            };

            var url = $"https://login.microsoftonline.com/{tennant}/oauth2/token";

            using var client = new HttpClient();
            var req = new HttpRequestMessage(HttpMethod.Post, url)
            {
                Content = new FormUrlEncodedContent(nvc)
            };

            using var res = client.SendAsync(req).Result;
            var jsonString = res.Content.ReadAsStringAsync().Result;
            var json = JsonConvert.DeserializeObject<dynamic>(jsonString);
            return json.access_token;
        }
    }
}

Fill in the fields required (subscription, appId, password, tenant).

When executed, the result of this application gives:

We now are able to parse the JSON returned after deserializing it into a dynamic.

As you can see, we can navigate to the ApiManagement node and read the tags there. This is the JSON returned:

Conclusion

This is an example of how to access the Azure Rest API using impersonation using a bearer token.

This code is simplified to show how it works. Please make it more robust when using it in production.