Thursday, 5 July 2018

How to call the Azure Resource Manager REST API from C#

Calling the Azure Resource Manager REST API from C# is pretty straightforward. You just add an access token to the request header. Getting that access token though, especially for the first time, does involve a few steps.
The purpose of this article is to put all the steps in one place in order to show how to write a C# program that makes REST calls to view and create basic Azure infrastructure resources.
Note: There are two deployment models currently available in Azure. The Cloud Services model, and the more recent Azure Resource Manager model, also referred to as ARM. Go here to learn about the differences between the two models: Understanding Resource Manager deployment and classic deployment. This article is all about programming to the ARM model in REST. There is also a managed library available for Azure Resource Manager. I’m picking REST here so I can make some calls which are not in the managed library yet, and also use the same calls from other programming languages.
High Level Steps
Let’s divide the beginning-to-end REST app development process into 7 simple steps:
The first four steps are one-time application setup steps – creating and registering an application with Azure, granting permissions and getting the details you need. There is a great write-up of these steps here: Authenticating a Service Principal with Azure Resource Manager.
1. Create a new Azure Active Directory application. This creates a definition of your app and registers it with Azure.
2. Create a Service Principal. This is an Active Directory “user” which represents an automated application. For a more detailed explanation of what Application and Service Principal objects are, see: Application Objects and Service Principal Objects.
3. Grant the Service Principal permissions (e.g. Contributor) on your subscription. Don’t create “Reader” or you won’t be able to do much, unless that is your intention.
4. Create a credential object for your application, and use that to get the tenant Id for the authentication context. For more information see What is an Azure AD Tenant?
Steps 5 to 7 cover configuring Visual Studio and the basic authentication your app code does. One of these steps: Step 6 – getting an access token, is also partially covered in the aforementioned authenticating a service principal write-up.
5. Install the Active Directory Authentication package in Visual Studio.
6. Get an access token for the app in your C# program.
7. Use the token to authorize a REST call.
Detailed Steps
If you’ve already completed, or are familiar with any of these steps, skips to the ones that interest you.
Pre-requisites
The one-time application setup steps detailed here make use of Azure command line tools. You will need the latest version of Azure PowerShell or CLI. In this case I used Azure PowerShell 1.0+, but both PowerShell and CLI instructions can be found in the Authenticating a Service Principal with Azure Resource Manager link. The best way to install Azure PowerShell (if you ask me, which you didn’t) is to install the Azure module from the PowerShell Gallery.
The basic PowerShell commands to install Azure PowerShell and login to your account are:
Set-ExecutionPolicy RemoteSigned
Install-Module AzureRM
Install-AzureRM
Login-AzureRmAccount
Note: the commands would be different if you use an older (pre-1.0) version of Azure PowerShell.
You will also need Visual Studio. I’ve tested with 2015 Professional.
1. Create a New Azure Active Directory Application
Make sure you’ve selected the Azure subscription you want to use (if you have more than one):
select-azurermsubscription –SubscriptionName
When you register an app give it a display name, a home page (which doesn’t need to exist) and a password, e.g.
$azureAdApplication = New-AzureRmADApplication -DisplayName “my ARM App” -HomePage “https://msftstack.wordpress.com/” -IdentifierUris “https://msftstack.wordpress.com/” -Password “xxxxxxx”
Make a note of $azureAdApplication.ApplicationId – your C# app will use it.
2. Create a Service Principal
Creating an application generates an application ID, which you then use to create the Service Principal:
New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId
3. Grant the Service Principal permissions
Now grant the permissions your app instance will have. For example, if you assign a role of “Reader”, your app would not be able to delete or create Azure resources. You can find a lost of role definitions to choose from here: RBAC: Built in Roles.
This example assigns basic read-write permissions using the Contributor role:
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $azureAdApplication.ApplicationId
4. Create a credential object for your application
Get subscription in which role assignment was created. It will be used to get tenant id:
$subscription = Get-AzureRmSubscription –SubscriptionName
$creds = Get-Credential
A prompt will appear. Here you are logging on on behalf of the application, so when it asks for the user name, enter the ApplicationId, and use the password you chose when creating the application.
Now get the tenantId for the authentication context:
Login-AzureRmAccount -Credential $creds -ServicePrincipal -Tenant $subscription.TenantId
You’ll get a list like this back:
Environment : AzureCloud
Account :
TenantId :
SubscriptionId :
The AccountId (AKA ApplicationId) and TenantId, along with your application password, are the values your program will need to authenticate.
5. Install the Active directory authentication package in Visual Studio
The C# app will be creating an AuthenticationContext. For those calls to resolve you need to install the Microsoft.IdentityModel.Clients.ActiveDirectory Nuget package in Visual Studio (reference: http://www.cloudidentity.com/blog/2013/09/12/active-directory-authentication-library-adal-v1-for-net-general-availability/).
6. Get an access token for the app in your C# program
In your C# code, add an assembly reference for the ActiveDirectory identity model:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
And add a method to get an access token using your tenantId, applicationId and app password:
public string GetAccessToken()
{
    string authContextURL = "https://login.windows.net/" + tenantId;
    var authenticationContext = new AuthenticationContext(authContextURL);
    var credential = new ClientCredential(clientId: , clientSecret: );
    var result = authenticationContext.AcquireToken(resource: "https://management.azure.com/", clientCredential: credential);

    if (result == null)
    {
        throw new InvalidOperationException("Failed to obtain the JWT token");
    }

    string token = result.AccessToken;
    return token;
}

Don’t hardcode these values in a real program. For my demo app I add a setup tab where I can save subscription specific values as application metadata.
7. Use the token to authorize a REST call
Now everything is set to make REST calls defined in the Azure Resource Manager REST API.
For example to do a GET with a call to list resource groups in a subscription your URI would look like this: https://management.azure.com/subscriptions//resourceGroups?api-version=2015-01-01:
Here’s a generic method to do a GET call and return output as a string. Note how the authentication token is added as a header:
private string doGET(string URI, String token)
{
    Uri uri = new Uri(String.Format(URI));

    // Create the request
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
    httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
    httpWebRequest.ContentType = "application/json";
    httpWebRequest.Method = "GET";

    // Get the response
    HttpWebResponse httpResponse = null;
    try
    {
        httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error from : " + uri + ": " + ex.Message,
                        "HttpWebResponse exception", MessageBoxButton.OK, MessageBoxImage.Error);
        return null;
    }

    string result = null;
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        result = streamReader.ReadToEnd();
    }

    return result;
}

or to do a PUT:
private string doPUT(string URI, string body, String token)
{
    Uri uri = new Uri(String.Format(URI));

    // Create the request
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
    httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
    httpWebRequest.ContentType = "application/json";
    httpWebRequest.Method = "PUT";

    try
    {
        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(body);
            streamWriter.Flush();
            streamWriter.Close();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error setting up stream writer: " + ex.Message,
            "GetRequestStream exception", MessageBoxButton.OK, MessageBoxImage.Error);
    }

    // Get the response
    HttpWebResponse httpResponse = null;
    try
    {
        httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error from : " + uri + ": " + ex.Message,
                        "HttpWebResponse exception", MessageBoxButton.OK, MessageBoxImage.Error);
        return null;
    }

    string result = null;
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        result = streamReader.ReadToEnd();
    }
    return result;
}


Putting it all together
To put these concepts together here’s a demo app which makes basic ARM calls. In this example an authentication token is requested every time there’s a GET/PUT/DELETE button push, as the token has a timeout. There are also some arbitrary buttons to build a URI and add the API version. Arbitrary because I happen to be working on Azure Virtual Machine Scale Sets at the moment, so added a button to list them, and will probably add other shortcut buttons as needed: https://github.com/gbowerman/armapp/

armapp

No comments:

Post a Comment