Conditional Access Policies
CONFIG365 deploys and manages Conditional Access policies and named locations via Microsoft Graph — ensuring every tenant enforces the right access controls from day one, with configurable state sync behaviour and fine-grained exclusion support.
How It Works
Conditional Access deployment is controlled by the deployConditionalAccess
pipeline toggle. When enabled, the pipeline runs in two phases: a plan phase that
computes and logs every change it would make without applying anything, followed by an apply
phase that pushes the changes to the tenant via the Graph API.
Named locations are always deployed before policies. This ensures any IP range or country locations referenced by a policy already exist when the policy is created.
Use {{GROUP:name}} placeholders in policy JSON for group references. CONFIG365 resolves the GUID at deploy time — no hardcoded object IDs in the baseline.
Running the pipeline multiple times is safe. The script compares current tenant state against the baseline and only applies deltas. Unchanged policies are left untouched.
Repository Structure
Place policy and named location JSON files under the following paths in the baseline repository:
baseline/
└── conditional-access/
├── policies/
│ ├── require-mfa-users.json
│ ├── block-legacy-auth.json
│ └── ...
├── named-locations/
│ ├── office-network.json
│ └── ...
└── optional-applications.json (optional)
baseline-remove/
└── conditional-access/
├── policies/
│ └── <stale-policy-name>.json
└── named-locations/
└── <stale-location-name>.json
Files in baseline-remove/ are only processed when the allowDelete pipeline toggle is enabled. Deletion is matched by displayName.
State Sync Modes
The conditionalAccess.stateSync setting in config365-options.json controls
how CONFIG365 handles the enabled/disabled state of CA policies. The global default can be overridden per-tenant.
preserve
Preserve Global DefaultIf a policy exists in the baseline but is currently disabled in the tenant, CONFIG365 leaves it disabled and does not re-enable it. All other properties (conditions, grant controls, display name) are still synced to match the baseline.
- • Safe default for brownfield tenants
- • Lets admins test policies before enforcing
- • Full property sync except enabled/disabled state
- • Recommended when onboarding existing tenants
baseline
BaselineCONFIG365 syncs the policy state exactly as defined in the baseline JSON, including enabling or disabling policies. A policy set to "enabled" in the baseline will be enabled in the tenant, even if an admin had manually disabled it.
- • Strict enforcement of baseline state
- • Prevents manual state drift between runs
- • Use with caution on production tenants
- • Suitable for greenfield or fully managed tenants
enableOnly
Enable OnlyCONFIG365 will enable policies that are set to "enabled" in the baseline but will never disable a policy, even if the baseline defines it as disabled. A useful middle ground when you want to progressively roll out policies without risk.
- • One-way state enforcement (enable only)
- • Never disables a policy already enabled in tenant
- • Safe for incremental policy roll-out
- • Can be combined with .baseline-ignore for fine control
JSON Structure
Policy and named location files map directly to the Microsoft Graph Conditional Access API schema. Group references
use {{GROUP:name}} placeholders
which CONFIG365 resolves to object IDs at deploy time — keeping the baseline portable across tenants.
{
"displayName": "Require MFA – Modern Workplace Users",
"description": "Enforce MFA for all baseline users accessing cloud applications.",
"state": "enabled",
"conditions": {
"users": {
"includeGroups": [
"{{GROUP:Baseline – Modern Workplace Users}}"
],
"excludeGroups": [
"{{GROUP:Baseline – Users Require MFA Excluded}}"
]
},
"applications": {
"includeApplications": ["All"]
},
"clientAppTypes": ["all"]
},
"grantControls": {
"operator": "OR",
"builtInControls": ["mfa"]
}
} {
"displayName": "Office Network – Head Office",
"@odata.type": "#microsoft.graph.ipNamedLocation",
"isTrusted": true,
"ipRanges": [
{
"@odata.type": "#microsoft.graph.iPv4CidrRange",
"cidrAddress": "203.0.113.0/24"
}
]
} Excluding Policies
Two mechanisms allow you to opt specific policies out of baseline management — one at the file level, one embedded in the policy description itself.
.baseline-ignore
Add gitignore-style patterns to .baseline-ignore in the tenant repo to
exclude entire policy or named location files from being processed. Supports * and ** wildcards.
# Exclude a specific policy file
conditional-access/policies/legacy-auth.json
# Exclude all named locations
conditional-access/named-locations/** CONFIG365:IGNORE
Add CONFIG365:IGNORE anywhere in the policy's description field
to tell CONFIG365 to leave that specific policy completely untouched, even if it appears in the baseline.
Useful for policies managed manually or by another tool.
{
"displayName": "Custom – Break Glass",
"description": "CONFIG365:IGNORE",
...
} Optional Applications
Some CA policies reference cloud applications — such as Azure Virtual Desktop or Azure VPN — that may not have a
service principal registered in every tenant. Add an optional-applications.json
file to your conditional-access/ directory to declare these apps.
CONFIG365 checks whether each optional app's service principal exists in the target tenant before deploying and
silently removes absent apps from policy conditions rather than failing the pipeline.
{
"applications": {
"2793c444-c711-4d67-a174-2cbe32990f81": "Azure Virtual Desktop",
"9cdead84-a844-4324-93f2-b2e6bb768d07": "Azure VPN Gateway"
}
} Manageable from the portal. The CONFIG365 CA Options page includes an Optional Applications editor that reads and writes this file directly via the Gitea API — no local Git checkout required. Changes are committed to the baseline repo the moment you click Save.
Per-Tenant State Sync Override
The global stateSync setting from config365-options.json
can be overridden for a specific tenant by creating a config/conditional-access/config.json
file in the tenant's own repository. The tenant-level value takes precedence over the global default.
Tenant repo structure
tenant-repo/
└── config/
└── conditional-access/
└── config.json config.json
{
"stateSync": "baseline"
}
Valid values are preserve, baseline, and enableOnly.
See the State Sync Modes section above for a description of each.
Required Graph Permissions
The Entra ID app registration used by CONFIG365 must have the following application permissions granted and admin-consented in each tenant:
Read existing CA policies and named locations to compute the deployment diff.
Create, update, and delete CA policies and named locations during the apply phase.