0 Comments

So far in my tutorial series on the Azure CLI, I’ve been assuming that you are logging in directly with the az login command. This will direct you to visit a webpage, enter a code and log in to Azure yourself. Now that’s fine if you’re running commands or scripts directly from a command prompt yourself, but what if you want to automate a task? Say you want to set up a scheduled task to shut down a Virtual Machine at midnight, or want to automate a resource management task from an Azure Function.

What you need is a service principal. A service principal is an identity your application can use to log in and access Azure resources. In this post I’ll show you how we can create a service principal from the CLI which can be used not only to run CLI commands from an automated process, but to use the Azure SDK for your programming language of choice (e.g. C#, Python, Java, Ruby, Node.js etc).

Create an Azure Active Directory application

Before you create a service principal, you need to create an “application” in Azure Active Directory. You can think of this as an identity for the application that needs access to your Azure resources.

You can create an AD Application with the Azure CLI, but do make sure you’ve selected the right subscription with az account set first, so that the application ends up in the correct Active Directory.

Here we select the subscription, and then use az ad app create to create an application. The only parameter that really matters is --display-name, but we are required to provide a --homepage and --identifier-uris parameter, so we just make up suitable values for them (they don’t have to be reachable URIs).

# select correct subscription
az account set -s "my subscription name"

# a name for our azure ad app
appName="ServicePrincipalDemo1"

# create an Azure AD app
az ad app create \
    --display-name $appName \
    --homepage "http://localhost/$appName" \
    --identifier-uris http://localhost/$appName

Now we need the app id from the output of az ad app create. You can get it again by searching for the AD app with the display name you chose like this:

# get the app id
appId=$(az ad app list --display-name $appName --query [].appId -o tsv)

Create the Service Principal

Now that we have an AD application, we can create our service principal with az ad sp create-for-rbac (RBAC stands for role based access control).

We need to supply an application id and password, so we could create it like this:

# choose a password for our service principal
spPassword="[email protected]!"

# create a service principal
az ad sp create-for-rbac --name $appId --password $spPassword 

This would create a service principal that has contributor access to the currently selected subscription.

However, its a good idea to restrict permission to only allow access to the minimal set of resources that the target application needs to use. So you can set the --role to reader instead of contributor if you only need read access. Or you can use the --scope argument to limit the scope to only allow management of a single resource group.

So here’s how we could create a service principal that has contributor level access to just a single resource group (the resource group should already exist).

spPassword="MyServicePrincipal1!"
subscriptionId=$(az account show --query id -o tsv)
resourceGroup="MyResourceGroup" # must exist

az ad sp create-for-rbac --name $appId --password $spPassword \
                --role contributor \
                --scopes /subscriptions/$subscriptionId/resourceGroups/$resourceGroup

Get the Service Principal App Id

Once you've created your service principal, you will need to get its app id (not to be confused with the app id of the AD application). You can get this from the output of the az ad sp create-for-rbac command, or you can get hold of it again by searching for service principals whose display name is the app id of the AD application like this:

# get the app id of the service principal
servicePrincipalAppId=$(az ad sp list --display-name $appId --query "[].appId" -o tsv)

Configuring Access

If you need to do anything more complex with the roles and scopes for your service principal, then the az role assignment group of commands will help you do this. If you created your service principal without specifying a role and scope, here’s how to delete the existing one, and add a new one:

# view the default role assignment (it will be contributor access to the whole subscription)
az role assignment list --assignee $servicePrincipalAppId

# get the id of that default assignment
roleId=$(az role assignment list --assignee $servicePrincipalAppId --query "[].id" -o tsv)

# delete that role assignment
az role assignment delete --ids $roleId

# get our subscriptionId
subscriptionId=$(az account show --query id -o tsv)

# the resource group we will allow ourselves to access (must exist)
resourceGroup="MyResourceGroup"

# grant contributor access just to this resource group only
az role assignment create --assignee $servicePrincipalAppId \
        --role "contributor" \
        --scope "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup"

# n.b to see this assignment in the output of az role assignment list, you neeed the --all flag:
az role assignment list --assignee $servicePrincipalAppId --all

Testing it Out

So we’ve created a service principal, but does it work? Well, before we can test it out, we need one more bit of information: we need the tenant id associated with our account. We can get that like this:

# get the tenant id
tenantId=$(az account show --query tenantId -o tsv)

And now let’s log out with az logout, and then log back in using our service principal, by using az login --service-principal and passing in the app id of the service principal, the password we chose, and the tenant id:

# now let's logout
az logout

# and log back in with the service principal
az login --service-principal -u $servicePrincipalAppId \
         --password $spPassword --tenant $tenantId

The proof that this all worked is in what we can do. If I ask to list all resource groups, I should just see the one I’m allowed access to:

# what groups can we see? should be just one:
az group list -o table

And if I try to create a new resource group, that will fail because I don’t have permission

# can we create a new resource group? should be denied:
az group create -n NotAllowed -l westeurope

However, if I want to create a new VM inside the one resource group I have been granted contributor access to, that will be allowed:

# but we should be able to create a VM:
vmName="ExampleVm"
adminPassword="[email protected]@ssw0rd!"

az vm create \
    --resource-group $resourceGroup \
    --name $vmName \
    --image win2016datacenter \
    --admin-username azureuser \
    --admin-password $adminPassword \
    --size Basic_A1 \
    --use-unmanaged-disk \
    --storage-sku Standard_LRS

# check its running state
az vm show -d -g $resourceGroup -n $vmName --query "powerState" -o tsv

Using the Service Principal from C#

The service principal can be used for more than just logging into the Azure CLI. It can be used alongside the Azure SDK for .NET (or indeed with the SDK for your favourite language).

For example, here’s the code for a simple Azure Function that runs on a schedule at midnight every night. It uses the service principal login details (read from app settings), then attempts to find a Virtual Machine with a specific name in a specific resource group (obviously this should be the resource group we granted contributor access to). And then if the machine is not in a stopped deallocated state, it attempts to put it into that state in order to save money.

#r "System.Configuration"
#r "System.Security"

using System;
using System.Configuration;
using Microsoft.Azure.Management.AppService.Fluent;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
using Microsoft.Azure.Management.ResourceManager.Fluent;

public static void Run(TimerInfo myTimer, TraceWriter log)
{
    log.Info($"VM Shutdown function executed at : {DateTime.Now}");
    var sp = new ServicePrincipalLoginInformation();
    sp.ClientId = ConfigurationManager.AppSettings["SERVICE_PRINCIPAL"];
    sp.ClientSecret = ConfigurationManager.AppSettings["SERVICE_PRINCIPAL_SECRET"];
    var tenantId = ConfigurationManager.AppSettings["TENANT_ID"];
    var resourceGroupName = ConfigurationManager.AppSettings["VM_RESOURCE_GROUP"];
    var virtualMachineName = ConfigurationManager.AppSettings["VM_NAME"];
    
    var creds = new AzureCredentials(sp, tenantId, AzureEnvironment.AzureGlobalCloud);
    IAzure azure = Azure.Authenticate(creds).WithDefaultSubscription();
    
    var vm = azure.VirtualMachines.GetByResourceGroup(resourceGroupName,virtualMachineName);
    log.Info($"{vm.Name} is {vm.PowerState}");
    
    if (vm.PowerState.Value != "PowerState/deallocated")
    {
        log.Info("Shutting it down:");
        vm.Deallocate();
        log.Info("Done");
    }
}

Learning More

Hopefully that’s enough to get you started creating and using your own service principals. For more details on different ways to create a service principal, check out this tutorial on the official docs site.

For previous entries in my Azure CLI tutorial series:

Want to learn more about the Azure CLI? Be sure to check out my Pluralsight course Azure CLI: Getting Started.
Vote on HN
comments powered by Disqus