Azure Service Principals In Depth

Page content
Keys laid out on the table

An application’s service principal holding permission keys to the power of Azure.

What is a Service Principal?

From the official documentation:

An Azure service principal is an identity created for use with applications, hosted services, and automated tools to access Azure resources. This access is restricted by the roles assigned to the service principal, giving you control over which resources can be accessed and at which level. For security reasons, it’s always recommended to use service principals with automated tools rather than allowing them to log in with a user identity.

In my own words, a service principal is an identity that represents an application for either (1) authentication and authorization with Azure resources or (2) authentication with other services that integrate with Azure AD.

Service principals are a specific type of security principal, which are entities that can be authenticated with Azure.

It is an identity

A service principal is essentially an identity which represents a non-human users, analogous to email for human users.

It is for applications, hosted services, and automated tools

A service principal is meant for use by non-human users (applications, microservices, functions, scripts, etc.). It should not be used by human users to authenticate and represent themselves.

It only has access to Azure roles assigned to it

A service principal can be assigned suitable Azure roles at different hierarchical levels, e.g. subscription, management group, resource. This allows service principals to abide by the principle of least privilege.

Pedantically, there are actually a few types of service principals:

  1. Application
  2. Managed identity
  3. Legacy

This rest of the blog post only concerns with the Application service principal type. Generally, when the “service principal” term is used, it usually refers to the Application service principal type.

Create a Service Principal

Just as we need to authenticate ourselves with an SMTP server to access our email, service principals need to authenticate with Azure to gain authorization to specific roles and permissions in Azure.

There are two ways to do this:

Password-based authentication

Using this mode, a random password is created by Azure for you. Optionally, you may specify a resource name for the service principal. You can set the role assignment at time of creation, or do it later.

# Create a service principal with required parameter

az ad sp create-for-rbac --scopes /subscriptions/<SUBSCRIPTION_ID>

# Create a service principal for a resource group using a preferred name and role

az ad sp create-for-rbac --name <SERVICE_PRINCIPAL_NAME> \
                         --role reader \
                         --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>

The service principal password is only shown to you at this time. Store it somewhere safe. If you lose it, you can reset its credentials like follows:

az ad sp credential reset --name <SERVICE_PRINCIPAL_NAME>

Certificate-based authentication

Using this mode, you will authenticate with Azure using a private key certificate.

The simplest way to get started is to get Azure to generate a self-signed certificate for you:

az ad sp create-for-rbac --name <SERVICE_PRINCIPAL_NAME> \
                         --role <ROLE_NAME> \
                         --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \
                         --create-cert

You can also generate your own private key certificate (PEM, CER, or DER) and provide it to Azure to create the service principal. The invoker of this service principal will also need the private key as proof of authentication to Azure.

az ad sp create-for-rbac --name <SERVICE_PRINCIPAL_NAME> \
                         --role <ROLE_NAME> \
                         --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \
                         --cert "-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----"

Or with a path to the certificate:

az ad sp create-for-rbac --name <SERVICE_PRINCIPAL_NAME> \
                         --role <ROLE_NAME> \
                         --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \
                         --cert @</path/to/cert.pem>

You can also upload or generate a certificate in Azure Key Vault and provide it to the service principal via the --cert and --keyvault parameter:

az ad sp create-for-rbac --name <SERVICE_PRINCIPAL_NAME> \
                         --role <ROLE_NAME> \
                         --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \
                         --cert <CERT_NAME_IN_KEYVAULT> \
                         --keyvault <KEYVAULT_NAME>

An example console output for service principal creation is as follows:

Creating a role assignment under the scopes of "/subscriptions/<SUBSCRIPTION_ID>"

Please copy C:\path\to\new\file.pem to a safe place.

When you run 'az login', provide the file path in the --password parameter

{
  "appId": "[APP_ID]",
  "displayName": "[SERVICE_PRINCIPAL_NAME]",
  "fileWithCertAndPrivateKey": "C:\\path\\to\\new\\file.pem",
  "name": "http://[SERVICE_PRINCIPAL_NAME]",
  "password": null,
  "tenant": "[TENANT_ID]"
}

Here is a concrete example output when creating a password-based service principal:

{
  "appId": "559513bd-0c19-4c1a-87cd-851a26afd5fc",
  "displayName": "myAKSClusterServicePrincipal",
  "name": "http://myAKSClusterServicePrincipal",
  "password": "e763725a-5eee-40e8-a466-dc88d980f415",
  "tenant": "72f988bf-86f1-41af-91ab-2d7cd011db48"
}

Do note on the APP_ID value, which we will need for role assignment next. This value is also commonly known as the CLIENT_ID, with the corresponding password known as the CLIENT_SECRET.

Manage Service Principal Roles

Creating a service principal by itself doesn’t give you any authorization to do anything in Azure. You need to first assign it some roles.

Assign a role:

az role assignment create --assignee <APP_ID> \
                          --role Reader \
                          --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \

Delete a role:

az role assignment delete --assignee <APP_ID> \
                          --role Contributor \
                          --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME> \

To view the currently assigned roles:

az role assignment list --assignee <APP_ID>

Signing in with a Service Principal

Essentially, you will need either a password or a private key to authenticate with a service principal.

You may log in using the service principal to try it out:

az login --service-principal --username <APP_ID> --password <PASSWORD> --tenant <TENANT_ID>

To log in using a certificate (private key):

az login --service-principal --username <APP_ID> --tenant <TENANT_ID> --password /path/to/cert

Subsequently you (or the CLI script you are running) are then able to create and manage resources as this service principal.

Usually though, instead of using the CLI, you will want to authenticate the service principal using the Azure SDK for your application’s programming language.

For example, using the Azure SDK for Java, you can do the same thing as above in Java code:

/**
*  Authenticate with a client certificate.
*/
ClientCertificateCredential clientCertificateCredential = new ClientCertificateCredentialBuilder()
  .clientId("<CLIENT_ID>")
  .pemCertificate("<path to PEM certificate>")
  // Choose between either a PEM certificate or a PFX certificate.
  //.pfxCertificate("<path to PFX certificate>", "<PFX_CERTIFICATE_PASSWORD>")
  .tenantId("<TENANT_ID>")
  .build();

Or if your secret is stored in Azure Key Vault:

/**
 *  Authenticate with client secret.
 */
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
  .clientId("<CLIENT_ID>")
  .clientSecret("<CLIENT_SECRET>")
  .tenantId("<TENANT_ID>")
  .build();

// Azure SDK client builders accept the credential as a parameter.
SecretClient client = new SecretClientBuilder()
  .vaultUrl("https://<your Key Vault name>.vault.azure.net")
  .credential(clientSecretCredential)
  .buildClient();

But maybe YAGNI? (You Ain’t Gonna Need It)

If your application is hosted within say, an Azure VM, you should actually not use a service principal (application type) but instead try to use the following secretless methods (out of scope of this blog post):

Using the above two methods, the application does not need to manage and secure the password or certificate, which is definitely a boost for security. So first consider if you can do away with service principals altogether.

For applications hosted in Azure Kubernetes Service (AKS), then you’ll probably need a service principal to represent the pods or containers, because there is no Azure identity representation at the pod or container level.

That’s it for this blog post. Happy to hear your thoughts via jon@joncloudgeek.com.