October 16, 2024 Marco

Securing service principals in Azure

With today’s rapid development, especially in the cloud environment, automation, and application-based access have become indispensable. Where things are automated, we usually need impersonal identities, aka service principals, to carry out these tasks. Service principals play a vital role in this process, providing applications and scripts with the ability to authenticate and access Azure resources and API’s seamlessly. However, their autonomous nature and the significant permissions they often possess make them inherently risky. Despite their importance, service principals are frequently overlooked when it comes to security measures.

Given the nature of service principals, it’s important to remember:

  • They perform non-interactive sign-ins and cannot use MFA
  • Higher privileges are usually assigned to fulfill the tasks, i.e IaC deployments
  • Credentials or authentication information must be stored somewhere for the running application to retrieve
  • Logging and monitoring can be challenging due to the amount and different properties

In this blog post, we look at how we can effectively protect service principals and their access.

General misconception

It is important to know that Conditional Access policies do not cover service principal sign-ins, i.e. sign-ins from Entra ID app registrations. Therefore, if you use an app registration with a secret, it’s essentially Basic Authentication without any restrictions!

Security measures

Use managed identities wherever you can

Managed identities are identity objects for which Microsoft handles credential rotation and protection. Credentials are inaccessible to users, even those in privileged roles, eliminating the risk of accidental leaks in code. Managed identities are always linked to an Azure resource. For example, a managed identity can be assigned to an Azure VM, allowing scripts on that VM to authenticate as the managed identity, but only from that specific VM. System-assigned managed identities share the lifecycle of their resources, meaning that when a resource is deleted, the managed identity is automatically deleted as well.

Since managed identities are a concept of Azure itself, you cannot use them outside of Azure, i.e. on your local machine. For local development, you can use a combination of app registrations and managed identities with some environment checks. However, authentication cannot occur outside of an Azure resource.

Managed identities are Azure resources, unlike app registrations, which belong to Entra ID. This enables more precise RBAC on the identity resource.
Attention: Since the managed identity is tied to a resource, it’s crucial to lock down the permissions on that resource as well as the managed identity resource.

You can learn more about managed identities in my other blog post: What are Managed Identities?

Use certificates instead of secrets

In order to authenticate using an app registration, you need the client ID (sometimes referred to as application ID) and either a secret, certificate or federated credential (we’ll get to that in the following section). While using a secret might be tempting due to its simplicity, secrets are as unsafe as it gets. Keep in mind that, by default, app registrations are not protected by Conditional Access. This is like logging into your applications and services with just a username and password, without MFA or additional security measures.

A more secure approach is to use certificates instead of secrets. Certificates, or cryptographic keys, are more resistant to tampering or interception and have expiration dates. To authenticate, you need to have the certificate. Obviously, the certificates need to be stored securely on the source, like an on-premises VM or application. You can either upload a certificate directly to the app registration or automatically generate and store one during the creation of the app registration using an existing Key Vault.

Use federated credentials

Federated credentials rely on the concept of federation and trust (yes, trust), which removes the need for manual credential management and rotation, and eliminates the risk of expired certificates and secrets. A federation is always established between two IdP’s, the requesting application’s IdP (i.e. GitHub) and the user’s IdP (i.e. Entra ID). You cannot use federated credentials when directly authenticating against Entra ID, like from a script. They can be configured both on user-assigned managed identities and Entra ID app registrations. This allows the use of a user-assigned managed identity outside of Azure! It is therefore even more important to harden the RBAC permissions on user-assigned managed identity resources and prevent the deployment of such on scopes where permissions are inherited unintentionally.

How it works

The federated identity credential is used to indicate which token from the external identity provider (IdP) an application can trust. Many authentication concepts rely on trust, i.e. SAML or OpenID Connect (OIDC). We establish trust by configuring certain trust information from the relying party on the IdP and vice versa, usually an Issuer, Subject Identifier, and Audience. To keep things secure, each app needs a unique combo of the issuer and subject identifier claim. When an app asks Entra ID for an access token using an external token provided by the app’s own IdP, Entra ID checks if the issuer and subject identifier in the external token match the values configured in the federated credential. If they match, Entra ID assumes that the requesting app is authenticated against its own IdP, which we trust. Entra ID issues the app an access token, allowing it to do its job. This flow diagram should explain it a bit better:

  1. The external workload (such as a GitHub Actions workflow) requests a token from the external IdP (such as GitHub).
  2. The external IdP issues a token to the external workload.
  3. The external workload (the sign-in action in a GitHub workflow, for example) sends the token to Entra ID and requests an access token.
  4. Entra ID checks the trust relationship on the user-assigned managed identity or app registration. It validates the external token against the OpenID Connect (OIDC) issuer URL on the external IdP.
  5. When the checks are satisfied, the Entra ID issues an access token to the external workload.
  6. The external workload accesses the protected resources using the access token from the Entra ID. A GitHub Actions workflow, for example, uses the access token to publish a web app to Azure App Service.

Reference: Workload identity federation – Microsoft Entra Workload ID | Microsoft Learn

Use Conditional Access for Workload Identities

Conditional Access for Workload Identities can be seen as an add-on to the regular Conditional Access capabilities and is licensed separately. A Workload Identities Premium license must exist in the tenant for the number of workload identities you want to protect (they are not included in the Entra ID P1 or P2 licenses). Only app registrations/enterprise applications are covered, managed identities are not covered.

With Conditional Access for Workload Identities, it is possible to allow or block sign-ins from app registrations/enterprise apps based on:

  • Trusted locations (IP ranges or countries)
  • Service principal risk by leveraging Microsoft Entra ID Protection
  • In combination with authentication contexts (Public Preview)
  • Strictly enforce location policies using Continuous Access Evaluation (CAE) for Workload Identities (Public Preview)

Some things to keep in mind:

  • Managed identities are not covered in those policies
  • Your tenant may have numerous workload identities, but only a fraction is likely used as service principals with specific permissions. Identifying and protecting these service principals would be a significant improvement
  • Use the sign-in logs to analyze and evaluate the sign-in behavior of existing workload identities
  • At of the time of writing, you can enable a free Workload Identities Premium trial to test the features, providing you with 200 licenses for 90 days

Conclusions

As usual, different architectures have different technical requirements which require different technical solutions. As a rule of thumb, I’d recommend the following:

  1. If a non-interactive service principal authentication happens from an Azure resource, always use managed identities if possible. Keep in mind that the managed identity resource or the resource where a managed identity is attached is now the security scope for which access must be restricted.
  2. For tools where federation is supported, i.e. GitHub or Azure DevOps, use federated credentials on a user-assigned managed identity.
  3. For non-interactive service principal authentications from outside of Azure, i.e. an on-premises automation engine, use app registrations with certificates.
  4.  If a source application does not support certificates and you must use secrets, use a looooong and complex secret and store it securely. Also, make sure to rotate it in a timely manner.
  5. For both 3 and 4, analyze, evaluate, and protect critical app registrations using Conditional Access for Workload Identities. Restricting access to specific IP addresses already massively reduces the attack surface.

I hope the post gave you an overview and insight into how service principals should be protected. Hit me up if you have any questions.

Resources

, , , , ,
Marco Gerber

Marco

Senior Cloud Engineer, keen on Azure and cloud technologies.

Leave a Reply

Your email address will not be published. Required fields are marked *