How to list subscriptions with Microsoft.Azure.Res

2019-04-12 22:42发布

问题:

Context

My core goal is to write an Azure WebApps deployment tool in C#. The process will be roughly

  1. User logs in
  2. User selects subscription
  3. User selects or creates resource group
  4. User selects or creates storage for the web app
  5. User selects or creates web service plan
  6. User selects or creates web app
  7. Tool uploads the web app using Kudu to POST a zip

Since the last step can't be done in the portal, my idea was to do everything in a GUI tool.

I started out using Kudu's ARMClient.Authentication and Microsoft.Azure.ResourceManager 1.0.0-preview. However, when it comes to creating a storage account I get a permissions error (The subscription is not registered to use namespace Microsoft.Storage), so my plan B was to do the authentication myself following Brady Gaster's blog post.

The problem

I've set up an application as documented, and using its clientId and tenantId I'm able to log in and list tenants. But I can't list any subscriptions. (NB I've partly elided the clientId and tenantId in case there are security risks with giving them in full).

        string clientId = "f62903b9-ELIDED";
        string tenantId = "47b6e6c3-ELIDED";
        const string redirectUri = "urn:ietf:wg:oauth:2.0:oob";
        const string baseAuthUri = "https://login.microsoftonline.com/";
        const string resource = "https://management.core.windows.net/";

        var ctx = new AuthenticationContext(baseAuthUri + tenantId);
        var authResult = ctx.AcquireToken(resource, clientId, new Uri(redirectUri), PromptBehavior.Auto);
        var token = new TokenCredentials(authResult.AccessToken);
        var subClient = new SubscriptionClient(token);

        var tenants = await subClient.Tenants.ListAsync();
        foreach (var tenant in tenants) Console.WriteLine(tenant.TenantId);

        var subs = await subClient.Subscriptions.ListAsync();
        foreach (var sub in subs) Console.WriteLine(sub.DisplayName);

When I run this it prompts me to login, and lists the tenants corresponding to the subscriptions I own or co-administer. But it doesn't list a single subscription. If I change the IDs to the commonly used (I think officially for Powershell) values

        clientId = "1950a258-227b-4e31-a9cf-717495945fc2";
        tenantId = "common";

then it's the same.

What is the step I've missed in order to get a list of my subscriptions?

回答1:

A couple things you can look into...

1) the error you saw during creating of the storage account is likely due to the Resource Provider not being registered for use with the subscription. Any RP needs to be registered before use, some clients (Portal, PowerShell) will register the RP for you so you never notice it. See: https://msdn.microsoft.com/en-us/library/azure/dn790548.aspx - you should be able to do that from your code if the user has sufficient perms.

2) You may not be getting any subscriptions back because your endpoint (management.core.windows.net) is the endpoint for Azure Service Management not Azure Resource Manager (management.azure.com). If the subscription access is granted via AzureRM and RBAC, the old ASM apis will not see (i.e. have access to) those subscriptions.



回答2:

You need to iterate through the tenants, authenticate in tenant and get a subscription list for every tenant.

The following code will output the Subscriptions like Get-AzureRmSubscription powershell cmdlet does.

class Program
{
    private static string m_resource = "https://management.core.windows.net/";
    private static string m_clientId = "1950a258-227b-4e31-a9cf-717495945fc2"; // well-known client ID for Azure PowerShell
    private static string m_redirectURI = "urn:ietf:wg:oauth:2.0:oob"; // redirect URI for Azure PowerShell

    static void Main(string[] args)
    {
        try
        {
            var ctx = new AuthenticationContext("https://login.microsoftonline.com/common");
            // This will show the login window
            var mainAuthRes = ctx.AcquireToken(m_resource, m_clientId, new Uri(m_redirectURI), PromptBehavior.Always);
            var subscriptionCredentials = new TokenCloudCredentials(mainAuthRes.AccessToken);
            var cancelToken = new CancellationToken();
            using (var subscriptionClient = new SubscriptionClient(subscriptionCredentials))
            {
                var tenants = subscriptionClient.Tenants.ListAsync(cancelToken).Result;
                foreach (var tenantDescription in tenants.TenantIds)
                {
                    var tenantCtx = new AuthenticationContext("https://login.microsoftonline.com/" + tenantDescription.TenantId);
                    // This will NOT show the login window
                    var tenantAuthRes = tenantCtx.AcquireToken(
                        m_resource,
                        m_clientId,
                        new Uri(m_redirectURI),
                        PromptBehavior.Never,
                        new UserIdentifier(mainAuthRes.UserInfo.DisplayableId, UserIdentifierType.RequiredDisplayableId));
                    var tenantTokenCreds = new TokenCloudCredentials(tenantAuthRes.AccessToken);
                    using (var tenantSubscriptionClient = new SubscriptionClient(tenantTokenCreds))
                    {
                        var tenantSubscriptioins = tenantSubscriptionClient.Subscriptions.ListAsync(cancelToken).Result;
                        foreach (var sub in tenantSubscriptioins.Subscriptions)
                        {
                            Console.WriteLine($"SubscriptionName : {sub.DisplayName}");
                            Console.WriteLine($"SubscriptionId   : {sub.SubscriptionId}");
                            Console.WriteLine($"TenantId         : {tenantDescription.TenantId}");
                            Console.WriteLine($"State            : {sub.State}");
                            Console.WriteLine();
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
        finally
        {
            Console.WriteLine("press something");
            Console.ReadLine();
        }
    }
}