A complete, integrated Microsoft security architecture — 8 layers, from identity through AI-assisted operations — built as Infrastructure-as-Code, real detection content, and professional documentation. Read docs/PRD.md first: it states plainly what this proves and what it doesn't, before anything else in this repo.
Every Bicep file here is compiled with the real Bicep CLI before being committed — run scripts/validate_bicep.sh yourself to check.
| Phase | Layer | Status |
|---|---|---|
| 1 | Foundation (VNet, NSG, Key Vault) | ✅ Complete |
| 2 | Identity + Endpoint (Entra ID, Conditional Access, Intune, Defender for Identity/Servers) | ✅ Complete |
| 3 | SIEM / SOAR (Sentinel, Logic Apps, KQL, ATT&CK mapping) | ✅ Complete |
| 4 | Cloud & Network Security (Defender for Cloud, Firewall, WAF, Front Door, DDoS) | ✅ Complete |
| 5 | Email, CASB, Data Protection (Defender for O365, Defender for Cloud Apps, Purview) | ✅ Complete |
| 6 | Threat Intelligence & AI (Defender TI, Security Copilot) | ⏳ Planned |
| 7 | Attack Simulations & Runbooks (7 documented scenarios) | ⏳ Planned |
| 8 | Documentation + polish | ⏳ Planned |
What's here:
bicep/foundation/vnet.bicep— VNet (10.10.0.0/16) with 7 subnets, including the exact-name-requiredAzureFirewallSubnetandAzureBastionSubnetthat later phases depend onbicep/foundation/nsg.bicep— workload NSG, HTTPS allowed, RDP/SSH restricted to Bastion-only, direct-from-internet RDP/SSH explicitly deniedbicep/foundation/keyvault.bicep— Key Vault with RBAC authorization (not legacy access policies), soft-delete + purge protection enabled, network access denied by defaultbicep/foundation/main.bicep— orchestrates all three, verified to link correctlyarchitecture/diagrams/hld.svg— full 8-layer high-level designarchitecture/diagrams/lld-foundation.svg— low-level design for this phase specifically, matching the Bicep output exactly (subnet CIDRs, NSG rule priorities, Key Vault settings — nothing in the diagram that isn't in the code)docs/PRD.md— objective, scope, honest positioning, success criteria
Verified, not just written:
bash scripts/validate_bicep.sh✅ PASS — bicep/foundation/keyvault.bicep
✅ PASS — bicep/foundation/main.bicep
✅ PASS — bicep/foundation/nsg.bicep
✅ PASS — bicep/foundation/vnet.bicep
4/4 Bicep files compiled clean.
Zero linter warnings across all 4 files as well.
What's here:
bicep/identity/defender-for-identity.bicep— enables Defender for Identity at subscription scopebicep/endpoint/defender-for-servers.bicep— enables Defender for Servers Plan 2 (the plan that actually includes EDR — P1 doesn't)conditional-access/— 3 real Microsoft Graph-schema Conditional Access policies (require MFA, block legacy auth, require compliant device for privileged roles) plus a PowerShell deployment script. Documented distinctly from the Bicep resources above: Conditional Access lives in Microsoft Graph/Entra ID, a different control plane from Azure Resource Manager — that distinction is stated, not blurred for a cleaner storyintune/windows-compliance-baseline.json— the compliance policy CA003 actually depends on (BitLocker, Secure Boot, Defender status, password policy)detections/kql/— 2 KQL detections for this phase: privileged sign-in from a non-compliant device, and Defender tamper/AV-disable attempts (T1562.001)architecture/diagrams/lld-identity-endpoint.svg— low-level design for this phase
A real correction made during this phase, not glossed over: Defender plan resources (Microsoft.Security/pricings) initially failed to compile at resource-group scope — the Bicep CLI rejected it with error BCP135, which is how the subscription-scope requirement was caught and fixed, not assumed correct from the start.
Verified, not just written:
bash scripts/validate_bicep.sh✅ PASS — bicep/endpoint/defender-for-servers.bicep
✅ PASS — bicep/endpoint/main.bicep
✅ PASS — bicep/foundation/keyvault.bicep
✅ PASS — bicep/foundation/main.bicep
✅ PASS — bicep/foundation/nsg.bicep
✅ PASS — bicep/foundation/vnet.bicep
✅ PASS — bicep/identity/defender-for-identity.bicep
✅ PASS — bicep/identity/main.bicep
8/8 Bicep files compiled clean.
All 4 JSON policy files (3 Conditional Access + 1 Intune) parse and validate against their documented Microsoft Graph schema fields.
What's here:
bicep/siem-soar/sentinel-workspace.bicep— Log Analytics workspace + Sentinel onboarding deployed together (Sentinel is a solution enabled on a workspace, not a standalone resource — the module reflects that instead of treating them as separate layers)detections/kql/— 17 more KQL detections added this phase (19 total across the project), spanning identity, endpoint, cloud/container, email, and data-protection sources — because Sentinel is the central SIEM regardless of which layer a signal originates fromdetections/generate_attack_coverage.py— parses every rule's ATT&CK header and generates a real Navigator layer, run and verified: 15 techniques covered, 2 rules intentionally excluded and named (one uses a NIST CSF tag instead since it detects posture drift, not an attack technique; one is a Conditional-Access control-effectiveness check)soar/playbooks/— 2 real Logic App templates:playbook-enrich-and-respond.json— the documented enrichment chain (VirusTotal → AbuseIPDB → Whois → Teams → ServiceNow), with device isolation gated behind confirmed-malicious enrichment results, not the raw incident aloneplaybook-disable-compromised-user.json— gated on the specific source detection rule (password spray / lateral movement / guest privilege escalation), not any generic incident, matching the "Impossible Travel → Disable User" scenario from the original brief
Verified, not just written:
bash scripts/validate_bicep.sh # 10/10 Bicep files compiled clean
python3 detections/generate_attack_coverage.py # 15 techniques extracted, real Navigator layer writtenBoth SOAR playbook JSON files parse and validate; both are deliberately gated on high-confidence conditions rather than auto-acting on a raw incident, matching the "human-in-the-loop for destructive actions" discipline used throughout the rest of this profile's SOAR work.
What's here:
bicep/cloud-network-security/firewall-rules.bicep— Azure Firewall Premium, deployed into the exactAzureFirewallSubnetreserved back in Phase 1 (this is what that reservation was for). Policy-based rule management with real network + application rule collections, threat intel in Alert-only mode as the starting posture — same "don't hard-block on day one" discipline used for Conditional Access in Phase 2front-door-waf.bicep— Front Door Premium with a WAF policy running Microsoft's Default Ruleset (OWASP coverage) in Prevention mode — a deliberately different posture than the Firewall's Alert-only threat intel, reasoned about explicitly in the file's comments, not just copy-pastedddos-protection.bicep— DDoS Protection Plan, with the VNet-association step correctly identified as living on the Phase 1 VNet resource rather than modeled as a separate (nonexistent) attachment resourcebastion.bicep— Azure Bastion into the reservedAzureBastionSubnet— the actual service that makes Phase 1's "RDP/SSH from Bastion subnet only" NSG rules meaningful, rather than a rule pointing at nothingdefender-for-cloud.bicep— Defender for Storage (with on-upload malware scanning, wired to Phase 3'sstorage-malware-upload.kql), SQL, Containers, and the CSPM plan tier
Two orchestrators, not one — and that's deliberate: main.bicep (resource-group scope: Firewall, Front Door, DDoS, Bastion) and main-defender.bicep (subscription scope: Defender plans), matching the exact scope split Azure itself enforces — the same reasoning already established in Phase 2 for identity/endpoint.
Bugs the real Bicep CLI actually caught during this build (kept here rather than quietly fixed and forgotten):
- Two hardcoded-URL linter warnings on the Firewall's allow-listed management FQDNs — properly suppressed with a documented reason rather than reworded to dodge the linter
- An unused
locationparameter on the Front Door module — removed outright once traced to Front Door resources all beingGlobalscope, not resource-group-scoped - An unused
vnetIdparameter on the DDoS module — fixed by actually threading it through to an output instead of leaving dead code
Verified, not just written:
bash scripts/validate_bicep.sh # 17/17 Bicep files compiled clean, project-wide# Phase 1 — resource-group scoped
az group create --name rg-mse-platform --location eastus
az deployment group create \
--resource-group rg-mse-platform \
--template-file bicep/foundation/main.bicep
# Phase 2 — subscription scoped (Defender plans apply subscription-wide)
az deployment sub create --location eastus --template-file bicep/identity/main.bicep
az deployment sub create --location eastus --template-file bicep/endpoint/main.bicep
# Conditional Access + Intune (Microsoft Graph, not Bicep)
pwsh conditional-access/deploy-conditional-access.ps1
# Phase 3 — resource-group scoped (Sentinel workspace)
az deployment group create \
--resource-group rg-mse-platform \
--template-file bicep/siem-soar/main.bicep
# Logic Apps playbooks require an existing Sentinel workspace + API connections —
# see soar/playbooks/*.json "metadata.note" for the exact permissions each needs
# Phase 4 — resource-group scoped (Firewall, Front Door, DDoS, Bastion) —
# requires Phase 1's subnet/VNet output IDs as parameters
az deployment group create \
--resource-group rg-mse-platform \
--template-file bicep/cloud-network-security/main.bicep \
--parameters firewallSubnetId=<phase1-output> bastionSubnetId=<phase1-output> vnetId=<phase1-output>
# Phase 4 — subscription scoped (Defender for Storage/SQL/Containers/CSPM)
az deployment sub create --location eastus --template-file bicep/cloud-network-security/main-defender.bicep
# Phase 5 — no Bicep; three separate deployment surfaces
pwsh email-security/deploy-email-security.ps1
python3 casb/deploy_casb_policies.py # set CLOUDAPPS_TENANT_URL + CLOUDAPPS_API_TOKEN to deploy for real
pwsh data-protection/deploy-purview-policies.ps1No subscription was available at build time — see docs/PRD.md Section 4 for the exact, honest boundary on what that means for what this repo does and doesn't prove.
A deliberately different deployment shape than Phases 1-4 — worth explaining directly, since it's a real architectural fact, not a gap: none of Defender for Office 365, Defender for Cloud Apps, or Purview are ARM resources. They're licensed M365/Security-portal-configured products with their own control planes, so this phase has zero Bicep files and that's correct, not incomplete.
What's here, across three genuinely distinct deployment surfaces:
| Layer | Deployment surface | Files |
|---|---|---|
| Email security (Defender for O365) | Exchange Online PowerShell | email-security/ — Safe Links (click-through disabled, not just warned), Safe Attachments (dynamic delivery, fail-closed on scan error) |
| CASB (Defender for Cloud Apps) | Cloud Apps REST API | casb/ — anomaly detection (impossible travel/mass download for SaaS apps) + OAuth app governance (flagged for review, never auto-revoked) |
| Data protection (Purview) | Security & Compliance PowerShell | data-protection/ — 4-tier sensitivity label taxonomy + a DLP policy that actually references those labels, deployed in the correct dependency order |
Each policy ties back to a specific detection, not built in isolation:
- Safe Links →
safelinks-clickthrough-despite-warning.kql(the policy blocks outright; the detection catches the edge case where it didn't) - CASB anomaly policy →
identity-lateral-movement-enriched.kql(same lateral-movement concept, SaaS-app side instead of Azure AD sign-in side) - Purview DLP →
purview-confidential-file-external-share.kql(policy blocks the share; detection catches labeling gaps)
Verified, not just written:
python3 casb/deploy_casb_policies.py # dry-run, prints the exact request each policy would sendBoth CASB policies print a real, correctly-shaped API request in dry-run mode. All 7 new JSON files across email-security/casb/data-protection parse and validate.
Not yet built — Defender Threat Intelligence integration and Security Copilot use-case documentation (KQL generation, incident summarization, MITRE mapping).
MIT — see LICENSE.