Baseline

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.

Conditional AccessNamed LocationsState SyncGraph APIZero DriftPer-Tenant Override

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 First

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.

Group Placeholder Resolution

Use {{GROUP:name}} placeholders in policy JSON for group references. CONFIG365 resolves the GUID at deploy time — no hardcoded object IDs in the baseline.

Idempotent Runs

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 Default

If 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

Baseline

CONFIG365 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 Only

CONFIG365 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.

Example: Policy JSON
{
  "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"]
  }
}
Example: Named Location JSON
{
  "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.

Example: optional-applications.json
{
  "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:

Policy.Read.All

Read existing CA policies and named locations to compute the deployment diff.

Policy.ReadWrite.ConditionalAccess

Create, update, and delete CA policies and named locations during the apply phase.