Skip to content

M5 — DLP Policies (M365) — Test Runbook

Asset: M5 — M365 Data Loss Prevention as policy-as-code (policy/dlp/): committed YAML policy definitions + offline gate + idempotent Security & Compliance PowerShell deploy. Owner: CO · Milestone: M4.

Division of labor (read first): M5 covers the M365 content-inspection layer (Exchange/SharePoint/OneDrive/Teams). The cloud-storage half of "DLP" is already enforced by existing Azure assets — public-network deny (N5), encryption (M1), TLS (M3), residency (M6). M5 does not duplicate them.


Part A — Offline gate (~2 min, $0)

cd policy/dlp
./validate.sh                 # or: python3 validate.py
python3 tests/test_validate.py

Acceptance: - [ ] validate.sh reports the 3 seeded policies valid (pii-financial, pii-personal, healthcare-phi). - [ ] The validator unit tests pass, and the broken fixtures under tests/fixtures/broken/ (bad mode, unknown location, no rules, min_count 0) + duplicate-names/ each fail the gate (exit 1) — the gate is fail-closed. - [ ] Schema enforced per policy: name, mode (Enable | TestWithNotifications | TestWithoutNotifications | Disable), locations (Exchange/SharePoint/ OneDrive/Teams), non-empty rules, each sensitive_types[].min_count ≥ 1, unique policy names.


Part B — Policy-set review (~4 min, $0)

  • Audit-first rollout — all three ship in TestWithNotifications (the SnowOps enforce=false→true stance); block_external only triggers on the high-volume rule. PHI (healthcare-phi.yaml) ships enabled: false — opt in for HIPAA clients only.
  • Each policy has a low-volume rule (notify on ≥1) and a high-volume rule (block external + High incident on ≥10) — graduated response, not a blunt block.
  • Sensitive info types are real Purview types (e.g. "Credit Card Number", "International Banking Account Number (IBAN)", SSN/passport, health identifiers).

Part C — Live deploy (requires M365 Compliance admin + licensing)

Requires appropriate M365 compliance licensing (E5 / Compliance add-on) for full DLP, the ExchangeOnlineManagement + powershell-yaml modules, and the Compliance Administrator role.

# Dry run first — prints the plan without connecting/changing anything
./deploy-dlp.ps1 -DryRun

# Real deploy (connects via Connect-IPPSSession)
./deploy-dlp.ps1
  • -DryRun prints each policy + rule it would create/update; no connection made.
  • Real run is idempotentNew-DlpCompliancePolicy when absent, Set-DlpCompliancePolicy when present; re-running makes no further changes.
  • In the Purview compliance portal, the 3 policies appear (PHI disabled) in TestWithNotifications.
  • Send a test email containing a fake card-number-shaped string to an external address → the policy-tip/notification fires and an incident report is generated; external send is blocked once the high-volume threshold is crossed.
  • Soak, then enforce: leave in TestWithNotifications for the agreed soak period (tune false positives), then flip mode: Enable in the YAML, re-validate, and re-deploy.

Notes

  • Terraform/azurerm cannot manage M365 DLP — hence the policy-as-code + PowerShell-deploy shape (same pattern as J5 dashboards-as-code and F8).
  • The YAML is the single source of truth; the portal is never hand-edited (drift would be overwritten on the next deploy).