Overview
Conditional Access Policies can be used to block access from geographic locations that are deemed out-of-scope for your organization or application. The scope and variables for this policy should be carefully examined and defined.
Rationale
Conditional Access, when used as a deny list for the tenant or subscription, is able to prevent ingress or egress of traffic to countries that are outside of the scope of interest (e.g., customers, suppliers) or jurisdiction of an organization. This is an effective way to prevent unnecessary and long-lasting exposure to international threats such as APTs.
Impact
Microsoft Entra ID P1 or P2 is required. Limiting geographical access will deny access to users traveling or working remotely in a different part of the world. A point-to-site or site-to-site tunnel, such as a VPN, is recommended to address exceptions to geographic access policies.
Default Value
No policies are configured by default.
Additional Information
These policies should be tested by using the What If tool in the References. Setting these can and will create issues with logging in for users until they use an MFA device linked to their accounts. Further testing can be done via the insights and reporting resource in References, which monitors Azure sign-ins.
Remediation guidance
From Azure Portal
Part 1 of 2 - Create the policy and enable it in Report-only mode.
- Open Conditional Access | Overview
- Click the
+ New policybutton, then: - Provide a name for the policy.
- Under
Assignments, selectUsersthen:- Under
Include, selectAll users - Under
Exclude, checkUsers and groupsand only select emergency access accounts
- Under
- Under
Target resources, selectCloud appsthen:- Under
Include, selectAll cloud apps - Leave
Excludeblank unless you have a well defined exception.
- Under
- Under
Conditions, selectLocationsthen:- Select
Include, then add entries for locations for those that should be blocked. - Select
Exclude, then add entries for those that should be allowed (IMPORTANT: Ensure that all Trusted Locations are in theExcludelist.)
- Select
- Under
Access Controls, selectGrantand Confirm thatBlock Accessis selected. - Set
Enable policytoReport-only. - Click
Create.
NOTE: The policy is not yet 'live,' since Report-only is being used to audit the effect of the policy.
Part 2 of 2 - Confirm that the policy is not blocking access that should be granted, then toggle to On.
- With your policy now in report-only mode, return to the Microsoft Entra blade and click on
Sign-in logs. - Review the recent sign-in events - click an event then review the event details (specifically the
Report-onlytab) to ensure:- The sign-in event you're reviewing occurred after turning on the policy in report-only mode.
- The policy name from step 5 above is listed in the
Policy Namecolumn. - The
Resultcolumn for the new policy shows that the policy wasNot applied(indicating the location origin was not blocked).
- If the above conditions are present, navigate back to the policy name in Conditional Access and open it.
- Toggle the policy from
Report-onlytoOn. - Click
Save.
From PowerShell
First, set up the conditions object values before updating an existing conditional access policy or before creating a new one. You may need to use additional PowerShell cmdlets to retrieve specific IDs, such as the Get-AzureADMSNamedLocationPolicy, which outputs the Location IDs for use with conditional access policies.
$conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
$conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition $conditions.Applications.IncludeApplications = <"All" | "Office365" | "app ID" | @("app ID 1", "app ID 2", etc...> $conditions.Applications.ExcludeApplications = <"Office365" | "app ID" | @("app ID 1", "app ID 2", etc...)>
$conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition $conditions.Users.IncludeUsers = <"All" | "None" | "GuestsOrExternalUsers" | "Specific User ID" | @("User ID 1", "User ID 2", etc.)> $conditions.Users.ExcludeUsers = <"GuestsOrExternalUsers" | "Specific User ID" | @("User ID 1", "User ID 2", etc.)>
$conditions.Users.IncludeGroups = <"group ID" | "All" | @("Group ID 1", "Group ID 2", etc...)>
$conditions.Users.ExcludeGroups = <"group ID" | @("Group ID 1", "Group ID 2", etc...)>
$conditions.Users.IncludeRoles = <"Role ID" | "All" | @("Role ID 1", "Role ID 2", etc...)>
$conditions.Users.ExcludeRoles = <"Role ID" | @("Role ID 1", "Role ID 2", etc...)>
$conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocationCondition $conditions.Locations.IncludeLocations = <"Location ID" | @("Location ID 1", "Location ID 2", etc...) >
$conditions.Locations.ExcludeLocations = <"AllTrusted" | "Location ID" | @("Location ID 1", "Location ID 2", etc...)>
$controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls $controls._Operator = "OR"
$controls.BuiltInControls = "block"
Next, update the existing conditional access policy with the condition set options configured with the previous commands.
Set-AzureADMSConditionalAccessPolicy -PolicyId <policy ID> -Conditions $conditions -GrantControls $controls
To create a new conditional access policy that complies with this best practice, run the following commands after creating the condition set above
New-AzureADMSConditionalAccessPolicy -Name "Policy Name" -State <enabled|disabled> -Conditions $conditions -GrantControls $controls
Multiple Remediation Paths
SERVICE-WIDE (RECOMMENDED when many resources are affected): Apply organization/tenant-level guardrails and baseline policies for the entire platform.
ASSET-LEVEL: Fix only the affected resources identified by this control.
PREVENTIVE: Add preventive policy checks to CI/CD and periodic posture scans.
References for Service-Wide Patterns
- Platform policy/governance and preventive control patterns should be applied tenant-wide where supported.
Query logic
These are the stored checks tied to this control.
Entra Exclusionary Geographic Access Policy
Connectors
Covered asset types
Expected check: eq []
{
connectors(
where: {
conditionalAccessPolicies_NONE: {
conditions: {
includeUsers: ["All"]
includeApplications: ["All"]
clientAppTypes: ["all"]
NOT: { includeLocations: [] }
}
grantControls: { builtInControls: ["block"] }
}
}
) {
...AssetFragment
}
}