Automate Retrieving Azure VM Data Using Azure Resource Graph (ARG) and C#

In this post, we’ll look into using Azure Functions to run C# code. We’ll keep things simple, and we’ll only use a timer-based function. The function itself will simply write output data to a storage account, as a simple JSON file. The payload is something we’ve looked at before in detail: the list of all Azure VMs with all their private and public IPs.

Why go over the same thing that was discussed previously? First, we’ve used several methods to gather the data in that post (Powershell, Azure Resource Graph Explorer (ARGE), Azure CLI) but neither involved C#. Secondly, the methods depicted weren’t automation-ready, in the sense that one would still have to perform things manually to get the results (for Powershell, run the script; for Azure CLI, run the commands; for ARGE, run the query and manually export the results).

This time, C# code within an Azure Function will automatically write our output .json file, without any sort of manual intervention, via a timer-based trigger. The underlying technology to get all the networking data for the Azure VMs will be Azure Resource Graph (ARG).

We’ll start with a high overview of how our components will interact. Then we’ll discuss the permissions required, see 3 ways to retrieve the list of Azure subscriptions (and why we have to do it), and build the code that will run the ARG query and place the results within a target storage account. Next, we’ll see 2 approaches for using Azure Function Apps to automate the code, and finish with a few Q&A. The plan, with direct links for each section:

Assumptions and Expectations

A couple of notes about the upcoming post:

  • Important warning before moving forward: This is not production-grade code! You’ll see keys that are hard-coded, instead of being stored in alternate locations (such as Azure Vault), multi-line queries specified directly in code, little or no exception handling, etc. The code snippets presented do work and get the job done, but they shouldn’t be placed in production as-is
  • All the code in this post was tested on .NET Core 3.1 and .NET 5. Parts of the authentication code involving AzureServiceTokenProvider were also tested on .NET Framework 4.7.2
  • This article only discusses – and retrives data for – the ARM virtual machines, and excludes the “classical” VMs (ASM model). The latter can be retrieved easily by simply swapping the ARG query used throughout this article with the one in listing 21 here

High-Level Workflow

At its core, we’ll have a function within a function app that obtains all the Azure subscriptions in the current tenant, runs a query against the Azure Resource Graph (ARG) service and retrieves the results, then persists them to a storage account as a simple .json file:

Figure 1 – Order of the operations that each of our function apps will perform

For the storage account, we’re going to use one of its access keys to write the .json file. But for the list of Azure subscriptions that our Azure function will retrieve, this depends on the permissions of the identity it assumes. Say the respective identity only has permissions to access 5 subscriptions out of the overall 50 in the tenant, then it’ll be only 5 in the retrieved list. Conversely, running the ARG query against the Azure tenant will also be tied to the permissions of the identity that “powers” the Azure function – as such, the data that the ARG service returns will only include what the caller has access to (as discussed in a previous post here). For our example above, the output of an ARG query against the tenant will only include results based on the 5 Azure subscriptions the identity has access to, regardless of the complexity of the query itself (eg multiple join, project, etc).

We’ll consider 2 types of identities that will back our Azure function, and as it has an impact against the permissions required, let’s discuss identities first.

Function Apps Identities

The 2 function apps we’ll use in this post will each use a different mechanism of authentication: the first will be backed by an enterprise app, while the second will use the function app’s own managed identity to authenticate against the Azure tenant.

We’ve looked in a previous post on what’s involved in using an enterprise app’s identity in code. Specifically, in that post, an enterprise app was created, and in turn a secret was created for it, which was used further in C# code to authenticate via the Microsoft Graph API (to an Excel file stored in Sharepoint Online). It’s the same thing we’ll do here as well, only that it’s not going to be Microsoft Graph API being targeted by the code, but Azure subscriptions and their resources. As the function within our function app will run, the identity that Azure will “see” coming towards it will actually be the enterprise app created, given that our C# code will authenticate based on one of the enterprise app’s secrets.

The second way, using the function app’s managed identity, will free us from having to explicitly manage credentials, as it’s Azure itself that will take care of this aspect, leaving us free to only implement our functional bit. Note that we’ll be using the system-assigned managed identity of our function app, not user-assigned identities; the distinction is very well captured in Microsoft’s own documentation here.

Why use 2 different approaches? Not because there’s a definite advantage to one – as there are actually drawbacks to both, whereby managed-identity functions cannot be debugged locally, while placing and managing an enterprise app’s secrets can become cumbersome – but because being able to opt between 2 ways of implementing something is generally a good thing.

Regardless of the kind of identity the function will assume, the correct permissions need to be assigned. Let’s go over this next.

Permissions Required

Going back to the conceptual diagram in figure 1, our function will need the required permissions for performing the first 2 steps, retrieving the list of Azure permissions and running the ARG query. The access for both operations is going to be controlled by Azure Resource Manager (ARM). ARM is the deployment and management service for Azure, and as per this article, “when a user sends a request from any of the Azure tools, APIs, or SDKs, Resource Manager receives the request. It authenticates and authorizes the request“.

Therefore the identity of the function app will have to be assigned permissions within Azure Resource Manager. Granting the minimum permissions for subscriptions so that we can retrieve VM data involves granting read permissions against the said subscriptions. You might be tempted to use the “Global Reader” role to delegate this, but that’s not the right approach. Even more, if you go ahead and do this, the delegation will fail to work as expected. Why is that? Because the “Global Reader” is a specific type of role, called Azure AD role. These roles grant permissions against objects stored within Azure AD, such as users or groups, but fail to provide access to Azure resources such as subscriptions, virtual machine configuration, storage accounts, etc. For these, an Azure RBAC role is required. So what is the Azure RBAC role to be used for providing read access against Azure VM resources in the tenant, so we can successfully run the Azure Resource Graph (ARG) query against them? The answer is the “Reader” role, described here. The distinction between Azure AD roles and Azure RBAC ones is quite important, and this is covered by the excellent article here.

Granting the permissions doesn’t have to be done subscription by subscription, but directly at the tenant level, by targeting the Tenant Root management group. Since the root management group can’t be moved or deleted then all the subscriptions will be in scope, which in turn will mean info about all the VMs in the Azure tenant will be available as well.

Function App Backed by an Enterprise App

For this scenario, the function app will be simply assuming the identity of a separate enterprise app. The permissions will allow the enterprise app access to the target Azure resources, but the function app – given that it impersonates it – will have access as well.

First, create the function app. If using Visual Studio to write the code, then creating a new solution can be a way to go: choose the “Azure Functions” option, and in the next screen choose “Timer trigger” (but leave the defaults to “Azure Functions v3 (.NET Core)” and “Storage Emulator”). Once the solution is created, publish using the wizard, which will automatically allow you to create a new function app in Azure. The (non-timestamped) name used in this article is “RetrieveAzureVmData_wEntApp”.

Secondly, create a new enterprise app called “EnterpriseAppForRetrieveAzureVmData” using the steps discussed here. Make sure to create a client secret as well. Once the app is registered, because we’ve used the Azure portal to do this action*, a service principal is created as well within Azure AD. And once registered,  we can add the app’s service principal with “Reader” permissions at the Tenant Root group level:

Figure 2 – Assigning the Reader Azure RBAC role to the dedicated enterprise app

The final permissions list is below. The only other entry belongs to my elevated Global Administrator, which translated to the User Access Administrator role* within Azure RBAC, as seen below. As this is a test Azure tenant, with a limited number of permissions granted, there’s not much else to be seen.

Figure 3 – Resulting permissions assigned at the tenant root group level

Function App Backed by Its Managed Identity

With this approach, we’re going to use an identity associated with the function app itself in order to grant it the required permissions against the target Azure resources.

Once a system assigned managed identity is enabled for a function app, a “service principal of a special type that may only be used with Azure resources” will be created for the function app (source here). In the portal the new state* can be easily spotted:

Figure 4 – The system assigned managed identity turned on for our respective function app

Assigning the required permissions looks strikingly similar to what was done back in figure 2. It’s only that it’s not a dedicated enterprise app being used this time, but the function app’s own system assigned managed identity:

Figure 5 – Assigning the Reader Azure RBAC role to the function app’s managed identity

The end result is again similar to what was seen in figure 3, but this time you’ll notice a slightly different icon for this service principal:

Figure 6 – Resulting permissions assigned at the tenant root group level

Azure will be taking care of this service principal’s underlying credentials, as detailed here.

Retrieving the Azure Subscriptions

We didn’t particularly care about feeding a subscription list previously, when we discussed Azure Resource Graph Explorer (ARGE), Azure CLI, or Powershell. For ARGE, simply choosing “Select all” in the Azure’s portal default subscription filter would do, and all the subscriptions would be used as the respective query’s scope. With Powershell, Search-AzGraph would by default run the supplied query against all the subscriptions in scope.

So why care now? Because using the .NET types included in the NuGet packages we’ll be relying on mandate that a list of “input” subscriptions is specified. This is explained here:

The scope of the subscriptions from which resources are returned by a query depend on the method of accessing Resource Graph. Azure CLI and Azure PowerShell populate the list of subscriptions to include in the request based on the context of the authorized user. The list of subscriptions can be manually defined for each with the subscriptions and Subscription parameters, respectively. In REST API and all other SDKs, the list of subscriptions to include resources from must be explicitly defined as part of the request.

Getting the list of Azure subscriptions is straightforward and is a one-liner as seen here (the subscriptions table is joined to the one containing the vaults). The problem is that this is obtained using ARG, and we need the list of subscriptions…before invoking ARG.

The solution? Simply use a different way of retrieving the subscriptions, that doesn’t rely on ARG. Discussing with Microsoft Support yielded at least 3 ways of doing this:

  • Microsoft.Azure.Management.Subscription.SubscriptionClient type defined within the Microsoft.Azure.Management.Subscription NuGet package
  • Microsoft.Azure.Management.ResourceManager.Fluent.ResourceManager type defined within the Microsoft.Azure.ResourceManager.Fluent NuGet package
  • Microsoft.Azure.Management.ManagementGroups.ManagementGroupsAPIClient type defined within the Microsoft.Azure.Management.ManagementGroups NuGet package

But before expanding on how each method works, we need to discuss credentials.

A Word About Credentials and Tokens

So far we’ve discussed the 2 identities that the function within our function app will take once it executes (a separate enterprise app and the system managed identity of the function app, respectively) but we also need to look at how credentials are handled. Regardless of the identity used, we’ll have to use it to authenticate in code to perform 2 operations: 1) retrieving the list of subscriptions and 2) running our ARG query that will return VM data.

But why care about credentials? Isn’t a function app’s managed identity supposed to take care of the credentials, and make them invisible to the developer? The thing is that the process of credential management is indeed automated in this case – with Azure rotating the underlying keys/passwords – but the client library clients we’ll be using for obtaining the Azure subscriptions and running the ARG queries do require each a credential object to be explicitly supplied.

Our main job – which will be the same for the 3 methods described previously – from a credentials perspective will consist of 2 steps: extracting the access token from an input credential, and then “packing” this token in the formats required by the various client library clients we’ll need to retrieve the subscriptions.

There are at least 2 ways of getting to the credentials: DefaultAzureCredential (within the Azure Identity client library) and AzureServiceTokenProvider (within the App Authentication client library). As the note here reads as of Dec 2020, Azure Identity is the preferred client library going forward. Yet we’ll look at how to use both.

Each of the 2 client libraries supports multiple authentication mechanisms, or “sources” from where to pick up the credentials. Azure Identity’s DefaultAzureCredential supports 7 such sources, in the following order: environment variables, a managed identity, shared token cache credential, Visual Studio, Visual Studio Code, Azure CLI, or client browser. This is nicely described here (just be aware that as of Dec 2020 it only lists 6 sources as it misses the shared token cache shared between Microsoft applications).

App Authentication’s  AzureServiceTokenProvider supports 4 sources, in order: a managed identity, Visual Studio, Azure CLI, or Integrated Windows authentication*.

Both types described above have one particular goal – to get an access token. Actually, if you try to use them, or simply browse through the code that implements them (DefaultAzureCredential here and AzureServiceTokenProvider here) you’ll see that there are only a handful of public methods available for each, of which GetTokenAsync (for DefaultAzureCredential) and GetAccessTokenAsync (for AzureServiceTokenProvider) take center stage.

How these 2 methods work is again similar between the 2 types – when invoked, they’ll ask for their “sources”, in the order seen above for each, to provide an access token. The first access token successfully to be retrieved as such is returned.

The good part of having access to multiple “sources” is that our code can be tested easily on a local machine, before being pushed to Azure to run as part of an Azure function app. An example: suppose you’ve authenticated to Azure CLI on your local machine, which gives you the ability to test the code against your own credentials. When you further decide to push the code to a function app in Azure and use its managed identity, then this will run without any changes (provided the managed identity has at least the same level of permissions as your own user account, of course). This works with both types described above because the managed identity comes before Azure CLI in the order of “sources” for each of them.

Do not confuse the identity the code is running under with the credential itself. For example, building and running C# code that makes use of either of the 2 types above in the session of a local user on a development machine will have the resulting process using the security context of whatever user is logged in. However, the credential that the code will use inside will be “sourced” as detailed above – for DefaultAzureCredential it’s going to be the first one of: environment variables, shared token cache, Visual Studio account, Visual Studio code account, Azure CLI, or interactive credential (via browser); for AzureServiceTokenProvider it’s going to be the first of: Visual Studio, Azure CLI, or Integrated Windows authentication*. An example: running the code as a local user on a machine, but having at least one usable token within the shared  token cache will result in the respective token to be used, which could very well correspond to a different user account than the one which is logged in to the machine.

Coming back to the access token, obtaining one has to be done against a specific service. After all, credentials are being used – and Azure AD will happily validate them – but what is the ultimate service for which the access token is issued? It can be Office 365, Microsoft Graph, etc. Same as with a bank ATM: you enter a card, punch the PIN code and you get authenticated, but the machine can’t possibly know what you want to do next (redraw cash, deposit money, make a payment and to which recipient/service, etc). As it turns out, we’ll need access tokens issued for the Azure Resource Manager service, both for listing the Azure subscriptions and for running our ARG query.

You can see this as part of the methods used to get a token – GetTokenAsync (for DefaultAzureCredential) orGetAccessTokenAsync (for AzureServiceTokenProvider) – both need a resource as input parameter. The end of this article shows how to use both methods. Note that both require a ResourceId parameter.

What value needs to be supplied for this parameter? This will be different depending on the method used, and we’ll discuss this next.

Getting an Access Token (App Authentication client library)

The simplest overload for AzureServiceTokenProvider‘s GetAccessTokenAsync takes a string as parameter, indicating the resource for which the access token is issued, based on the supplied credentials (on which the AzureServiceTokenProvider was built on initially). For the Azure Resource Manager service, the value to be used is https://management.core.windows.net. The access token is a simple base64 string, as such our method will return a string:

private static async Task<string> ExtractAccessTokenFromCredentials(AzureServiceTokenProvider tokenCredential)
{
    // Extract the access token from the supplied credentials
    string accessToken = await tokenCredential.GetAccessTokenAsync("https://management.core.windows.net");
    return accessToken;
}

To call the method, simply pass it a new AzureServiceTokenProvider() object. It will return an access token from whichever authentication mechanism could supply one*, as discussed in the previous section.

Make sure the Microsoft.Azure.Services.AppAuthentication namespace is referenced (using) and have the Microsoft.Azure.Services.AppAuthentication NuGet package installed as well, in order for the method above to work.

Getting an Access Token (Azure Identity client library)

Although AzureDefaultCredential‘s GetTokenAsync also returns a string, representing the access token, similar to what we’ve seen previously, the parameter it takes is no longer a string, but a TokenRequestContext struct. This needs to contain the list of scopes we’re requesting for our resource. In our case, it’s going to be the previous value, followed by /.default, as stated here. Thus the required value becomes https://management.core.windows.net/.default.

One thing you might want to control is which credential “source” gets used. Relying on the default search order (described previously) will most likely get you a valid credential, and in turn an access token, but it’s not that straightforward to find out where it came from when working with DefaultAzureCredential. Not to mention that your different “sources” could store different credentials, leaving you with inconsistent outcomes.

How to fix this? One way is to use the specific credential type in code: eg if you want to make sure that the Azure CLI credentials that you’re using on that specific machine get used, without worrying about other “sources” “beating” it, then replace DefaultAzureCredential with AzureCliCredential. There’s a whole list of them here. Another solution is to use credential chaining, whereby the credential types you want to use are chained in a specified order. An example: new ChainedTokenCredential(new SharedTokenCacheCredential(), new AzureCliCredential()). Using GetTokenAsync on this object will ensure that the shared token cache mechanism is attempted first, followed by the Azure CLI one.

Something to take into account is that all the DefaultAzureCredential and its siblings inherit from the Azure.Core.TokenCredential abstract class. Since we want to be able to get an access token based on multiple types of credentials, and not be limited only to AzureDefaultCredential, our method will take as input parameter the common ancestor, as follows:

private static async Task<string> ExtractAccessTokenFromCredentials(Azure.Core.TokenCredential tokenCredential)
{
    // Extract the access token from the supplied credentials
    var accessToken = await tokenCredential.GetTokenAsync(
        new Azure.Core.TokenRequestContext(new string[] {"https://management.core.windows.net/.default"}),
        new System.Threading.CancellationToken());
    return accessToken.Token;
}

To call the method, simply pass it any credential object that inherits from Azure.Core.TokenCredential, eg new DefaultAzureCredential(). It will return an access token from whichever authentication mechanism could supply one, as discussed above.

Only the Azure.Identity NuGet package is required for the method above to work.

Retrieving Azure subscriptions (cont.)

At this point, we have an access token, obtained with either of the 2 methods described in the last 2 sections. And now that we have an access token, we can “pack” this in the credential type that each of the client libraries in use by the 3 methods retrieving the subscriptions needs. Then once the subscriptions are retrieved, their ids will be placed inside a List<string> object, and it’s this what each of the “GetAllAzureSubscriptions” methods described next will return.

Despite its name, the methods will not return all of the Azure subscriptions that exist in the respective Azure tenant, but all the subscriptions that the access token provided has access to.

Let’s go through each of the methods in order.

Retrieve Azure Subscriptions Method #1: Microsoft.Azure.Management.Subscription.SubscriptionClient

This method uses the Microsoft.Azure.Management.Subscription.SubscriptionClient type, defined within the Microsoft.Azure.Management.Subscription NuGet package. At the time of this writing the latest version has been issued 3 months ago (v1.1.5), therefore it doesn’t appear to be an end-of-life library.

The class above needs credentials in the form of Microsoft.Rest.ServiceClientCredentials in its constructor (possible constructors are listed here). But we somehow need to use the access token we have obtained previously, and ServiceClientCredentials‘s only constructor doesn’t take any parameter. However, TokenCredentials, which inherits from ServiceClientCredentials, does have one of its constructors that takes an access token as parameter. We’ll make use of this one, as such: ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken).

Once the SubscriptionClient object is built, we’ll simply call its Subscriptions property’s ListAsync method, which is implemented within the Microsoft.Azure.Management.Subscription.SubscriptionsOperationsExtensions class. The final method is below:

private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken)
{
    // Pack the credentials in the format required next
    ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
            
    // Get the subscriptions
    SubscriptionClient subscriptionClient = new SubscriptionClient(serviceClientCreds);
    IEnumerable<SubscriptionModel> allSubscriptions = await subscriptionClient.Subscriptions.ListAsync();
            
    // Extract the subscriptions' ids
    List<string> allSubscriptionIds = new List<string>();
    foreach (var subscription in allSubscriptions)
    {
        allSubscriptionIds.Add(subscription.SubscriptionId);
    }
    return allSubscriptionIds;
}

Namespaces required:

  • Microsoft.Azure.Management.Subscription
  • Microsoft.Azure.Management.Subscription.Models
  • Microsoft.Rest
  • System.Collections.Generic
  • System.Threading.Tasks

NuGet packages required:

  • Microsoft.Azure.Management.Subscription

Retrieve Azure Subscriptions Method #2: Microsoft.Azure.Management.ResourceManager.Fluent.ResourceManager

This method uses the Microsoft.Azure.Management.ResourceManager.Fluent.ResourceManager type, defined within the Microsoft.Azure.Management.ResourceManager.Fluent NuGet package. At the time of this writing the latest version has been issued less than a month ago (v1.36.1).

The Authenticate method (described here) takes either an Microsoft.Azure.Management.ResourceManager.Fluent.Authentication.AzureCredentials parameter or an Microsoft.Azure.Management.ResourceManager.Fluent.Core.RestClient. In our version of the method we’re passing an AzureCredentials object, which in turn is built using the access token obtained previously, as follows: AzureCredentials credentials = new AzureCredentials(new TokenCredentials(accessToken), null, string.Empty, AzureEnvironment.AzureGlobalCloud).

Eventually the Subscriptions property’s ListAsync method is invoked within the Microsoft.Azure.Management.ResourceManager.Fluent.SubscriptionsOperationsExtensions class. Final code below:

private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken)
{
    // Pack the credentials in the format required next
    AzureCredentials credentials = new AzureCredentials(new TokenCredentials(accessToken), null,
        string.Empty, AzureEnvironment.AzureGlobalCloud);

    // Get the subscriptions
    var allSubscriptions = await ResourceManager
        .Configure()
        .Authenticate(credentials)
        .Subscriptions
        .ListAsync();
    List<string> allSubscriptionIds = new List<string>();
    foreach (var subscription in allSubscriptions)
    {
        allSubscriptionIds.Add(subscription.SubscriptionId);
    }
    return allSubscriptionIds;
}

Namespaces required:

  • Microsoft.Azure.Management.ResourceManager.Fluent
  • Microsoft.Azure.Management.ResourceManager.Fluent.Authentication
  • Microsoft.Rest
  • System.Collections.Generic
  • System.Threading.Tasks

NuGet packages required:

  • Microsoft.Azure.Management.ResourceManager.Fluent

Retrieve Azure Subscriptions Method #3: Microsoft.Azure.Management.ManagementGroups.ManagementGroupsAPIClient

This method uses the Microsoft.Azure.Management.ManagementGroups.ManagementGroupsAPIClient type defined within the Microsoft.Azure.Management.ManagementGroups NuGet package. The last version of the NuGet package is more than 2 years old (v1.1.1-preview), and it’s also a preview one, so maybe best not to use this going forward.

“Packing” the credentials is done similar to method #1, whereby a ServiceClientCredentials object is instantiated based on the access token, and in turn the client used to retrieve the subscriptions is instantiated based on those credentials.

Eventually the Entities property’s ListWithHttpMessagesAsync is called within the Microsoft.Azure.Management.ManagementGroups.EntitiesOperations class. Code follows:

private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken)
{
    // Pack the credentials in the format required next
    ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
            
    // Get the subscriptions
    ManagementGroupsAPIClient mgmtGroupsAPIclient = new ManagementGroupsAPIClient(serviceClientCreds);
    var results = await mgmtGroupsAPIclient.Entities.ListWithHttpMessagesAsync();
            
    // Extract the subscriptions' ids
    List<string> allSubscriptionIds = new List<string>();
    foreach (var entity in results.Body)
    {
        if (entity.Type == "/subscriptions")
            allSubscriptionIds.Add(entity.Name);
    }
    return allSubscriptionIds;
}

Namespaces required:

  • Microsoft.Azure.Management.ManagementGroups
  • Microsoft.Rest
  • System.Collections.Generic
  • System.Threading.Tasks

NuGet packages required:

  • Microsoft.Azure.Management.ManagementGroups

Code for Retrieving the List of Azure Subscriptions

We’ve discussed various methods of authenticating and of retrieving the Azure subscriptions in a tenant, but let’s actually see a complete implementation. Something that could easily be placed in a scratchpad solution, or ran quickly using LINQPad.

All we need is to use any of the 2 “ExtractAccessTokenFromCredentials” methods defined previously, and any of the 3 “GetAllAzureSubscriptions” shown above, while installing the right NuGet packages and referencing the right namespaces, as indicated for each method in the previous sections.

Since there are multiple authentication mechanisms supported by each of the 2 client libraries discussed, multiple credential types can be used, such as Azure CLI, Visual Studio, Visual Studio Code, etc. For a complete list, see the “A Word About Credentials and Tokens” section. The gist is that the code presented so far can be used either from a local machine, or from within an Azure function running against its managed identity, without any changes.

Here’s a working example below. For obtaining the access token from the credential, we’re using the Azure Identity client library, as this is what Microsoft currently recommends. For retrieving the subscriptions, let’s choose the first method that relies on Microsoft.Azure.Management.Subscription.SubscriptionClient:

using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.Azure.Management.Subscription;
using Microsoft.Azure.Management.Subscription.Models;
using Microsoft.Rest;

namespace GetSubscriptions
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var tokenCredential = new DefaultAzureCredential();
            List<string> allSubscriptionIds = await GetAllAzureSubscriptions(await ExtractAccessTokenFromCredentials(tokenCredential));
        }

        private static async Task<string> ExtractAccessTokenFromCredentials(Azure.Core.TokenCredential tokenCredential)
        {
            // Extract the access token from the supplied credentials
            var accessToken = await tokenCredential.GetTokenAsync(
                new Azure.Core.TokenRequestContext(new string[] {"https://management.core.windows.net/.default"}),
                new System.Threading.CancellationToken());
            return accessToken.Token;
        }
        
        private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken)
        {
            // Pack the credentials in the format required next
            ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
            
            // Get the subscriptions
            SubscriptionClient subscriptionClient = new SubscriptionClient(serviceClientCreds);
            IEnumerable<SubscriptionModel> allSubscriptions = await subscriptionClient.Subscriptions.ListAsync();
            
            // Extract the subscriptions' ids
            List<string> allSubscriptionIds = new List<string>();
            foreach (var subscription in allSubscriptions)
            {
                allSubscriptionIds.Add(subscription.SubscriptionId);
            }
            return allSubscriptionIds;
        }
    }
}

NuGet packages required:

  • Azure.Identity
  • Microsoft.Azure.Management.Subscription

The ARG Query to Get VM Data

Now that we have the list of subscription ids, we can run our Azure Resource Graph (ARG) query that will retrieve the VM data for all the VMs that are hosted across those subscriptions. The query has been discussed in detail in a previous post, and can be found in listing 23 here. This will be reused verbatim in our C# code.

Unlike the Powershell code discussed in the same article linked above, we won’t have to concern ourselves with batching the subscriptions. Back then, we had to manually specify the subscription ids in batches in order to work around the 1,000-subscription limit per query in the Powershell code (if there are less than 1,000 subscriptions no “input” subscriptions have to be specified), but now – since the ARG client library used in C# needs the subscriptions to scope the query – we’ll simply provide all of them.

The ResourceGraphClient object that we’ll use to send our query will be instantiated based on a constructor that takes a ServiceClientCredentials parameter. How to convert from the string access token retrieved from the original credential to this type has been previously discussed here, and the same approach will be used again.

What we’ll have to handle is the pagination of the query’s result. The VM data will not come in one go, but it’ll be split – by default in chunks of data consisting of 1,000 rows each. The way to achieve this is to supply a token that indicates the next “page” of results is required, and send the query multiple times until there’s nothing returned anymore. As a side-note, this token-mechanism is employed by the AWS SDK for .NET, as it was detailed in this previous blog post.

Note that if you’re using your own custom ARG query, make sure you include at least one of the original ids in the result set, otherwise you’ll only get the first 1,000 results (because the response’s first SkipToken will be null, preventing subsequent iterations within the loop). Have a look at the Azure post referenced previously to read more about this. The query included in this post doesn’t have any issue, since it includes one of the original ids (the VM’s id).

Each query’s results are converted to a JSON array, “stitched” together as each page is read, and then the resulting object returned.

Method’s code is below. Note that – just as the previous methods presented – you can run this against any type of valid access token, regardless of which credential that token was extracted from. That means running locally on your machine, or as part of an Azure Function app will work just fine, as long as there’s a valid access token supplied.

private static async Task<Newtonsoft.Json.Linq.JArray> GetVMData(string accessToken,
	List<string> listOfAllAzureSubscriptionIds)
{
    ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
    ResourceGraphClient argClient = new ResourceGraphClient(serviceClientCreds);

    string strQuery = @"Resources
	    | where type =~ 'microsoft.compute/virtualmachines'
	    | project id, vmId = tolower(tostring(id)), vmName = name
	    | join (Resources
	        | where type =~ 'microsoft.network/networkinterfaces'
	        | mv-expand ipconfig=properties.ipConfigurations
	        | project vmId = tolower(tostring(properties.virtualMachine.id)), privateIp = ipconfig.properties.privateIPAddress, publicIpId = tostring(ipconfig.properties.publicIPAddress.id)
	        | join kind=leftouter (Resources
	            | where type =~ 'microsoft.network/publicipaddresses'
	            | project publicIpId = id, publicIp = properties.ipAddress
	        ) on publicIpId
	        | project-away publicIpId, publicIpId1
	        | summarize privateIps = make_list(privateIp), publicIps = make_list(publicIp) by vmId
	    ) on vmId
	    | project-away vmId, vmId1
	    | sort by vmName asc";
	
    QueryRequest request = new QueryRequest();
    request.Subscriptions = listOfAllAzureSubscriptionIds;
    request.Query = strQuery;
    request.Options = new QueryRequestOptions { ResultFormat = ResultFormat.ObjectArray };

    JArray allVmData = new JArray();
    QueryResponse response;
    do
    {
        response = await argClient.ResourcesAsync(request);
        request.Options.SkipToken = response.SkipToken;

	allVmData.Merge(response.Data);
    } while (!string.IsNullOrEmpty(response.SkipToken));

    return allVmData;
}

Namespaces required:

  • Microsoft.Azure.Management.ResourceGraph
  • Microsoft.Azure.Management.ResourceGraph.Models
  • Microsoft.Rest
  • Newtonsoft.Json.Linq
  • System.Collections.Generic
  • System.Threading.Tasks

NuGet packages required:

  • Microsoft.Azure.Management.ResourceGraph

Write Data to Storage Account

The last step is for the VM data that has been extracted using the ARG query to be stored within an Azure storage account, as a block blob within a specific container. How to store a file in Azure Blob storage was discussed in a previous post. Just like in that article, we’re going to use one of the target storage account’s access keys to authenticate, by hard-coding the corresponding connecting string in our code. The container name is also specified directly in code.

One question you might have is whether one could end up with a torn write when uploading the file to Azure Storage, eg if the function app starts writing a file, but another consumer was to start downloading the file at about the same time. It turns out that this isn’t the case, and as long as there’s a single writer (or multiple writers but using lock mechanisms) then everything is ok.

The method will take a single parameter – the JSON array that contains the VM data retrieved previously by the “GetVMData” method.

private static async Task WriteVmDataToStorageAccount(Newtonsoft.Json.Linq.JArray allVmData)
{
    string blockBlobName = "azureVMs.json";
    // Hard-coded connection string below. It would be ideal to read this from Azure Vault
    var connectionString = "<ConnectionString>";

    // Build the object that will connect to the container within our target storage account
    BlobContainerClient blobContainerClient = new BlobContainerClient(connectionString, "<ContainerName>");
    // Write to Azure Blob Storage
    BlobClient blobClient = blobContainerClient.GetBlobClient(blockBlobName);
    using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(allVmData.ToString())))
    {
        await blobClient.UploadAsync(memoryStream, true);
    }
}

Namespaces required:

  • Azure.Storage.Blobs
  • Newtonsoft.Json.Linq
  • System.Threading.Tasks

NuGet packages required:

  • Azure.Storage.Blobs
  • Newtonsoft.Json

Full Code for the Function App Backed by an Enterprise App

At this point, we have all the elements required to write the full code for the function that will run inside an Azure function app. For the first type of function app that we’ve discussed previously – namely backed by a dedicated enterprise app – an access token needs to be obtained. The credential we’ll be using will be the client secret that was generated for the “EnterpriseAppForRetrieveAzureVmData” enterprise app. Going with the Azure identity client library, we’re not going to use DefaultAzureCredential, but a type suited for storing this app’s secret: ClientSecretCredential. 3 pieces of information will go inside this credential: the tenant id, the enterprise app’s id, and the app’s client secret.

The workflow for the code is going to be: first, use the “ExtractAccessTokenFromCredentials” method to obtain an access token based on the ClientSecretCredential. Then, retrieve the list of subscriptions the dedicated enterprise app (whose credentials we’ve just used) has access to. Next, obtain the VM data by running the ARG query as part of the “GetVMData” method and passing it the list of subscriptions. Finally, store the JSON array with the VM data inside a block blob underneath our target storage account by calling the “WriteVmDataToStorageAccount” method.

As the code will be running inside a function app, we’re going to slightly modify the methods so that they support logging, by adding one extra parameter. We’re also going to sparingly write useful info to this logging channel, such as how many subscriptions were retrieved or how many VMs were identified in all the subscriptions.

The final code is below. Make sure to include your own strings where indicated. Build, then publish the Azure function app. If you’ve followed along in the initial section then you already have a publishing profile, ready to be used. Note that since an app’s client secret is leveraged, debugging locally is possible (for example using Visual Studio and the Azure Emulator).

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Management.ResourceGraph;
using Microsoft.Rest;
using Microsoft.Azure.Management.ResourceGraph.Models;
using Microsoft.Azure.Management.Subscription;
using Microsoft.Azure.Management.Subscription.Models;
using Newtonsoft.Json.Linq;
namespace RetrieveAzureVmData_wEntApp
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task Run([TimerTrigger("0 */15 * * * *")] TimerInfo myTimer, ILogger log)
{
log.LogInformation($"Starting function at: {DateTime.Now}");
string strTenant = "TENANT_ID";
string strClientId = "ENTERPRISE_APP_ID";
string strClientSecret = "APP_CLIENT_SECRET";
string accessToken = await ExtractAccessTokenFromCredentials(
new ClientSecretCredential(strTenant, strClientId, strClientSecret));
await WriteVmDataToStorageAccount(await GetVMData(accessToken, await GetAllAzureSubscriptions(accessToken, log), log));
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
}
private static async Task<string> ExtractAccessTokenFromCredentials(Azure.Core.TokenCredential tokenCredential)
{
// Extract the access token from the supplied credentials
var accessToken = await tokenCredential.GetTokenAsync(
new Azure.Core.TokenRequestContext(new string[] { "https://management.core.windows.net/.default" }),
new System.Threading.CancellationToken());
return accessToken.Token;
}
private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken, ILogger log)
{
// Pack the credentials in the format required next
ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
// Get the subscriptions
SubscriptionClient subscriptionClient = new SubscriptionClient(serviceClientCreds);
IEnumerable<SubscriptionModel> allSubscriptions = await subscriptionClient.Subscriptions.ListAsync();
log.LogInformation($"{allSubscriptions.Count()} subscriptions retrieved");
// Extract the subscriptions' ids
List<string> allSubscriptionIds = new List<string>();
foreach (var subscription in allSubscriptions)
{
allSubscriptionIds.Add(subscription.SubscriptionId);
}
return allSubscriptionIds;
}
private static async Task<Newtonsoft.Json.Linq.JArray> GetVMData(string accessToken,
List<string> listOfAllAzureSubscriptionIds, ILogger log)
{
ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
ResourceGraphClient argClient = new ResourceGraphClient(serviceClientCreds);
string strQuery = @"Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project id, vmId = tolower(tostring(id)), vmName = name
| join (Resources
| where type =~ 'microsoft.network/networkinterfaces'
| mv-expand ipconfig=properties.ipConfigurations
| project vmId = tolower(tostring(properties.virtualMachine.id)), privateIp = ipconfig.properties.privateIPAddress, publicIpId = tostring(ipconfig.properties.publicIPAddress.id)
| join kind=leftouter (Resources
| where type =~ 'microsoft.network/publicipaddresses'
| project publicIpId = id, publicIp = properties.ipAddress
) on publicIpId
| project-away publicIpId, publicIpId1
| summarize privateIps = make_list(privateIp), publicIps = make_list(publicIp) by vmId
) on vmId
| project-away vmId, vmId1
| sort by vmName asc";
QueryRequest request = new QueryRequest();
request.Subscriptions = listOfAllAzureSubscriptionIds;
request.Query = strQuery;
request.Options = new QueryRequestOptions { ResultFormat = ResultFormat.ObjectArray };
JArray allVmData = new JArray();
QueryResponse response;
do
{
response = null;
try
{
response = await argClient.ResourcesAsync(request);
request.Options.SkipToken = response.SkipToken;
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
log.LogInformation("No of records in current response page: " + response.Count);
allVmData.Merge(response.Data);
} while (!string.IsNullOrEmpty(response.SkipToken));
return allVmData;
}
private static async Task WriteVmDataToStorageAccount(Newtonsoft.Json.Linq.JArray allVmData)
{
string blockBlobName = "azureVMs.json";
// Hard-coded connection string below. It would be ideal to read this from Azure Vault
var connectionString = "STORAGE_ACCOUNT_CONNECTION_STRING";
// Build the object that will connect to the container within our target storage account
BlobContainerClient blobContainerClient = new BlobContainerClient(connectionString, "STORAGE_ACCOUNT_CONTAINER_NAME");
// Write to Azure Blob Storage
BlobClient blobClient = blobContainerClient.GetBlobClient(blockBlobName);
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(allVmData.ToString())))
{
await blobClient.UploadAsync(memoryStream, true);
}
}
}
}

NuGet packages required:

  • Azure.Identity
  • Azure.Storage.Blobs
  • Microsoft.Azure.Management.ResourceGraph
  • Microsoft.Azure.Management.Subscription

Full Code for the Function App Backed by its Managed Identity

For this type of function app, the code is almost identical to the one we’ve seen in the previous section, with only one noticeable change: we’re going to use a managed identity. Thus the only difference is using a ManagedIdentityCredential object in order to extract the function app’s managed identity. The rest of the code stays the same.

Keep in mind that this managed identity backed function can’t be debugged locally using the Azure Emulator, as it doesn’t support managed identities. If you attempt it, you’ll end up with System.Private.CoreLib: Exception while executing function: Function1. Azure.Identity: ManagedIdentityCredential authentication unavailable. No Managed Identity endpoint found. You’ll have to build and push the function app to the cloud in order to see it run successfully.

Full code follows:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Management.ResourceGraph;
using Microsoft.Rest;
using Microsoft.Azure.Management.ResourceGraph.Models;
using Microsoft.Azure.Management.Subscription;
using Microsoft.Azure.Management.Subscription.Models;
using Newtonsoft.Json.Linq;
namespace RetrieveAzureVmData_wFuncAppMI
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task Run([TimerTrigger("0 */15 * * * *")] TimerInfo myTimer, ILogger log)
{
log.LogInformation($"Starting function at: {DateTime.Now}");
string accessToken = await ExtractAccessTokenFromCredentials(
new ManagedIdentityCredential());
await WriteVmDataToStorageAccount(await GetVMData(accessToken, await GetAllAzureSubscriptions(accessToken, log), log));
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
}
private static async Task<string> ExtractAccessTokenFromCredentials(Azure.Core.TokenCredential tokenCredential)
{
// Extract the access token from the supplied credentials
var accessToken = await tokenCredential.GetTokenAsync(
new Azure.Core.TokenRequestContext(new string[] { "https://management.core.windows.net/.default" }),
new System.Threading.CancellationToken());
return accessToken.Token;
}
private static async Task<List<string>> GetAllAzureSubscriptions(string accessToken, ILogger log)
{
// Pack the credentials in the format required next
ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
// Get the subscriptions
SubscriptionClient subscriptionClient = new SubscriptionClient(serviceClientCreds);
IEnumerable<SubscriptionModel> allSubscriptions = await subscriptionClient.Subscriptions.ListAsync();
log.LogInformation($"{allSubscriptions.Count()} subscriptions retrieved");
// Extract the subscriptions' ids
List<string> allSubscriptionIds = new List<string>();
foreach (var subscription in allSubscriptions)
{
allSubscriptionIds.Add(subscription.SubscriptionId);
}
return allSubscriptionIds;
}
private static async Task<Newtonsoft.Json.Linq.JArray> GetVMData(string accessToken,
List<string> listOfAllAzureSubscriptionIds, ILogger log)
{
ServiceClientCredentials serviceClientCreds = new TokenCredentials(accessToken);
ResourceGraphClient argClient = new ResourceGraphClient(serviceClientCreds);
string strQuery = @"Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project id, vmId = tolower(tostring(id)), vmName = name
| join (Resources
| where type =~ 'microsoft.network/networkinterfaces'
| mv-expand ipconfig=properties.ipConfigurations
| project vmId = tolower(tostring(properties.virtualMachine.id)), privateIp = ipconfig.properties.privateIPAddress, publicIpId = tostring(ipconfig.properties.publicIPAddress.id)
| join kind=leftouter (Resources
| where type =~ 'microsoft.network/publicipaddresses'
| project publicIpId = id, publicIp = properties.ipAddress
) on publicIpId
| project-away publicIpId, publicIpId1
| summarize privateIps = make_list(privateIp), publicIps = make_list(publicIp) by vmId
) on vmId
| project-away vmId, vmId1
| sort by vmName asc";
QueryRequest request = new QueryRequest();
request.Subscriptions = listOfAllAzureSubscriptionIds;
request.Query = strQuery;
request.Options = new QueryRequestOptions { ResultFormat = ResultFormat.ObjectArray };
JArray allVmData = new JArray();
QueryResponse response;
do
{
response = null;
try
{
response = await argClient.ResourcesAsync(request);
request.Options.SkipToken = response.SkipToken;
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
log.LogInformation("No of records in current response page: " + response.Count);
allVmData.Merge(response.Data);
} while (!string.IsNullOrEmpty(response.SkipToken));
return allVmData;
}
private static async Task WriteVmDataToStorageAccount(Newtonsoft.Json.Linq.JArray allVmData)
{
string blockBlobName = "azureVMs.json";
// Hard-coded connection string below. It would be ideal to read this from Azure Vault
var connectionString = "STORAGE_ACCOUNT_CONNECTION_STRING";
// Build the object that will connect to the container within our target storage account
BlobContainerClient blobContainerClient = new BlobContainerClient(connectionString, "STORAGE_ACCOUNT_CONTAINER_NAME");
// Write to Azure Blob Storage
BlobClient blobClient = blobContainerClient.GetBlobClient(blockBlobName);
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(allVmData.ToString())))
{
await blobClient.UploadAsync(memoryStream, true);
}
}
}
}

NuGet packages required:

  • Azure.Identity
  • Azure.Storage.Blobs
  • Microsoft.Azure.Management.ResourceGraph
  • Microsoft.Azure.Management.Subscription

Q&A

Q: Can I test an Azure function app running a managed identity on my local machine?
A: No, at least as of Dec 2020. You have to publish the function to Azure first. See this section of the post

Q: Isn’t Managed Service Identity (MSI) the correct term, instead of Managed Identity?
A: As per this article https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview, Microsoft simply renamed the concept: “Managed identities for Azure resources is the new name for the service formerly known as Managed Service Identity (MSI).

Q: If I go to the “Management groups” blade in the Azure portal, I get No management groups to display. There’s a button “Start using management groups”, but that only prompts me to create a new management group, and can’t seem to be able to use an existing one. Where’s the Tenant Root management group, which is supposed to exist by default?
A: There are 3 pre-requisites for having the Tenant Root management group created described here https://docs.microsoft.com/en-us/azure/security-center/security-center-management-groups#introduction-to-management-groups, and one of them is to simply navigate to the “Management Groups”. As such, all you have to do is refresh the page, and the Tenant Root management group, with all the subscriptions, will be shown.

Q: I’ve enabled Azure management groups, and can now see the Tenant Root management group, but I’m getting a note stating You are registered as a directory admin but do not have the necessary permissions to access the root management group. What’s needed to get full access?
A: There’s a link that explains what’s needed, and in turn links to the article https://docs.microsoft.com/en-gb/azure/role-based-access-control/elevate-access-global-admin that shows how to self-elevate in order to get the required access.
If you’re wondering how come an Azure AD role (Global Administrator) gets access to Azure resources, this is because the procedure causes the user performing it to get the User Access Administrator role within Azure RBAC at root scope. It’s not as if the Global Administrator “crosses” the divide between Azure AD and Azure RBAC roles, but it’s a special loophole that Microsoft provided for this particular case only.

Q: Is it Azure AD that checks the permissions of the principals trying to access Azure resources (such as a virtual machine)?
A: It’s not Azure AD but Azure Resource Manager (ARM) – the deployment and management service for Azure – that does check the access rights. As per this article https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview#consistent-management-layer, “When a user sends a request from any of the Azure tools, APIs, or SDKs, Resource Manager receives the request. It authenticates and authorizes the request“. So for example, if a principal that’s granted the “Reader” Azure RBAC role (not the “Global Reader” Azure AD role) tries to read data about a virtual machine, it’s ARM that will check the principal’s permissions and grant it access accordingly.

Q: Are you sure an Azure RBAC is required to grant access to resources, and not an Azure AD role?
A: Yes. A hint that RBAC roles are not tied to Azure AD is how one creates a custom RBAC: it’s not by going within Azure AD, but by opening the respective resource (Azure subscription, management group, App Function, resource group, etc) as detailed here https://docs.microsoft.com/en-us/azure/role-based-access-control/custom-roles-portal#start-from-scratch.

Q: Does registering an enterprise app using the Azure portal result in a service principal to be automatically created?
A: Yes, as this is specifically detailed here https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals#service-principal-object

Q: Why are secrets defined in the enterprise app’s global application object (Enterprise Apps blade), instead of the service principal (App Registrations) level?
A: Not a verified answer, but my guess is that code using that specific secret should work against any instance of the application, regardless of the tenant where it’s installed, as opposed to only in a single Azure AD tenant. The service principal of the app is a representation of the latter, thus it won’t make much sense in storing the secrets at its level only.

Q: What do you mean by “Integrated Windows authentication” as the 4th authentication mechanism for AzureServiceTokenProvider?
A: In the current version of the type’s implementation here https://github.com/Azure/azure-sdk-for-net/blob/1ee01cbe78969514ce104502fc9fa627e2c5a293/sdk/mgmtcommon/AppAuthentication/Azure.Services.AppAuthentication/AzureServiceTokenProvider.cs#L109 this “source” is only defined for “FullNetFx” (which indicates .NET Framework, as opposed to .NET Core / .NET) and the referenced type WindowsAuthenticationAzureServiceTokenProvider has the following comment on its only public method: “Get access token by authenticating to Azure AD using Integrated Windows Authentication (IWA), when domain is synced with Azure AD tenant“. So you do need a domain-joined machine (whose domain is also being replicated with Azure AD Connect to Azure AD) to use this authentication mechanism. If you try to run from a non-domain joined machine, you’ll get `Exception Message: Tried to get token using Active Directory Integrated Authentication. Access token could not be acquired. Failed to get user name from the operating system.Inner Exception : No mapping between account names and security IDs was done.
Note also that for this 4th “source” there are also ADAL references within the code, which is something to be kept in mind as this is being phased out in favor of MSAL.

Q: I’m trying to use AzureServiceTokenProvider‘s GetAccessTokenAsync with the https://management.core.windows.net/.default URI, but I’m running into AADSTS500011: The resource principal named https://management.core.windows.net/.default was not found in the tenant named <tenant-id>. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant. What’s wrong?
A: The URI of the resource to be used for AzureServiceTokenProvider must not contain “./default”. Use https://management.core.windows.net and it should work just fine.

Q: I’m using the Azure Identity client library for authenticating and I can see that https://management.core.windows.net is working just fine. Shouldn’t this be used instead as resource id within the article?
A: Indeed, for some of the credential types it works fine, such as DefaultAzureCredential or AzureCliCredential, but for others you need to append /scope, as in https://management.core.windows.net/scope. The latter is the case for SharedTokenCacheCredential. Using the /.default scope however works across all the types of credentials that DefaultAzureCredential consists of, hence this was used in the article. Originally I’ve found this workaround here https://stackoverflow.com/questions/51781898/aadsts70011-the-provided-value-for-the-input-parameter-scope-is-not-valid. Update 1/7/2021: Why some of the credentials work with some ids but not others is very well explained here https://github.com/Azure/azure-sdk-for-net/issues/17696#issuecomment-756027485.

Q: I’m trying to use Azure.Identity‘s InteractiveBrowserCredential but instead of being authenticated, the browser sends back: Authentication failed. You can return to the application. Feel free to close this browser tab. Error details: error invalid_request error_description: AADSTS65002: Consent between first party application '04b07795-8ddb-461a-bbee-02f9e1bf7b46' and first party resource '797f4846-ba00-4fd7-ba43-dac1f8f63013' must be configured via preauthorization - applications owned and operated by Microsoft must get approval from the API owner before requesting tokens for that API. What’s wrong?
A: Make sure you’re using the correct scope within the TokenRequestContext constructor. As of now (Jan 2021) – and as per the previous question – some credential types work with certain ids passed, while others don’t. The recommended way is to always use the https://management.core.windows.net/.default string, which will also make the error observed go away. The cause of the issue comes from the fact that migration from Azure AD endpoint v1 to v2 isn’t yet completed for Azure, and as such various approaches are used in the underlying code to try a “common” approach. This is very well detailed here https://github.com/Azure/azure-sdk-for-net/issues/17696#issuecomment-756027485.

Q: Can I run the function apps listed in this article against a different tenant?
A: For the function app backed by an enterprise app, as long as the tenant id, enterprise app id, and the app’s client secret are valid, and the respective enterprise app has the required permissions, all is well and you’ll be able to access resources in a different tenant than the one where the function app is defined. For a function app backed by its managed identity, then this scenario will not work, as it’s explicitly listed in Microsoft’s own documentation https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/known-issues#can-i-use-a-managed-identity-to-access-a-resource-in-a-different-directorytenant.

Q: How can I specify the order in which AzureServiceTokenProvider attempts to get a token from its “sources”?
A: Use the constructor that takes a connection string as parameter, and use the values here https://docs.microsoft.com/en-us/dotnet/api/overview/azure/service-to-service-authentication#connection-string-support.

Q: For the first method of retrieving the Azure subscriptions (based on Microsoft.Azure.Management.Subscription.SubscriptionClient), it’s quite easy to spot that the type ultimately invoked is Microsoft.Azure.Management.Subscription.SubscriptionsOperationsExtensions (in JetBrains Rider, F12 with the cursor on the ListAsync method). But for the second method (based on Microsoft.Azure.Management.ResourceManager.Fluent.ResourceManager) I can’t really see that an extension method is used. How can this be observed?
A: If using JetBrains Rider, have a look at the short movie below. Use F11 to briefly step into the 2 first methods (Configure,Authenticate) and the Subscriptions property, followed by F10 to step out. F11 again to step into the ListAsync method, and once inside step into the ListAsync method found there.

Q: Can I assign different identities to 2 functions running under the same Azure function app?
A: Not to my knowledge. If you look at the “Identity” blade within the Azure portal, you’ll see that it’s at the level of the function app, not at the level of any of the functions within.

Q: What icon is the one shown for the function app backed by its managed identity, back in figure 6?
A: The icon matches the one used for App Service Environments.

Q: Is one guaranteed to target all subscriptions in an Azure tenant by scoping the permissions for an identity at the tenant root group level?
A: Yes. This is specifically indicated in the note here https://docs.microsoft.com/en-us/azure/governance/management-groups/overview#important-facts-about-the-root-management-group: “Any assignment of user access or policy assignment on the root management group applies to all resources within the directory“.

Visual Studio

Q: Why does creating a function app with Visual Studio always result in a timestamped name?
A: By default, the wizard that creates a new function app from within the “Publish” wizard timestamps the name of the function, corresponding to when the wizard was initiated. The name is however editable, so you can simply edit the name before creating the function app. Why is this timestamp being used? Most likely because the name of the function app has to be globally unique, in the sense that <FunctionAppName>.azurewebsites.net has to be unique, otherwise you’re not allowed to proceed (you also get this in the Azure portal wizard: The app name <FunctionAppName> is not available).

Q: Does creating a function app using Visual Studio result in its system-managed identity being enabled from the very start?
A: From my tests done in Dec 2020, it appears that this is indeed the case, and the function app created as such gets its system assigned managed identity enabled. Using the Azure portal to create a function app from scratch instead results in no such managed identity enabled.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s