TERRAFORM PROVIDER FOR INTUNE AND ENTRA ID

Review your Microsoft 365 tenant as code.

TerraProvider is a Terraform and OpenTofu provider for Microsoft 365 that manages Intune, Entra ID and tenant-wide settings as code. A Conditional Access or compliance change is a plan someone reads before it's applied, and configuration drift from the portal shows up in the next one.

Community Edition free for organizations with up to 99 users. See pricing

Microsoft Partner of the Year, winner or finalist nine times; Security MSSP of the Year, finalist three times

In the portal: No changes since the last apply.

terraform plan
microsoft365wp_conditional_access_policy.require_mfa_admins: Refreshing state... [id=2b7f9c3e-8d41-4f6a-b0e2-5c9a1d7e3f80]No changes. Your infrastructure matches the configuration.
Example data.

Works with what you already run

  • Terraform
  • OpenTofu
  • Microsoft Graph
  • GitHub Actions
  • Azure DevOps
  • Azure CLI
THE PROBLEM

Intune and Entra ID are configured in browser tabs

That works until someone asks what changed, who agreed to it and why production looks different from test.

  1. Nobody reviews a click.

    A Conditional Access change is live the moment someone presses Save. The first review is often the helpdesk call.

    With TerraProvider the change is a pull request. The plan shows which attribute moves from what to what before anything touches the tenant.

  2. The portal remembers who, not why.

    The audit log names the account that saved a policy. The ticket, the reason and the colleague who agreed are somewhere else, if anywhere.

    Git keeps the reason next to the change: commit message, pull request, approval. Rolling back is a revert.

  3. Test and production drift apart.

    The test tenant got the new compliance rule in March, production in May, with one setting different and nobody sure which.

    One module, applied to both tenants. Where they differ, a variable says so.

  4. The second tenant is built by hand.

    A new subsidiary or a rebuilt tenant means clicking through every policy with the old tenant open in the next tab.

    Export groups, filters and policies into code once (start from the tenant you have), then apply the same code to the new tenant.

  5. The change nobody wrote down.

    Someone fixed something in the portal late at night and forgot about it. The documentation now describes a tenant that doesn't exist.

    The next plan lists it, including settings your files never mention. You decide whether to adopt it or put it back.

    ~ exclude_users = [ - "7c1e4a90-3f2b-4d8e-9a61-0b5c2e8f1d47" ]

Intune as code: the plan also watches the settings you leave out

Most Terraform files name only the settings someone cared about. TerraProvider fills in every other attribute with the default the Microsoft portals use, so the plan compares the whole object, not just your lines. If someone adds an exclusion to your MFA policy by hand, the next plan shows it, and the next apply takes it out again.
  • A change to an attribute in your file: shown, and reverted by the next apply.
  • A change to an attribute you left out: shown, and reset to the portal's default.
  • A change you want to keep: write it into the file, and the difference is gone.

A setting someone switched on and off again in the portal can show up once as a change back to its default. That's the price of watching everything, and one apply settles it.

In the portal: No changes since the last apply.

microsoft365wp_conditional_access_policy.require_mfa_admins: Refreshing state... [id=2b7f9c3e-8d41-4f6a-b0e2-5c9a1d7e3f80]No changes. Your infrastructure matches the configuration.
Example data.

Conditional Access as code: new policies start in report-only

A Conditional Access policy without a state doesn't go live. TerraProvider creates it in report-only, so you can read the sign-in logs before anyone is locked out. Switching it on is a one-line change, reviewed like every other one.
  • The default is enabledForReportingButNotEnforced unless your file says otherwise.
  • Every policy change is a diff in a pull request, with the approval next to it.
  • Microsoft's agent conditions for Conditional Access came with the Graph metadata update in 0.20.0.
resource "microsoft365wp_conditional_access_policy" "block_legacy_auth" {  display_name = "Block legacy authentication"  conditions = {    users = {      include_users = ["All"]    }    applications = {      include_applications = ["All"]    }    client_app_types = ["exchangeActiveSync", "other"]  }  grant_controls = {    operator          = "OR"    built_in_controls = ["block"]  }}

Example data, plan shortened.

COVERAGE

What it manages

60
Resources
127
Data sources
25
Graph areas
13
Tenant-wide settings
UNDER THE HOOD

Resources are generated from Microsoft's Graph metadata

Microsoft publishes a machine-readable description of every object in Microsoft Graph. TerraProvider's resources are generated from it, and the attribute descriptions in its docs are Microsoft's own words. When Microsoft adds a setting to an object the provider already manages, a metadata update brings it into every resource that has it, in one release.

A new kind of object still needs a release of its own: someone has to decide which attributes are required and which defaults are right. The metadata update covers new attributes of the objects already there.

  1. Microsoft adds a setting to Graph.

    For example, whether users may register agent identity blueprints.
  2. TerraProvider 0.20.2 brings it in.

    allowed_to_create_agent_identity_blueprints arrives in authorization_policy, with the same default as Microsoft's, twelve days before Microsoft switches it on.
  3. Microsoft switches it on.

    For every user, unless the tenant switched it off before.
  4. Your pull request.

    Set it to false in the module, and the next apply switches it off in every tenant that uses the module.
GETTING STARTED

Import the tenant you already have

Nobody starts with an empty tenant. TerraProvider adopts what's there, and the quickstart does most of the typing.

  1. Export what's there.

    The quickstart's PowerShell export writes your groups, filters, notification templates, compliance policies, configuration policies into Terraform files, with a mapping for every object.
  2. Import it into the state.

    The same tool imports them in order, groups and filters before the policies that point to them. Any other resource can be imported too.
  3. Export single objects.

    For anything else, one command writes a Terraform file for a single object by its id, using Terraform's own configuration generation.
  4. Run the first plan.

    It shows what still differs between code and tenant. The quickstart calls the result a starting point, which is fair: not every attribute can be exported, and a short clean-up is normal.

TerraProvider encrypts and uploads the app package itself

A Win32 app in Intune is a settings page and an encrypted file. TerraProvider handles both: it encrypts and uploads .intunewin, MSI, APPX/MSIX, DMG, PKG packages itself, and uploads again only when the file changed. Two functions read the metadata out of .intunewin and APPX/MSIX files, so the file name and the setup file come from the package, not from a copy in your HCL.
  • 21 app types, from Win32 and MSI to macOS DMG and PKG, App Store, managed Google Play and web links.
  • Assignments to all devices, all licensed users, groups and exclusion groups, each with a filter, in the same resource.
  • Line-of-business apps for iOS and Android are read, not uploaded.
locals {  agent_package = "${path.module}/packages/contoso-agent-4.2.1.intunewin"  agent_meta    = provider::microsoft365wp::parse_intunewin_metadata(local.agent_package)}resource "microsoft365wp_mobile_app" "contoso_agent" {  display_name = "Contoso Agent"  publisher    = "Contoso"  win32_lob = {    file_name              = local.agent_meta.file_name    setup_file_path        = local.agent_meta.setup_file    install_command_line   = "setup.exe /quiet"    uninstall_command_line = "setup.exe /uninstall /quiet"    detection_rules = [{      filesystem = {        detection_type      = "exists"        path                = "C:\\Program Files\\Contoso\\Agent"        file_or_folder_name = "agent.exe"      }    }]    source_file   = local.agent_package    source_sha256 = filesha256(local.agent_package)  }  assignments = [{    target   = { group = { group_id = "3d9b2f61-7a4e-4c18-9e05-6f2a8b1c4d93" } }    settings = { win32_lob = {} }  }]}

Example data.

OPERATIONS

It runs in the pipeline that already deploys Azure

  • No stored secrets.

    OpenID Connect from GitHub Actions or an Azure DevOps service connection. Client certificates, secrets, managed identity and AKS workload identity are there when you need them.

  • Nothing extra on your laptop.

    Locally the provider uses your az login session unless you tell it otherwise.

  • Four clouds.

    The global cloud, US Government L4, US Government L5 (DoD) and Microsoft's cloud in China.

  • 10 builds.

    macOS, Linux, Windows and FreeBSD on amd64 and arm64, plus 32-bit ARM for Linux and FreeBSD.

  • Throttling is handled.

    When Graph says slow down, the provider waits 3 to 60 seconds and tries again, up to 10 times per request.

  • Terraform or OpenTofu.

    The same provider address in both registries. The quickstart's workflows run OpenTofu.

Apply one tenant baseline to many tenants

Each provider block signs in to one tenant. Give every tenant its own block and call the same module for all of them: the baseline is written once, and the differences are variables you can read in a review.
  • A new tenant gets the baseline with one module block and one plan.
  • Exceptions live in variables, not in someone's memory.
main.tf
provider "microsoft365wp" {  alias     = "contoso"  tenant_id = "8a1f3c2e-5b7d-4e90-a6c4-2d9e7f1b0c35"}provider "microsoft365wp" {  alias     = "fabrikam"  tenant_id = "e4c27b91-0d3a-4f86-b5e2-7a1c9d0f6b48"}module "baseline_contoso" {  source    = "./baseline"  providers = { microsoft365wp = microsoft365wp.contoso }}module "baseline_fabrikam" {  source     = "./baseline"  providers  = { microsoft365wp = microsoft365wp.fabrikam }  allow_byod = true}

Example data.

Which edition fits your organization

No credit card or sales call to get started. Both editions run the same provider; Enterprise adds support and covers organizations from 100 users.

  • Community Edition

    • Free for organizations with up to 99 users
    • For your own tenant, run by your own staff
    • For test labs and small environments; no support included
  • Enterprise

    • Packages from 100 users, starting at EUR 1,200 a year
    • Support included: Monday to Friday, 08:00 to 18:00 CET/CEST, typical response under 8 hours for incidents
    • For enterprises of any size

Specifications

Provider

Address
terraprovider/microsoft365wp
Works with
Terraform and OpenTofu
Plugin protocol
6
Latest version
0.20.2, October 23, 2026
Builds
10 (macOS, Linux, Windows, FreeBSD)
Source
Public on GitHub, Business Source License 1.1

Microsoft Graph

API
Microsoft Graph, beta endpoint
Clouds
global, US Government L4, US Government L5 (DoD), China
Throttling
Up to 10 retries, 3 to 60 seconds apart

Coverage

Resources
60
Data sources
127
Areas
25
Tenant-wide settings
13
Functions
2
Import
every resource

Sign-in

Methods
OpenID Connect (GitHub Actions, Azure DevOps, any OIDC token), client certificate, client secret, managed identity, AKS workload identity, Azure CLI
Local default
Azure CLI

Behavior

Attributes you leave out
Watched, set to the portal's default
New Conditional Access policies
report-only
App packages
.intunewin, MSI, APPX/MSIX, DMG, PKG, uploaded again only when the file changed

Licensing and support

Editions
Community Edition (up to 99 users, free), Enterprise (from 100 users)
Support
Monday to Friday, 08:00 to 18:00 CET/CEST
Response
Typically under 8 hours for incidents, within support hours
Language
English
Certification
TerraProvider is ISO 27001 certified (certificate)

Questions before the first plan

01Does it work with OpenTofu?

Yes. The provider is listed in the OpenTofu Registry under the same address, terraprovider/microsoft365wp, and the quickstart's own workflows run OpenTofu. Your configuration doesn't change when you switch.

02How is TerraProvider different from Microsoft365DSC?

They work in different toolchains. Microsoft365DSC is built on PowerShell Desired State Configuration; TerraProvider is a provider for Terraform and OpenTofu, so your tenant settings are written in HCL and use the same state, plans and pipelines as your Azure resources. If your team already runs Terraform or OpenTofu, it fits into what you have.

03Does it create users and groups?

No. Users, groups, applications and service principals are lookups: you can read them and reference their ids. Create groups with HashiCorp's azuread provider, as the quickstart does, and assign policies to them with TerraProvider.

04Can we use it next to the azuread provider or another Microsoft 365 provider?

Yes. The quickstart does it: groups come from HashiCorp's azuread provider, and TerraProvider assigns policies to them in the same configuration. Let one provider own each object: TerraProvider watches every attribute of the objects it manages, so a change another tool makes to one of them shows up in the next plan.

05We configured our tenant by hand. Do we start over?

No. Every resource can be imported into the Terraform state, and the quickstart exports groups, filters, notification templates, compliance policies, configuration policies into code and imports them for you. Expect a short clean-up after the first plan; the export is a starting point, not a perfect copy.

06What happens to settings we don't write down?

They get the default the Microsoft portals use, and the plan watches them like everything else. A change made in the portal shows up in the next plan; the next apply resets it, unless you put the new value into your file.

RealmJoin
RealmJoin

RealmJoin is the other half of Microsoft Intune

TerraProvider puts your Intune policies into code. RealmJoin takes on the work around them: more than 3,000 app packages kept up to date, runbooks, helpdesk access and local admin passwords, in one console on top of Intune.
RealmJoin Product Logo

Try TerraProvider, or talk to the team.

Product team
The TerraProvider team