FedRAMP Landing Zone on Azure

Build a governed Azure landing zone with Terraform, Azure Policy, and Prowler.

Introduction

30 Second Summary

Every federal agency moving to the cloud needs a secure foundation before a single workload goes live. That foundation is called a landing zone, and building one is the most in-demand skill in government cloud engineering.

In this project, you will build a FedRAMP-ready landing zone on Azure using Terraform, scan it with an open-source compliance tool called Prowler, and draft a System Security Plan that maps your infrastructure to NIST 800-53 controls.

What You'll Build

A lab-scale FedRAMP-ready landing zone on Azure with production-pattern Terraform code, a compliance scan report, and a draft System Security Plan.

By the end of this project, you'll have:

  • A Terraform-managed Azure Management Group hierarchy with two Azure Policy assignments enforcing region restrictions and denying public blob access.
  • A Prowler CIS Azure Benchmark compliance scan report showing pass/fail results across 160+ security checks.
  • A markdown SSP draft mapping CIS Azure findings to NIST 800-53 control families with calculated control-coverage percentages, generated by Cursor Composer 2 from your actual code and scan output.
  • A secret mission where you add encryption-at-rest enforcement, re-scan, and measure the improvement in your compliance posture.

Are there any prerequisites?

You need Cursor IDE (Pro plan), Terraform CLI, Azure CLI (logged in), Python 3.10+ with pipx, and an Azure subscription with permissions to create Management Groups and Policy assignments.

Prowler installation is covered in Step 1, so you do not need it installed beforehand.

Install Prowler and Set Up Your Project

Before you can build a landing zone, you need two things: a compliance scanner to validate your work and a Terraform project to define your infrastructure as code.

Prowler is an open-source security tool that runs 160+ checks against your Azure subscription. Cursor Composer 2 will scaffold your entire Terraform project in one prompt, generating diffs across multiple files that you review and accept.

In this step, you will install Prowler, verify your Azure CLI session, and use Composer to create a production-pattern Terraform project structure.

In This Step, Get Ready To:
  • Install Prowler and verify your Azure CLI authentication.
  • Scaffold a multi-file Terraform project with Cursor Composer.
  • Initialize Terraform and download the azurerm provider.
Install Prowler via Pipx

Prowler requires Python 3.10 through 3.12 to run. Using pipx installs Prowler in its own isolated Python environment so it does not conflict with other packages on your system.

  • Open your terminal and run the following command to install Prowler.
pipx install prowler
  • Verify the installation by checking the version.
prowler -v

I see a version number

You should see something like Prowler 5.x.x. You are good to go.

I see an error

If you see command not found, make sure pipx is installed and its bin directory is on your PATH.

  • Run pipx ensurepath and restart your terminal.
  • Try prowler -v again.

Still seeing command not found?

If you are still stuck, ask for help debugging your Prowler installation.

Now verify that your Azure CLI session is active. Prowler uses your stored Azure CLI credentials to authenticate against your subscription.

  • Run the following command to confirm your subscription.
az account show

What Should I See?

You should see a JSON object with your subscription name, ID, and state showing "Enabled". If you see an error, run az login first to authenticate.

Why Does Prowler Need Azure CLI Auth?

Prowler reads your Azure resources to check them against security benchmarks. The --az-cli-auth flag tells Prowler to reuse your existing Azure CLI credentials. This is the simplest authentication method for a lab environment. No Service Principal setup required.

Create the Project Folder and Scaffold With Composer

A well-structured Terraform project separates concerns into dedicated files. Instead of creating each file manually, you will use Cursor Composer to generate the entire project structure in one prompt.

  • Create a folder called azure-landing-zone and navigate into it.
mkdir azure-landing-zone
cd azure-landing-zone
  • Open the folder in Cursor by running cursor . or by opening Cursor and selecting File then Open Folder.
  • Open Composer by pressing Cmd+I (macOS) or Ctrl+I (Windows).
  • Enter the following prompt into Composer.
Create a Terraform project structure for an Azure landing zone. I need: main.tf, variables.tf, outputs.tf, providers.tf, and a policies/ folder with placeholder files for allowed-regions.json and deny-public-blob.json. Use azurerm provider ~> 4.0 and require Terraform >= 1.12.

What Does This Prompt Do?

Composer generates inline diffs across all the files it creates. You will see green highlighted lines for new code. Review each file to understand the structure before accepting.

The providers.tf file pins the azurerm provider to version ~> 4.0. The policies/ folder will hold JSON policy rule files that you reference later when assigning Azure Policies.

  • Review the diffs Composer generates across all files.
  • Click Accept All to apply the changes.

Why Use Composer for Scaffolding?

Composer 2 can generate and edit multiple files in a single pass. For a Terraform project with 5+ files, this saves significant setup time compared to a standard chat interface where you would copy-paste code into each file one by one.

Initialize Terraform

Terraform needs to download the Azure provider plugin before it can manage any resources. The terraform init command reads your providers.tf file and downloads the specified provider version.

  • Open the integrated terminal in Cursor by pressing Ctrl+`.
  • Run the following command.
terraform init

What Should I See?

You should see output showing Terraform downloading the hashicorp/azurerm provider, followed by a green message: Terraform has been successfully initialized!

Pro tip: Agent mode

If Composer is in Agent mode, you can prompt it to run terminal commands directly. Try typing "Run terraform init in the terminal" into Composer. Agent mode executes the command and shows you the output without switching windows.

Terraform initialized successfully

Your project is ready. You now have a .terraform directory containing the downloaded provider plugin and a .terraform.lock.hcl file that locks the provider version.

I see an error

The most common issue is a version mismatch in providers.tf. Make sure the file specifies azurerm ~> 4.0 and required_version >= 1.12.

  • Check your Terraform version by running terraform -v.
  • If your version is below 1.12, update Terraform before continuing.

Still seeing an error?

If you are still stuck, ask for help debugging terraform init.

Your project is scaffolded and Terraform is initialized. Next up, you will use Composer to define the Management Group hierarchy that forms the governance backbone of your landing zone.

Build the Management Group Hierarchy

In the previous step, you installed Prowler, scaffolded your Terraform project, and initialized the azurerm provider. Now it is time to build the governance backbone of your landing zone.

Federal cloud environments need a hierarchy that separates shared infrastructure from application workloads. Azure Management Groups provide this hierarchy. They let you apply governance policies once at the top and have them cascade down to every subscription underneath. In this step, you will create that hierarchy and attach two Azure Policy assignments that enforce NIST 800-53 controls before any workload is deployed.

In This Step, Get Ready To:
  • Create a three-level Management Group hierarchy (root, Platform, and Workloads) using Cursor Composer.
  • Assign an allowed-regions policy that restricts deployments to eastus and westus2 (NIST 800-53 CM-7).
  • Assign a deny-public-blob policy that prevents public access on Storage Account containers (NIST 800-53 SC-7).
Define the Management Group Structure With Composer

A Management Group is a container above subscriptions in Azure's resource hierarchy. By nesting Management Groups, you create an organizational tree. Any policy you assign to a parent group automatically inherits down to every child group and subscription beneath it.

Your hierarchy will have three groups: a root group called "FedRAMP-Lab," and two children underneath it. "Platform" is for shared infrastructure like logging and networking. "Workloads" is for application teams.

  • Open Composer with Cmd+I (macOS) or Ctrl+I (Windows).
  • Enter the following prompt:
In main.tf, create an Azure Management Group hierarchy. Create a root group called 'FedRAMP-Lab' with display_name 'FedRAMP Lab Root'. Under it, create two child groups: 'Platform' (for shared infrastructure) and 'Workloads' (for application teams). Use azurerm_management_group resources. Add the current subscription to the Workloads group. Reference variables for the group names so they are configurable.
  • Review the inline diffs Composer generates across main.tf and variables.tf.
  • Accept the changes.

What is Composer doing here?

Composer reads your existing project files and generates azurerm_management_group resources with parent_management_group_id references to create the parent-child relationships. It also adds matching variable definitions in variables.tf so you can rename the groups later without editing resource blocks directly.

Why Azure Policy Instead of Service Control Policies?

If you have worked with AWS, you might expect Service Control Policies (SCPs) for governance guardrails. Azure does not have SCPs. The equivalent is Azure Policy, which can be assigned at the Management Group level and inherited by all child subscriptions.

Azure Policy can both audit non-compliant resources and actively deny their creation. This makes it the correct Azure-native pattern for the governance guardrails you are about to deploy.

Define and Assign the Allowed-Regions Policy

Your first policy enforces geographic boundaries. In a FedRAMP environment, agencies must control where data is stored and processed. Restricting deployments to specific regions maps directly to NIST 800-53 control CM-7 (Least Functionality) by limiting the attack surface to only approved locations.

  • Open Composer and enter the following prompt:
Create an Azure Policy assignment that restricts resource deployment to only 'eastus' and 'westus2'. Use the built-in policy definition 'Allowed locations' (ID: e56962a6-4747-49cd-b67b-bf8b01975c4c). Assign it to the FedRAMP-Lab root Management Group so it applies to all child groups. Put the policy assignment in main.tf. This policy maps to NIST 800-53 control CM-7 (Least Functionality) by limiting where resources can be created.
  • Review the diffs Composer generates in main.tf.
  • Accept the changes.

What is a built-in policy definition?

Azure ships hundreds of pre-written policy definitions that you can assign without writing any custom JSON. The "Allowed locations" policy (ID e56962a6-4747-49cd-b67b-bf8b01975c4c) is one of the most commonly used. You reference it by its ID in your Terraform code, then pass parameters like which regions to allow.

Define and Assign the Deny-Public-Blob Policy

Your second policy prevents data exposure. Publicly accessible blob containers are one of the most common cloud security incidents. This policy maps to NIST 800-53 control SC-7 (Boundary Protection) by preventing public network exposure of storage data.

  • Open Composer and enter the following prompt:
Create a second Azure Policy assignment that denies public access on Azure Storage blob containers. Use the built-in policy definition 'Storage account public access should be disallowed' (ID: 4fa4b6c0-31ca-4c0d-b10d-24b96f62a751). Assign it to the FedRAMP-Lab root Management Group. This policy maps to NIST 800-53 control SC-7 (Boundary Protection) by preventing public network exposure of storage data.
  • Review the diffs in main.tf.
  • Accept the changes.

Unverified Policy Definition ID

The policy definition ID 4fa4b6c0-31ca-4c0d-b10d-24b96f62a751 for "Storage account public access should be disallowed" has not been independently verified. If Terraform throws an error during terraform plan, you can look up the correct ID by running:

az policy definition list --query "[?contains(displayName, 'public access')].{name:name, displayName:displayName}" --output table

Replace the ID in your Terraform code with the correct one from the output.

At this point, your main.tf should contain three Management Group resources and two policy assignment resources. You have not deployed anything yet. That happens in the next step after you add audit logging.

Your governance hierarchy is defined. Next up, you will add centralized audit logging and deploy everything to Azure with terraform apply.

Enable Centralized Audit Logging

Your Management Groups and Policy assignments define what can happen in your landing zone. But without logging, you have no evidence that your controls are working. Federal auditors need proof that every control-plane operation is captured and stored.

In this step, you will create a centralized audit log pipeline. Azure's Activity Log records every management operation in your subscription. You will export those events to a dedicated Storage Account so they are preserved beyond the default 90-day window and available for compliance evidence.

In This Step, Get Ready To:
  • Create a resource group and Storage Account for centralized audit logs.
  • Configure a diagnostic setting that exports your subscription's Activity Log to the Storage Account.
  • Deploy the full landing zone with Terraform and verify all resources in Azure.
Create a Storage Account for Audit Logs

NIST 800-53 control AU-4 (Audit Log Storage Capacity) requires that your system allocates enough storage to hold audit records. A dedicated Storage Account in its own resource group keeps your compliance evidence isolated from application data.

  • Open Composer with Cmd+I (macOS) or Ctrl+I (Windows).
  • Enter the following prompt:
Add a resource group called 'rg-fedramp-logging' in eastus, and a Storage Account called 'stfedrampauditlogs' (or a variable-based name) with Standard_LRS replication. Enable blob versioning. This storage account will hold Activity Log exports. These resources map to NIST 800-53 control AU-4 (Audit Log Storage Capacity).
  • Review the diffs Composer generates across main.tf and variables.tf.
  • Accept the changes.

Why blob versioning?

Blob versioning preserves previous versions of your audit log files. If a log file is overwritten or deleted, the previous version is still recoverable. This supports NIST 800-53 control AU-9 (Protection of Audit Information) by making audit records tamper-evident.

Create a Diagnostic Setting for the Activity Log

The Activity Log captures every control-plane operation in your Azure subscription. Creating resources, modifying policies, changing role assignments. By default, these events are retained for 90 days and then discarded. A diagnostic setting exports these events to your Storage Account so they are preserved for as long as you need them.

This maps to two NIST 800-53 controls. AU-2 (Audit Events) requires that you define which events to capture. AU-3 (Content of Audit Records) requires that each record contains enough detail to establish what happened, when, and who was responsible.

  • Open Composer and enter the following prompt:
Add an azurerm_monitor_diagnostic_setting resource that exports the subscription-level Activity Log to the Storage Account we just created. Enable the 'Administrative', 'Security', 'Policy', and 'Alert' log categories. This maps to NIST 800-53 controls AU-2 (Audit Events) and AU-3 (Content of Audit Records) by ensuring all control-plane operations are captured and stored centrally.
  • Review the diffs in main.tf.
  • Accept the changes.

What are these four log categories?

  • Administrative captures create, update, and delete operations on resources.
  • Security captures alerts from Microsoft Defender for Cloud.
  • Policy captures Azure Policy evaluation events, including deny actions from your allowed-regions and deny-public-blob policies.
  • Alert captures Azure Monitor alert activations.

Together, these four categories give auditors a complete picture of who did what in your subscription.

Deploy Everything With Terraform

Your Terraform configuration now includes Management Groups, Policy assignments, a Storage Account, and a diagnostic setting. Time to deploy the full landing zone.

  • Run terraform plan in the Cursor integrated terminal.
terraform plan

The plan output shows every resource Terraform will create. You should see the three Management Groups, two Policy assignments, one resource group, one Storage Account, and one diagnostic setting.

  • Review the plan carefully. Confirm no unexpected resources are being created or destroyed.
  • Run terraform apply to deploy.
terraform apply -auto-approve

What does -auto-approve do?

The -auto-approve flag skips the interactive confirmation prompt. In a lab environment this saves time. In production, you would always review the plan before approving.

Terraform applies the configuration and outputs the results. Watch for any errors in the output.

  • Verify the Management Groups were created by running the following command.
az account management-group list --output table

What should I see?

The output should list your three Management Groups: FedRAMP-Lab, Platform, and Workloads. You can also verify the Storage Account and diagnostic setting in the Azure Portal by navigating to Monitor > Activity log > Export Activity Logs.

Seeing permission errors?

Management Group creation requires the Management Group Contributor role at the tenant root level. Policy assignments require Resource Policy Contributor. If you see authorization errors, confirm your Azure account has these permissions by running az role assignment list --assignee $(az ad signed-in-user show --query id -o tsv). Help me troubleshoot my Azure permissions for Management Groups and Policy assignments.

Your landing zone now has governance guardrails and an audit trail. Every resource creation, policy evaluation, and security alert is flowing to your centralized Storage Account. Next up, you will run a Prowler compliance scan to see how your landing zone measures up against the CIS Azure Benchmark.

Run the Prowler Compliance Scan

Your landing zone is live. Management Groups are governing your subscription hierarchy, Azure Policies are enforcing region restrictions and blocking public blob access, and audit logs are flowing to centralized storage. But how do you prove it works? In this step, you'll run an automated compliance scan using Prowler to benchmark your Azure environment against 160+ security checks and identify exactly where your compliance gaps are.

In This Step, Get Ready To:
  • Run a Prowler CIS Azure Benchmark scan against your Azure subscription.
  • Review the HTML compliance report and identify your top compliance gaps.
  • Use Cursor Composer to analyze the raw scan results and summarize the most critical failures.
Run Prowler Against Your Azure Subscription

Prowler uses your stored Azure CLI credentials to authenticate and scan your subscription. The --az-cli-auth flag tells Prowler to use the same identity you logged in with via az login, so no Service Principal setup is needed for this lab.

  • In the Cursor integrated terminal, run the following command:
prowler azure --az-cli-auth --output-directory ./prowler-output

What does this command do?

This command runs all default CIS Azure Benchmark checks against your Azure subscription. That is 160+ individual security checks spanning compute, storage, networking, IAM, and more. The --output-directory flag tells Prowler where to save the results.

Prowler generates CSV, JSON-OCSF, and HTML reports automatically in the prowler-output folder.

The scan takes 3-5 minutes. You will see checks scrolling through the terminal as Prowler evaluates each one. Some checks will show PASS in green and others will show FAIL in red.

Why CIS Azure Benchmark Instead of NIST 800-53?

Prowler's NIST 800-53 and FedRAMP compliance frameworks are currently AWS-only. There are no Azure-specific variants published. For Azure, Prowler supports CIS Azure Benchmarks (v2.0-v4.0), ISO 27001, NIST CSF, and Microsoft NIS2 Directive.

The CIS Azure Benchmark maps closely to many NIST 800-53 controls. Mapping CIS findings to NIST 800-53 control families is exactly what real Azure compliance teams do day-to-day. You will do this crosswalk yourself in the next step.

Seeing Permission Errors?

Prowler requires at minimum the Reader role on your subscription. Some Entra ID checks also need Microsoft Graph permissions like Directory.Read.All and Policy.Read.All. If certain checks fail due to permissions, that is expected in a lab environment. The scan will continue past those checks.

If the scan fails to start entirely, help me troubleshoot my Prowler Azure authentication.

Review the Prowler HTML Report

Once the scan finishes, Prowler creates several report files. The HTML report gives you a visual dashboard of your compliance posture at a glance.

  • Open the prowler-output folder in your file explorer.
  • Find the HTML file (it will have a name like prowler-output-<timestamp>.html).
  • Open it in your browser.

The summary dashboard shows pass/fail counts broken down by Azure service category. Scroll through the results and note which categories have the most failures. These are your compliance gaps.

What should I see?

You should see a mix of PASS and FAIL results. Many checks will fail because this is a lab environment without production-grade configurations like key vaults, network security groups, or advanced threat protection. That is completely normal. The failures are your roadmap for hardening the environment.

Use Composer to Analyze the Results

The HTML report gives you a visual overview, but the CSV file contains the raw data. Cursor Composer can read this file directly from your project and produce a structured analysis.

  • Open Composer with Cmd+I (macOS) or Ctrl+I (Windows).
  • Enter the following prompt, replacing the file reference with the actual CSV filename in your prowler-output folder:
Read @prowler-output/<your-csv-filename>.csv. Summarize the findings:
how many checks passed, how many failed, and which Azure service
categories have the most failures. List the top 5 most critical failures.

Pro tip: use @ references

When you type @ in Composer, it shows a list of files and folders in your project. Select the CSV file from the prowler-output folder so Composer reads the actual scan data. This is how Composer generates analysis grounded in your real results, not generic advice.

Composer reads the file directly from your project and produces a structured summary. Review the output. You now have a clear picture of your compliance posture: what is passing, what is failing, and where to focus your hardening efforts.

Why Does This Matter for FedRAMP?

In a real FedRAMP authorization, you need to provide evidence that your cloud environment meets specific security controls. Automated scan reports like this one serve as that evidence. The next step takes this further by mapping these findings to the actual NIST 800-53 control families that FedRAMP requires.

You now have automated compliance evidence for your landing zone. Next up, you will map these findings to NIST 800-53 control families and draft a System Security Plan.

Map Findings to NIST 800-53 and Draft the SSP

You have a Prowler scan report full of pass/fail results, but a compliance officer does not care about CIS Azure Benchmark check IDs. They care about NIST 800-53 control families. In this step, you will bridge that gap by mapping your CIS findings to NIST 800-53 Rev 5 control families and drafting a System Security Plan (SSP) that a non-technical reviewer can actually read.

In This Step, Get Ready To:
  • Generate a NIST 800-53 crosswalk file that maps CIS Azure findings to control families with coverage percentages.
  • Draft plain-English SSP narratives for every implemented control family, referencing your actual Terraform resources.
  • Refine the SSP draft to a reading level suitable for non-technical reviewers.
Generate the NIST 800-53 Crosswalk

Prowler's Azure compliance frameworks use CIS Azure Benchmarks, not NIST 800-53 directly. There is no Azure-specific NIST 800-53 or FedRAMP framework published for Prowler. The crosswalk from CIS to NIST 800-53 is a realistic exercise that mirrors what real compliance teams do day-to-day.

Why CIS Azure Instead of NIST 800-53 Directly?

Prowler's NIST 800-53 and FedRAMP compliance frameworks are currently AWS-only (e.g., nist_800_53_revision_5_aws). For Azure, Prowler supports CIS Azure Benchmarks (v2.0-v4.0), ISO 27001, NIST CSF, and Microsoft NIS2 Directive. CIS Azure Benchmark checks map closely to many NIST 800-53 controls, so building the crosswalk yourself is the standard approach for Azure compliance work.

  • Open Composer with Cmd+I (macOS) or Ctrl+I (Windows).
  • Enter the following prompt:
Using @prowler-output/ (the CSV results) and @main.tf, create a file called nist-crosswalk.md. Map each CIS Azure Benchmark finding to the corresponding NIST 800-53 Rev 5 control family. Group results by control family (AC, AU, CM, IA, SC, SI, etc.). For each family, calculate the percentage of checks that passed. Show a summary table at the top with family name, total checks, passed, failed, and coverage percentage.

What Does This Prompt Do?

Composer reads your actual Prowler CSV results and your Terraform code using the @file context references. It then maps each CIS check to the NIST 800-53 control family it satisfies. The output is a markdown file with a summary table showing your control-coverage percentage per family.

  • Review the generated nist-crosswalk.md file. Confirm it contains a summary table at the top with columns for family name, total checks, passed, failed, and coverage percentage.
  • Accept the diffs once the file looks complete.

You should see control families like AC (Access Control), AU (Audit and Accountability), CM (Configuration Management), IA (Identification and Authentication), SC (System and Communications Protection), and SI (System and Information Integrity) represented in the table. Some families will have high coverage from your policies and diagnostic settings. Others will show gaps because this is a lab-scale landing zone, not a full production environment.

Draft SSP Narratives for Implemented Controls

A System Security Plan is the core document in any FedRAMP authorization package. It describes how your system implements each required security control. Real SSPs must use the FedRAMP-provided template for each baseline (Low, Moderate, High). For this lab, you will generate a markdown draft that follows the same structure.

  • Open Composer and enter the following prompt:
Using @nist-crosswalk.md and @main.tf, create a file called ssp-draft.md. For each NIST 800-53 control family where we have passing checks, write a plain-English narrative describing how our landing zone implements that control. Reference specific Terraform resources by name. For example, for AU-2, describe how azurerm_monitor_diagnostic_setting captures Administrative, Security, Policy, and Alert events and stores them in the centralized Storage Account. Write at a 7th-grade reading level. A non-technical reviewer should understand each paragraph.

What Is a 7th-Grade Reading Level?

FedRAMP SSP narratives need to be understood by auditors, program managers, and authorizing officials who may not be engineers. Writing at a 7th-grade reading level means short sentences, common words, and no unexplained jargon. If you mention a technical term, define it in the same sentence.

  • Review the generated ssp-draft.md file.
  • Confirm that each narrative references specific Terraform resource names from your main.tf.
  • Check that the AU-2 narrative mentions the diagnostic setting capturing Administrative, Security, Policy, and Alert log categories.
  • Check that the CM-7 narrative mentions the allowed-regions policy restricting deployment to eastus and westus2.
  • Check that the SC-7 narrative mentions the deny-public-blob policy preventing public network exposure of storage data.
  • Accept the diffs once the narratives are complete.
Refine the SSP Draft

The first draft is rarely perfect. Real-world SSP writing follows a cycle of draft, review, and refine. You will now pick one narrative that feels too technical and ask Composer to rewrite it for a non-technical audience.

  • Read through ssp-draft.md and identify one narrative that uses too much jargon or feels hard to follow.
  • Open Composer and enter a refinement prompt. Here is an example targeting the CM-7 narrative:
Rewrite the CM-7 narrative in ssp-draft.md for a hiring manager who has never used Azure. Explain what 'allowed regions' means in one sentence before describing the policy.

Pro tip: iterate more than once

You can run multiple refinement passes. Try rewriting the SC-7 or AU-2 narratives too. Each pass makes the document more readable. This iterative workflow is exactly how compliance teams produce SSPs that pass review.

  • Review the rewritten narrative. Confirm it defines technical terms before using them.
  • Accept the diffs once you are satisfied with the clarity.

Your project folder now contains three key deliverables: your Terraform code in main.tf, your compliance evidence in nist-crosswalk.md, and your draft authorization document in ssp-draft.md. This is the full lifecycle of federal cloud authorization work: build, scan, document.

You have infrastructure as code, automated compliance evidence, and a draft SSP all generated from the same project. Next up, take on the Secret Mission to add encryption-at-rest enforcement and watch your control-coverage percentage climb.

Secret mission

Add Encryption-at-Rest Enforcement

Your landing zone enforces region restrictions and blocks public blob access. But what about data at rest? In this secret mission, you'll add a third Azure Policy that enforces encryption for all Storage Accounts, re-run Prowler to measure the improvement, and update your SSP draft with a new SC-28 narrative.

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Management Groups and Policy assignments are free. The Storage Account incurs minimal charges (pennies) only if data is written to it, so there is no meaningful cost impact from leaving these resources in place.

Resources you used:

  • 3 Azure Management Groups (FedRAMP-Lab, Platform, Workloads).
  • 2 Azure Policy assignments (allowed-regions, deny-public-blob).
  • 1 Resource Group (rg-fedramp-logging).
  • 1 Storage Account (for audit logs).
  • 1 Diagnostic Setting (Activity Log export).
  • Prowler installed via pipx (local tool).
  • Local project folder azure-landing-zone with Terraform files, Prowler output, and SSP draft.

Keep everything running

No action needed. Choose this if you plan to continue experimenting with Azure Policy assignments or want to build on this landing zone for future projects.

  • Your Management Groups and Policy assignments remain active and will govern any future resources deployed in this tenant.
  • The Storage Account will continue to receive Activity Log exports. Storage costs are negligible for a lab environment.
  • Your local Prowler installation, Terraform files, and SSP draft remain on your machine.

Pause - I'll come back to this later

These Azure resources do not have a running or stopped state, so there is nothing to pause on the cloud side. Management Groups and Policy assignments are free and remain in place until explicitly deleted.

  • Keep your azure-landing-zone project folder intact so you can pick up where you left off.
  • Your Terraform state file tracks all deployed resources. When you return, run terraform plan to see the current state.
  • If you want to remove only the Storage Account to avoid any minor storage charges, you can delete it manually in the Azure Portal while keeping the Management Groups and Policies.

Delete - I don't want to use this again

Remove all Azure resources created during this project and clean up your local environment.

  • In the Cursor terminal, navigate to your azure-landing-zone folder and run terraform destroy. Confirm when prompted. This removes all Management Groups, Policy assignments, the Resource Group, the Storage Account, and the Diagnostic Setting.
  • To uninstall Prowler, run pipx uninstall prowler in your terminal.
  • Delete the azure-landing-zone folder from your local machine if you no longer need the Terraform files, Prowler reports, or SSP draft.

Mission accomplished

That's a Wrap!

Nice work! You just built a governed Azure landing zone from scratch using Terraform, validated it with an open-source compliance scanner, and drafted a System Security Plan. That is the full lifecycle of federal cloud authorization work.

Here's what you learned:

  • Built an Azure Management Group hierarchy with Azure Policy assignments that enforce NIST 800-53 controls (CM-7, SC-7, AU-2, AU-3, AU-4) across your entire tenant.
  • Ran a Prowler CIS Azure Benchmark scan across 160+ security checks, interpreted the results, and mapped findings to NIST 800-53 Rev 5 control families with coverage percentages.
  • Used Cursor Composer 2 to scaffold multi-file Terraform projects, analyze compliance scan output, and generate plain-English SSP narratives directly from your actual code.
  • Added encryption-at-rest enforcement (SC-28) as a third Azure Policy, re-ran Prowler, and measured the improvement in your overall control-coverage percentage.

Ready to quiz yourself?