Skip to content

Manage MCSB Control Overrides

The engineer's guide to switching off Microsoft Cloud Security Benchmark (MCSB) controls. Use this runbook to:

  • Disable an MCSB control that is permanently not-applicable by design.
  • Re-enable a control you previously disabled.
  • Bring a new management group into scope.
  • Work out why a subscription still shows a recommendation you disabled.

For what Secure Score measures and why changing this list moves it, see Defender for Cloud Secure Score.

Disable or exempt?

Disable a control only when it can never be met under the operating model. For example, "more than one owner on subscriptions" fails forever because Owner is only granted just-in-time through PIM. If the control could be met one day, or only one resource can't meet it, use a policy exemption with an expiresOn of 90 days or less.

Why there are two places to change

MCSB checks every subscription twice, through two separate policy assignments:

Assignment Scope Owned by Feeds
A psa-sec-mcsb-001 mg-intermediate-root, inherited by every subscription EPAC Regulatory compliance dashboard
B SecurityCenterBuiltIn ("ASC Default") Each subscription on its own Defender for Cloud, which creates and keeps resetting it Secure Score and Defender recommendations

EPAC is told to leave Defender's own assignments alone (keepDfcSecurityAssignments = true in global-settings.jsonc), so it can't touch B. Terraform can't own B either: Defender would keep putting it back. So B is handled by a small Terraform deployment that only patches one property on it.

A control disabled in only one of them keeps showing up through the other. Always change both.

The projects

Project Lane Holds
azure-governance-epac B Assignment A. Each control's effect lives in one row of pa-sec-MsftCloudSecurityBenchmarkV2.csv.
azure-mcsb-overrides B Assignment B, for every subscription in the mg-grinntec subtree except mg-sandboxes. The control list lives in disabled-controls.tf, the scope in management-groups.tf.

Both are Tier 1: CI plans on the MR and applies on merge to main.

How azure-mcsb-overrides works

local.scanned_management_groups
        │  data "azurerm_management_group"  →  .subscription_ids (direct children only)
        ▼
local.target_subscription_ids
        │  for_each
        ▼
module "defender"  →  solution-manage-defender-for-cloud
                       →  terraform-azurerm-defender-for-cloud
        ▼
azapi_update_resource  PATCH properties.overrides on
  /subscriptions/<id>/providers/Microsoft.Authorization/policyAssignments/SecurityCenterBuiltIn
  • Subscriptions are found automatically. Nobody lists them by hand.
  • Terraform only changes overrides. One override sets policyEffect = Disabled for every reference ID in local.disabled_mcsb_controls. Defender keeps owning everything else on the assignment.
  • A nightly pipeline schedule re-runs plan and apply on main, so a new subscription is covered within a day.
  • Identity: sp-pla-tf-mcsb-overrides-rw signs in with OIDC. It has Reader and Resource Policy Contributor on mg-grinntec and is defined in t0-azure-service-principals.

Before you start, find the control's reference ID

Both projects identify a control by its policyDefinitionReferenceId, the member's name inside the MCSB initiative (for example identityDesignateMoreThanOneOwnerMonitoring). It is not the display name and not the policy definition GUID.

The quickest place to find it is the EPAC CSV, which lists every member it sets an effect for. If the control isn't there yet, look it up in the live initiative:

(Get-AzPolicySetDefinition -Name 'e3ec7e09-768c-4b64-882c-fcada3772047').PolicyDefinition |
  Where-Object policyDefinitionId -match '<policy-definition-guid>' |
  Select-Object policyDefinitionReferenceId, policyDefinitionId

Also write down why it can never apply. You'll need that for the code comment and for ADR-0001.


Disable a control

1. Disable it in EPAC (assignment A)

📁 azure-governance-epac: 📃 Definitions/policyAssignments/security/pa-sec-MsftCloudSecurityBenchmarkV2.csv

Find the control's row and set prodEffect to Disabled. If it has no row, add one. The name column is the policy definition GUID, not the reference ID. EPAC fails Build-DeploymentPlans with "unknown Policy name" if you put the reference ID there.

name,referencePath,policyDefinitionReferenceId,category,displayName,allowedEffects,prodEffect,prodParameters
09024ccc-0c5f-475e-9457-b7c0d9ed487b,,identityDesignateMoreThanOneOwnerMonitoring,Identity,More than one owner should be designated for subscriptions,AuditIfNotExists;Disabled,Disabled,{}

Disabled must be one of the values in allowedEffects.

Open an MR. Check the plan changes only psa-sec-mcsb-001's parameters, then merge.

2. Disable it on every subscription (assignment B)

📁 azure-mcsb-overrides: 📃 disabled-controls.tf

Add the same reference ID to local.disabled_mcsb_controls, with a comment that gives the display name and the reason:

disabled_mcsb_controls = [
  # "There should be more than one owner assigned to subscriptions"
  # (09024ccc-…). No standing Owner role assignments by design; Owner is
  # granted JIT via the PIM-eligible platform group.
  "identityDesignateMoreThanOneOwnerMonitoring",
  # Microsoft Defender for Key Vault should be enabled
  "keyVaultsAdvancedDataSecurityMonitoringEffect",
]

Open an MR. CI posts the plan. Check it shows one in-place update per subscription to module.defender["<sub-id>"].…azapi_update_resource.mcsb_overrides, and that each update only changes the selectors[0].in list. It should create or destroy nothing. Merge, and CI applies.

3. Record it in ADR-0001

📁 azure-governance-epac: 📃 Docs/adr/ADR-0001-mcsb-audit-separate-enforcement.md

Add a row to the By-design not-applicable MCSB controls table with the reference ID, the reason, and both places it is disabled. This is the record auditors read. The code comments don't replace it.


Re-enable a control

Do the steps above in reverse:

  1. EPAC: set the CSV row's prodEffect back to its audit effect (Audit or AuditIfNotExists). Merge.
  2. azure-mcsb-overrides: remove the reference ID from local.disabled_mcsb_controls. Merge.
  3. ADR-0001: remove the row, or mark it re-enabled with the date.

Removing the deployment does not undo the overrides

azapi_update_resource doesn't revert its change when it's destroyed. If you delete the module call, or the whole deployment, the last override stays on every subscription. To clear everything, set disabled_mcsb_controls = [] and let it apply first. That sends an empty overrides list.


Add a management group to scope

📁 azure-mcsb-overrides: 📃 management-groups.tf

azurerm_management_group.subscription_ids only returns subscriptions that are direct children of a management group. A subscription under a management group that isn't listed is silently skipped.

When a new management group is added under mg-grinntec, add it here too:

scanned_management_groups = [
  "mg-grinntec",
  "mg-platform",
  "mg-connectivity",
  "mg-identity",
  "mg-landingzones",
  "mg-quarantine",
  "mg-new-group",   # ← new
]

The plan should show the new group's subscriptions being added. Leave mg-sandboxes out on purpose: sandbox subscriptions are short-lived.


Check it worked

On one subscription, read the override straight from the assignment:

az policy assignment show \
  --name SecurityCenterBuiltIn \
  --scope /subscriptions/<sub-id> \
  --query "overrides[0].selectors[0].in"

The output should match local.disabled_mcsb_controls exactly.

In the portal: Defender for Cloud → Recommendations. The control's recommendation drops out of the list, and out of Secure Score, once Defender re-assesses. That takes up to about 24 hours. Seeing it on the day of the merge doesn't mean the change failed.


When it fails

Symptom Cause Fix
Nightly run fails with 404 on …/policyAssignments/SecurityCenterBuiltIn for one subscription Defender for Cloud isn't on for that subscription yet, so the assignment doesn't exist. EPAC psa-sec-defender-001 turns it on, but only after a remediation cycle. Nothing. The next nightly run passes once EPAC has enrolled it. If it keeps failing for days, check the psa-sec-defender-001 remediation for that subscription.
A subscription still shows a disabled recommendation after 24h Its management group isn't in scanned_management_groups, or it's under mg-sandboxes Add the group, or accept it if the subscription is a sandbox.
Recommendation is gone from Secure Score but still fails on the regulatory compliance dashboard Disabled on B only Do step 1.
Compliance dashboard is clean but the recommendation still appears Disabled on A only Do step 2.
Build-DeploymentPlans fails with "unknown Policy name" CSV name column holds the reference ID instead of the policy definition GUID Put the GUID in name and the reference ID in policyDefinitionReferenceId.
Plan wants to change selectors on every subscription when you didn't change the list Someone changed disabled-controls.tf on main without an apply, or the solution ref was bumped Read the diff. If it matches what's on main, let it apply.

Secure Score will move

Disabling a control takes it out of the score calculation, so the score usually goes up after this change. Re-enabling one usually takes it down. Mention the expected effect in the MR so a jump in the weekly governance report isn't mistaken for real drift.