Conditional Access for single-tenant service principals available in preview

Let’s start the new year with some positive news. A much anticipated feature has been released in preview, namely Conditional access policy support for Service principal objects. In this article, we will take a quick look at it and the goodness it offers.

As customary, let’s start with a short history lesson. Traditionally, many of the authentication-related features Azure AD offers only extended to cover user objects, such as Conditional access policies or Sign-in logs. This in turn created a problem, as while tenants were able to tightly control how and when their users can and cannot authenticate (and look at the corresponding logs), securing applications was simply not possible. For years, authentication flows such as the client credential flow bypassed the CA pipeline, meaning there was no possibility to restrict access to highly privileged apps to say requests coming only from your on-premises environment. Similarly, for the myriad of service principal objects corresponding to third-party applications that might be in use in your organization, there was no way to restrict access to only the ISVs datacenters or similar.

Microsoft has of course been aware of this gap, and over the past few years finally started tackling it. First, we got the introduction of non-interactive/service principal sign-ins, at the end of 2020. And at the end of 2021, we finally got the initial release of CA support for service principals!

To configure a Conditional access policy that applies to service principal objects (or Workload identities, as they are referred to in the UI), one follows a process very similar to the “regular” CA creation workflow. Start by navigating to the Azure AD blade > Security > Conditional Access, then hit the New Policy button. Enter a proper name for your policy, and under the next section, now renamed to User or workload identities, click on the newly introduced dropdown control and select Workload identities (preview) as illustrated on the left hand-side of the screenshot below. Next, hit the Select Service Principals button and add the corresponding service principal objects as needed. You can also configure exclusions, or force the policy to apply to all service principal login attempts. The example below will use the SP object I’ve created to facilitate certificate-based authentication for Exchange Online PowerShell:

At this point, we should probably mention few oddities of the current version of the UI. First, I’m certainly not a bit fan of the dropdown approach, at the very least it leaves some confusion as to whether a policy can apply to both user and service principal objects. Next, the “select” pane on the right can certainly use some improvements. It only seems to return three objects in my tenant, out of the few hundred SPs available. In another tenant, I get five entries instead, again very limited and not representative of the real situation. Regardless, the UI allows you to search for a SP by name, or object ID, which is likely what you should do.

Once you have configured the identities against which the policy will apply, you can continue by specifying which Cloud apps or actions the policy will apply to. You don’t have much choice here really, as the preview only supports All or None values. Make sure to set it to All cloud apps. Similarly, you can only configure the Locations condition under Conditions. Which is probably the only one you care about when it comes to configuring SP-based policy, to be fair. So make sure you configure the appropriate locations here, in my case I will go with Any location and all trusted locations excluded. Lastly, under the Grant section, select Block access, which will be your only option as mentioned above. Toggle the policy On, or run it in Report-only mode, then hit the Save button to confirm the changes.

Give the policy few mins to propagate, then you can go ahead and test it. Here’s for example the sing-in event logged in when I tried to obtain a token for the designated service principal from an IP outside of my trusted locations. Note the highlighted message, which will closely match the error response from the Graph endpoint itself:

Invoke-WebRequest : {"error":"invalid_grant","error_description":"AADSTS53003: Access has been blocked by Conditional Access policies. The access policy does not allow token issuance.\r\nTrace ID: ff416ec4-b78c-44c0-8059-e4149f1a2801\r\nCorrelation ID:
e5816db6-0bb3-4558-88be-6fbb8b3eee75\r\nTimestamp: 2022-01-03 09:35:20Z","error_codes":[53003],"timestamp":"2022-01-03 09:35:20Z","trace_id":"ff416ec4-b78c-44c0-8059-e4149f1a2801","correlation_id":"e5816db6-0bb3-4558-88be-6fbb8b3eee75","error_uri":"https://
login.microsoftonline.com/error?code=53003","suberror":"message_only","claims":"{\"access_token\":{\"capolids\":{\"essential\":true,\"values\":[\"f02df45d-66cd-4e92-9e9c-bb2f32c67d8a\"]}}}"}

Similar entries will be generated when trying to connect via the Exchange Online V2 PowerShell module, which is what I wanted to achieve by configuring such policy in the first place. If effect, now I’m in control of both user and service principal login attempts, so even if someone manages to get a hold of the certificate I’ve configured for my ExoPS app (client credentials in general), they will not be able to login outside of the IP ranges I have designated as trusted. Great success!

For the sake of completeness, here’s also the information provided on the Conditional Access tab for the failed login attempt, specifically the policy evaluation details.

And with the risk of being too annoying, here’s also how Exchange Online PowerShell module will handle the policy restrictions. The thing to note here is that we are actually providing the client (application) Id value, and not the objectID of the service principal configured in our conditional access policy. Since the service principal is the local representation of the application object within the tenant, the login request is effectively performed against it. And as you can see it is successfully blocked, whereas the same cmdlet will work just fine from an “allowed” location.

The reason I wanted to mention this is to clarify some potential confusion that might result by looking at the official documentation. As noted therein, this preview is only the first step in bringing conditional access support to “workload identities”, and currently it only support single-tenant service principals, owned by your organization. Multi-tenant applications, such as the ones used by ISVs and managed identities are not currently supported. In the above example, I’ve actually used a multi-tenant application, but the policy was scoped to the local service principal, so it technically fails within the requirements. This is also a good place to remind you that “not supported” does not always mean “it will not work”, just that Microsoft has not committed to officially support such scenarios.

We should probably also spend some time talking about what “workload identities” are. For the purpose of the current article/feature, you can regard them as the equivalent of service principals “owned” by your organization.

Few last notes before closing the article. Firstly, the policy will apply to all types of authentication used by the service principal, including certificate, client credentials, symmetric key, etc. You should really be using certificates only at this point, but anyway, I digress. Another interesting observation is that the policy took quite a while to appear in the output of the /beta/identity/conditionalAccess/policies/ Graph API query, which is probably a side effect of the preview status. Looking at the Graph API output though, one can glance at the servicePrincipalRiskLevels property, which is and indicator that Microsoft is already working on expanding support for more conditions!

This entry was posted in Azure AD, Microsoft 365, Office 365. Bookmark the permalink.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.