Last reviewed: May 2026
Build the AWS services on the SC-100 exam with plain Terraform — one block at a time, each tied back to an exam domain. The same code works on OpenTofu.
By the end of this lab you'll have provisioned, with plain Terraform, the SC-100 Zero Trust scaffolding — a Conditional Access policy stub via the azuread provider, Microsoft Defender for Cloud + Sentinel onboarded onto a single Log Analytics workspace, an Azure Policy assignment that enforces 'storage accounts must require HTTPS' across the subscription, and a Key Vault for the architecture's secrets layer. This is the SC-100 Design a Zero Trust strategy and architecture domain in five blocks.
Drop the snippets into a single main.tf, run terraform init, then terraform apply step-by-step.
>= 1.5 or OpenTofu >= 1.6.az login).azuread Terraform provider for the Conditional Access policy.The Entra P1 license is the big one — without an existing tenant license, $6/user/month is real. Foundational CSPM and the lab Sentinel data are essentially free.
Standard Azure opener + the azuread provider for Conditional Access.
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
azuread = { source = "hashicorp/azuread", version = "~> 3.0" }
}
}
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = true
}
}
}
provider "azuread" {}
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "current" {}
locals {
tags = {
Project = "certlabpro-sc-100"
ManagedBy = "terraform"
}
}
resource "azurerm_resource_group" "main" {
name = "certlabpro-sc-100-rg"
location = "eastus"
tags = local.tags
}Conditional Access is the SC-100 Zero Trust identity primitive: verify explicitly — every access request gets evaluated against context (user, device, location, app, risk) before being granted. We create a policy in report-only mode that says "for all users accessing all cloud apps, require MFA". Report-only means the policy logs what would happen if enforced — the SC-100-recommended path before flipping a CA policy to enabled.
Real Zero Trust architectures stack many CA policies — risk-based, location-based, device-compliance-based, session-control. This stub is the shape; SC-100's Design a strategy for securing privileged access domain tests the layered-policy composition.
resource "azuread_conditional_access_policy" "require_mfa_report" {
display_name = "certlabpro-sc-100-require-mfa-report-only"
state = "enabledForReportingButNotEnforced"
conditions {
client_app_types = ["all"]
users {
included_users = ["All"]
}
applications {
included_applications = ["All"]
}
}
grant_controls {
operator = "OR"
built_in_controls = ["mfa"]
}
}SC-100 Evaluate Governance, Risk, and Compliance (GRC) technical strategies tests the dual-purpose of these two services: Defender for Cloud is the posture layer (continuous compliance assessment, security recommendations); Sentinel is the detection layer (KQL hunting, incident response). Together they're the Microsoft answer for "how do I architect security observability for a Zero Trust environment?".
We onboard both onto a single Log Analytics workspace — the SC-100 reference architecture for cost efficiency (data lands once, both services read from the same store).
resource "azurerm_log_analytics_workspace" "main" {
name = "log-sc100"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
sku = "PerGB2018"
retention_in_days = 30
tags = local.tags
}
resource "azurerm_security_center_subscription_pricing" "cspm" {
tier = "Free" # Foundational CSPM
resource_type = "CloudPosture"
}
resource "azurerm_sentinel_log_analytics_workspace_onboarding" "main" {
workspace_id = azurerm_log_analytics_workspace.main.id
}Azure Policy is the SC-100 Design a strategy for governance and compliance primitive — it enforces rules across every resource in a scope. We assign the built-in policy Secure transfer to storage accounts should be enabled at subscription scope with effect = Deny, which prevents creation of any new storage account that allows HTTP. The exam tests this DenyAssignment vs Audit distinction: Deny prevents the bad config from happening; Audit only flags it after the fact.
The Zero Trust principle this enforces is encrypt in transit by default — one of the recurring SC-100 exam themes. Real Zero Trust deployments use Policy initiatives (groups of policies) to enforce dozens of controls at once; this is the smallest demonstrable shape.
# Look up the built-in policy definition by name.
data "azurerm_policy_definition" "storage_https" {
display_name = "Secure transfer to storage accounts should be enabled"
}
resource "azurerm_subscription_policy_assignment" "storage_https" {
name = "certlabpro-sc-100-storage-https"
display_name = "Storage accounts must require HTTPS"
description = "Enforces secure transfer (HTTPS-only) on all storage accounts in the subscription."
policy_definition_id = data.azurerm_policy_definition.storage_https.id
subscription_id = data.azurerm_subscription.current.id
# The built-in policy defaults to Audit; force Deny for Zero Trust enforcement.
parameters = jsonencode({
effect = {
value = "Deny"
}
})
}Zero Trust architectures centralize secrets in a hardened vault — RBAC-only, no admin-key shortcuts, soft-delete + purge protection on. We provision the vault with purge protection off for lab cleanup convenience; in production every SC-100-recommended vault has purge_protection_enabled = true to meet the standard 'unrecoverable deletion required' compliance requirements.
With the five blocks in place (Conditional Access for identity, Defender + Sentinel for posture and detection, Azure Policy for guardrails, Key Vault for secrets), the SC-100 Zero Trust scaffolding is shaped. Every additional SC-100 architectural pattern (Privileged Identity Management for just-in-time elevation, Microsoft Purview for data classification, Defender XDR for unified threat protection) layers onto this base.
resource "azurerm_key_vault" "main" {
name = "kv-sc100-${substr(replace(uuid(), "-", ""), 0, 6)}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
enable_rbac_authorization = true
soft_delete_retention_days = 7
purge_protection_enabled = false # set true in production for compliance
tags = local.tags
lifecycle {
ignore_changes = [name]
}
}
resource "azurerm_role_assignment" "kv_admin_self" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azurerm_client_config.current.object_id
}terraform destroy tears down everything. Notes:
https_traffic_only_enabled setting (the policy enforces creation; it doesn't reconfigure existing resources).purge_soft_delete_on_destroy = true in the provider actually purges.SC-100 covers an enormous architectural surface this lab can't fit — Privileged Identity Management (PIM), Microsoft Entra Identity Protection, Microsoft Defender XDR's full integration surface, Microsoft Purview (data governance + risk + compliance manager + Insider Risk + eDiscovery + Information Protection / DLP), Microsoft 365 compliance center, Azure Policy initiatives (groups of policies, the SC-100 production answer), Defender for Cloud's CSPM-Plus paid tier (asset-graph queries, agentless scanning, attack-path analysis), Azure Lighthouse for multi-tenant management, and the entire multi-cloud Defender for Cloud experience (AWS / GCP connectors).
We stick to the Conditional Access + Defender + Sentinel + Azure Policy + Key Vault primitives because they're the substrate every more-advanced SC-100 architecture composes on top of. PIM elevates Entra group memberships that Conditional Access conditions on. Defender XDR enriches the Defender for Cloud alerts. Purview classifies data sitting in Key-Vault-secured storage accounts. Policy initiatives bundle the controls you've defined here into compliance packages.
For service-by-service coverage, see the Browse, Playbook, and Editorial sections of this cert page.