Conditional Access Architecture
In 2020, I got the opportunity to launch a business from the ground with an (overly?) trusting founder. Armed with basic PowerShell and dusty programming skills, I had one goal: create a repeatable scan for Microsoft 365 to identify configuration gaps and hardening opportunities. Later that year, I got the opportunity to start running my duct tape/string code in customer environments. My life changed.
Over the course of my time with that company, all the way through our acquisition, I learned so many skills and saw so many environments. We had customers around the world, in various sectors, and different kind of operational demands. It taught me a lot about different organizations and how they approach security. It also revealed a lot about conditional access policy management.
Anti-Patterns
Very few organizations fully understand their CA policies. Fewer still feel comfortable with them. In most cases, it’s because they came as a solution to problems in a unique way that most people weren’t familiar with. They also came bundled with so many other changes in the cloud space that learning the right patterns didn’t get the priority it needed. As a result, there are some really common anti-patterns.
Pattern One: Treating it like a firewall
A common pattern is to create a long list of rules that are purpose tailored to each nuance in the organization. You run Temple OS for your developers, need Yubikeys, and must access things from strictly hostile nations? Here’s a conditional access policy for you. Your customer service department needs their own bespoke access solution, and some of their apps need an MFA exception? Here’s a policy for you.
This anti-pattern seems helpful on the surface, but it starts to create gaps quickly in your org. If you need to apply a control universally, but everyone is in a special exception you, you have to update several policies. Combine this with poor visibility across your conditional access policies and now there’s a gap to get into the environment without MFA. It’s not great.
Pattern Two: Using conditions as controls
Sometimes we need to carve out a policy to apply only to mobile devices, or to block access from Linux devices. So, it makes sense that you would create a condition that if device OS == Linux, then block it. However, in many cases this data is self-reported by the machine and we can tamper with it (I’ve tested and reported this in the past). This is a common issue I’ve come across.
Pattern Three: Inclusive Policies
Inclusive policy architecture relies on administrators who add all users to appropriate groups or policies individually and avoiding the use of the almighty “all” option for policy scope. I’ve seen the use of dynamic groups, birth right group management, and other means to attempt and orchestrate that all users have MFA employed on their accounts. This again creates gaps which require manual review.
Quick Detour: Deconfliction
Microsoft handles deconfliction in conditional access policies based on level of specificity, and then level of restriction. This order of operations is critical in understanding some of the pro-patterns we’ll discuss in a minute.
Example One: Group Based Inclusion
Let’s say we have two policies:
Policy A: MFA required for members of a group containing (George, Sally, others)
Policy B: MFA excluded from user George.
In this policy configuration, George will not be required to authenticate with MFA because of specificity of the exclusion.
Example Two: Group Based Exclusion
Let’s say we have two policies:
Policy A: MFA not required for members of a group containing (George, Sally, others)
Policy B: MFA required for user George.
In this policy configuration, George will be required to authenticate with MFA because of specificity of the inclusion.
Example Three: Same Specificity Clash
Let’s say we have two policies:
Policy A: MFA not required for George.
Policy B: MFA required for user George.
In this policy configuration, George will be required to authenticate with MFA because the level of specificity is the same and MFA requirement is the more strict evaluation.
With these three examples in mind, we can approach the architecture of our CA policies in a new way.
Psych: Another Detour
The other thing to keep in mind is that Conditional Access policies are additive. Unlike firewalls, Microsoft evaluates all possible policies at each point of authentication to ensure that all conditions are met. This allows you to stack policies and exceptions without having to create a rats nest of policies.
Re-Architecting The Conditional Access Policy
Conditional access policies are broken down into four groups of “levers” we can pull:
Scope (Apps, Users, Roles, Groups)
Conditions (Devices, Management, Location)
Grant Requirements (MFA, Trusted Device, etc.)
Session Management
Let’s start with a basic policy to start.
Policy One: Multifactor Authentication
Scope (All users, All apps)
Conditions (None)
Grant Requirements (MFA)
Session Management (Default)
In the policy, we now know that all users who interact with our tenant will be required to have MFA by default. No more guessing who does(n’t) have MFA enforced. But…what about our legacy style service accounts? That’s a relatively easy fix:
Scope (All users, All apps, excluding users who are in the service user group)
Now we know which users are excluded from MFA and we don’t have to deal with chasing down exceptions.
Policy Two: Trusted Devices
Scope (All users, All apps)
Conditions (None)
Grant Requirements (Trusted Device)
Session Management (Default)
This policy allows us to granularly apply trusted device requirements for apps within the environment which is handy for ensuring your data only has direct access from trusted devices. Again, having a single policy to control this allows you to quickly identify any exceptions and manage them.
Policy Three: Forced Reauthentication
Scope (All users, select apps)
Conditions (None)
Grant (MFA)
Session Control (Signin, everytime*)
This policy allows you to add additional friction for attackers trying to get to privileged portals. I wouldn’t recommend having this on all apps, but financial apps, security tools, and your infrastructure portals might deserve this level of requirement.
Note: Every time might cause a lot of heartburn for some of your users. If this creates unacceptable levels of end user frustration, I would push this back to no less than once every 24 hours.
Looking back
We’ve now got three policies that we can tailor to our environment to cover many of our general use cases. There may still be some limited exceptions that aren’t covered, but this should reduce the impact you have to deal with. We’ve split out the controls so they can be tailored individually to different situations without requiring a complete policy rewrite or generation too.
There will always be one-offs that buck against the recommendations here, but in general avoiding the anti-patterns and using the additive nature of the conditional access policies will allow you to craft a policy that meets your needs without the complexity of dozens of bespoke policies.