Your first Entra ID policies with Terraform: Named Location and Conditional Access Policy
Table of Contents
Introduction
Connect GitHub Actions to Entra ID with OIDC
The previous article covered OIDC (OpenID Connect) authentication for GitHub, which lets GitHub Actions use short-lived tokens, Microsoft Graph permissions, remote state access, and the GitHub deployment environment.
This article uses that connection to manage configuration. Our new Terraform will manage and describe resources, show proposed changes in a Terraform plan, and create them during an apply, all through GitHub Actions.
We will create two new resources, named-locations.tf and conditional-access.tf, review their plan, deploy through GitHub, and inspect the result in Entra.
There is also an alternative path for importing objects you already manage through the portal. We will cover this in a future article.
Step 1: Check the tenant and supporting setup
Federated credentials setup runbook
Before adding resources, confirm that you have:
- Completed the previous GitHub-to-Entra setup, including its Terraform preparation steps.
- A test Entra tenant.
- Working Terraform provider configuration, remote state, and GitHub plan and apply workflows.
- Reviewed the tenant's existing Conditional Access policies and Security Defaults configuration.
- Ensure the files
main.tf,outputs.tfandvariables.tfare present in a subfolder calledterraform.
Licensing: Conditional Access requires Microsoft Entra ID P1 or an entitlement that includes it. Check Microsoft's licence requirements for the users in scope.
Security Defaults: Use a tenant already prepared for Conditional Access. Do not disable Security Defaults just to run this example; it does not replace those protections. Follow Microsoft's transition guidance when planning that change.
Step 2: Prepare the folder and automation permissions
Terraform baseline files to get started
- Create a feature branch in your prepared starter repository.
- Confirm that its
terraformdirectory contains the provider, backend, and input variable configuration. - Check that both plan and apply run from that directory.
- Confirm the automation identity's Microsoft Graph application permissions and administrator consent.
For example, create the branch from the repository root:
git checkout -b feature/first-entra-policy
The working folder will contain:
terraform/
├── main.tf # Provider and backend configuration
├── variables.tf # Supporting inputs
├── outputs.tf # Supporting outputs
├── named-locations.tf # New location resource
└── conditional-access.tf # New policy and its exclusion-group input
IMPORTANT! Ensure you grant
ApplicationGraph permissions forPolicy.Read.AllandPolicy.ReadWrite.ConditionalAccesson your Service Principle that runs the Terraform code
Step 3: Create the named location
- Create
terraform/named-locations.tf. - Add the following resource.
resource "azuread_named_location" "named_location_restricted_signin" {
display_name = "Restricted Sign-in Locations"
country {
countries_and_regions = ["GB"]
include_unknown_countries_and_regions = false
}
}
The resource label, named_location_restricted_signin, identifies this declaration within Terraform. The display_name identifies it in the Entra portal. GB is the two-letter country code for the United Kingdom.
Creating the named location does not restrict access, it just defines it.
A VPN (virtual private network) or proxy can change the public address Entra sees. Geolocation accuracy also affects the result; a country is not proof that a sign-in is trustworthy. Microsoft's network guidance explains how these signals work.
Step 4: Create the report-only policy
Conditional Access policy resource
Conditional Access evaluates sign-in conditions and applies access controls when those conditions match. This example includes all users and applications, then excludes the UK location.
- Create
terraform/conditional-access.tf. - Add the policy below.
resource "azuread_conditional_access_policy" "ca_1050_block_high_risk_countries" {
depends_on = [
azuread_named_location.named_location_restricted_signin,
]
display_name = "GLOBAL - 1050 - BLOCK - High-Risk Countries"
state = "enabledForReportingButNotEnforced"
conditions {
client_app_types = ["all"]
applications {
included_applications = ["All"]
}
users {
included_users = ["All"]
}
locations {
included_locations = ["All"]
excluded_locations = [azuread_named_location.named_location_restricted_signin.object_id]
}
}
grant_controls {
operator = "OR"
built_in_controls = ["block"]
}
}
For users in scope, this policy would block access from outside the selected country. The block grant control expresses that decision; enabledForReportingButNotEnforced keeps it from enforcing the block.
The reference to azuread_named_location.named_location_restricted_signin.object_id creates a Terraform dependency. Terraform creates the location before the policy, regardless of file ordering.
Excluding the UK from this policy does not bypass other policies. Microsoft documents how report-only evaluation records results without enforcing the policy.
Step 5: Validate and review the proposed change
Once you have these two files in place, you can run formatting and static validation from your repository:
cd terraform
terraform fmt -check -diff
terraform init -backend=false
terraform validate
For a fresh local checkout, -backend=false lets you initialise providers for validation without accessing remote state. These checks do not authenticate to Entra or prove the policy will behave as intended.
The authenticated workflow runs tflint, a Terraform configuration linter, followed by formatting, backend initialisation, validation, and planning. If you use its lint configuration locally, also run:
tflint --init
tflint -f compact
- Commit the two resource files and the supporting workflow input change.
- Open a pull request against
main. - Wait for the GitHub Actions plan workflow to complete.
- Read the resource changes in the plan output or uploaded artifact.
For the new resources, we should expect the following at the bottom of the plan file, 2 to add for our 2 files created.
Plan: 2 to add, 0 to change, 0 to destroy.
Check you can see the country list, location reference, and report-only state.
A green workflow confirms that its checks passed.
Step 6: Deploy and inspect the result in Entra
- Merge the reviewed pull request into
main. - Approve the
productiondeployment if its environment has required reviewers configured. - Confirm that the apply workflow succeeds.
- Open Entra ID → Conditional Access → Named locations in the Entra admin centre.
- Check the location's country list.
- Open the policy and verify its users, applications, exclusions, and Report-only state.
Step 7: Check the policy against representative sign-ins
You should use the Conditional Access What If feature to check policy scope against the new Conditional Access policy. Then inspect actual sign-in logs, including the Report-only tab, to check what Entra evaluated and if everything is working as expected.
Once you are happy with your testing, you can do a new Pull Request changing the Conditional Access Policy state to be enabled.
Troubleshoot authentication, state, and policy results
| Symptom | What to check |
|---|---|
| OIDC authentication fails | Compare the issuer, audience, and subject with the previous article |
| Microsoft Graph reports insufficient privileges | Check application permissions and administrator consent for the failing resource |
| Terraform cannot access state | Check backend configuration, Azure permissions, and storage network access |
| A sign-in appears in the wrong country | Check the public egress address, where traffic leaves your network, and Microsoft's detected location |
| An enforced policy has an unexpected effect | Use the recovery process to return it to report-only or disabled, then reconcile configuration and state |
References
- Entra ID as Code repository
- Connect GitHub Actions to Entra ID with OIDC
- Federated credentials setup runbook
- Reusable Terraform workflow
- Named location resource and import format
- Conditional Access policy resource and import format
- Conditional Access network signals
- Conditional Access report-only evaluation
- Generate Terraform configuration for imports