Creating a Service Principal with the Azure CLI
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](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="My5erv1c3Pr1ncip@l1!"
# 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="ARe@11y5ecur3P@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:
- Deploying ARM Templates with the Azure CLI
- Backup and Restore a SQL Database with the Azure CLI
- Connect a Web App to a SQL Database with the Azure CLI
- Manage Storage Queues with the Azure CLI
- Manage Blob Storage with the Azure CLI
- Create and Configure a VM with the Azure CLI
- Automate away your Azure Portal usage with the Azure CLI
- Using Queries with the Azure CLI
- Introducing the Azure CLI
Comments
Great article. Well done.
Bruno TerkalyExcellent article .. thx for sharing
Edwin Contreras