AeoliTech Whitepaper

Policy-as-Code for NIST 800-171

Expert research on CMMC preparation and defense compliance

Enforcing 800-171 Controls Continuously Using Policy-as-Code

Author: Leonard Esere, Senior Cybersecurity Engineer, AeoliTech

Date: April 2026

Classification: Public


Abstract

Defense Industrial Base (DIB) contractors face a mounting challenge: NIST SP 800-171 Rev. 2 mandates 110 specific security requirements for Controlled Unclassified Information (CUI) systems, and DFARS 252.204-7012 requires continuous protection — not annual checkboxes. Yet most organizations still treat compliance as a documentation exercise, producing policy binders that drift the moment an engineer provisions a new resource.

Policy-as-code (PaC) fundamentally changes this dynamic. By encoding NIST 800-171 controls as machine-readable, version-controlled, and automatically enforced rules, organizations shift from reactive auditing to proactive prevention. This whitepaper explains what policy-as-code is, how Azure Policy built-in initiatives, AWS Config Conformance Packs, Terraform Sentinel, and Open Policy Agent (OPA) bring 800-171 controls to life, and how a complete policy-as-code lifecycle — from authoring through remediation — looks in practice. It walks through a concrete example of control 3.1.1 (Access Control) becoming an enforced Conditional Access policy with drift alerting, and explains how AeoliTech's PolicyCortex platform operationalizes these patterns for CMMC-bound organizations.


Table of Contents

1. What Is Policy-as-Code?

2. The NIST 800-171 Compliance Challenge

3. Azure Policy: Built-In 800-171 Initiatives

4. AWS Config Rules and Conformance Packs

5. Terraform Sentinel and OPA for Infrastructure Guardrails

6. GitHub Actions for Continuous Policy Drift Detection

7. Control 3.1.1 in Practice: From Requirement to Enforced Policy

8. The Policy-as-Code Lifecycle

9. Remediation Automation

10. PolicyCortex: Operationalizing PaC for CMMC

11. Conclusion

12. About the Author

13. References


1. What Is Policy-as-Code?

Policy-as-code is the practice of defining, managing, and enforcing organizational rules — security controls, compliance mandates, cost guardrails — as software artifacts: version-controlled files, tested in CI/CD pipelines, and applied automatically across infrastructure. Rather than maintaining a PDF that tells engineers what they should do, policy-as-code tells the cloud platform what it must do — and stops non-compliant configurations before they reach production.

The concept borrows heavily from infrastructure-as-code: if you can describe a virtual machine in a Terraform file, you can describe the security rules that govern that virtual machine in a Sentinel or Rego file. Both live in Git, both are reviewed via pull request, and both are deployed automatically.

Core characteristics of a mature policy-as-code implementation:

| Characteristic | Description |

|---|---|

| Version-controlled | Policies stored in Git with full commit history and blame |

| Tested | Unit tests for policy logic, integration tests against real plan outputs |

| Automated enforcement | Policies evaluated on every change before resources are provisioned |

| Auditable | Every policy evaluation — pass or fail — produces a log record |

| Remediating | Non-compliant resources are automatically brought back into compliance |

| Mapped | Every policy definition linked to one or more control IDs |

For NIST 800-171 and CMMC, this is not optional engineering elegance — it is an operational requirement. The DoD's annual affirmation mandate under DFARS 252.204-7021 requires organizations to continuously affirm that 110 security requirements are in place. Continuous means always, not at last year's assessment.


2. The NIST 800-171 Compliance Challenge

NIST SP 800-171 Rev. 2 organizes 110 requirements across 14 security families:

| Family | ID Range | Example Requirement |

|---|---|---|

| Access Control | 3.1.1 – 3.1.22 | Limit system access to authorized users |

| Audit & Accountability | 3.3.1 – 3.3.9 | Create and retain system audit logs |

| Configuration Management | 3.4.1 – 3.4.9 | Establish baseline configurations |

| Identification & Authentication | 3.5.1 – 3.5.11 | Use multi-factor authentication |

| Incident Response | 3.6.1 – 3.6.3 | Establish incident response capability |

| Maintenance | 3.7.1 – 3.7.6 | Perform controlled maintenance |

| Media Protection | 3.8.1 – 3.8.9 | Protect system media |

| Personnel Security | 3.9.1 – 3.9.2 | Screen individuals |

| Physical Protection | 3.10.1 – 3.10.6 | Limit physical access |

| Risk Assessment | 3.11.1 – 3.11.3 | Assess risk periodically |

| Security Assessment | 3.12.1 – 3.12.4 | Periodically assess controls |

| System & Comms Protection | 3.13.1 – 3.13.16 | Monitor system communications |

| System & Info Integrity | 3.14.1 – 3.14.7 | Identify and correct flaws |

| Awareness & Training | 3.2.1 – 3.2.3 | Provide security awareness training |

The challenge is not understanding these requirements — it is keeping them operational as environments change. Engineers provision new subscriptions. Developers push misconfigured containers. Vendors request broad IAM permissions. Without automated enforcement, each of these events is a potential compliance gap. The average large DIB contractor deploys hundreds of cloud resources per week; manual review cannot keep pace.

Policy-as-code solves this with a shift-left approach: compliance checks happen at authoring time, at PR review, at terraform plan, and at resource creation — not after the fact during an annual assessment.


3. Azure Policy: Built-In 800-171 Initiatives

Microsoft publishes a built-in Regulatory Compliance initiative for NIST SP 800-171 Rev. 2 in Azure Policy. This initiative maps Azure Policy definitions to NIST 800-171 control IDs, providing an out-of-the-box compliance baseline for Azure environments.

Finding the Initiative:

Navigate to Azure Portal → Policy → Definitions → search "NIST SP 800-171 Rev. 2". The initiative contains definitions spanning all 14 control families, with effects including Audit, AuditIfNotExists, Deny, and DeployIfNotExists.

Key Policy Definitions by Control Area:

| Control ID | Control Description | Azure Policy Definition | Effect |

|---|---|---|---|

| 3.1.1 | Limit access to authorized users | A maximum of 3 owners should be designated for your subscription | AuditIfNotExists |

| 3.1.1 | Limit access to authorized users | Audit usage of custom RBAC roles | Audit |

| 3.1.1 | Limit access to authorized users | Authentication to Linux machines should require SSH keys | AuditIfNotExists |

| 3.1.2 | Limit system access to types of transactions | App Service apps should have remote debugging turned off | AuditIfNotExists |

| 3.1.3 | Control CUI flow | App Configuration should use private link | AuditIfNotExists |

| 3.1.14 | Route remote access via managed control points | Azure Key Vaults should use private link | Parameterized |

| 3.5.3 | Use multi-factor authentication | MFA should be enabled for accounts with write permissions | AuditIfNotExists |

| 3.13.8 | Implement cryptographic mechanisms | Automation account variables should be encrypted | AuditIfNotExists |

Important caveats from Microsoft's own documentation: A "Compliant" status in Azure Policy refers only to the policy definitions themselves — it does not ensure you are fully compliant with all requirements of a control. Some controls have no Azure Policy definition and require manual attestation. Assessors must understand this distinction.

Assigning the Initiative:

`bash

az policy assignment create \

--name "nist-800-171-r2" \

--display-name "NIST SP 800-171 Rev 2 Compliance" \

--policy-set-definition "/providers/Microsoft.Authorization/policySetDefinitions/03055927-78bd-4236-86c0-f36125a10dc9" \

--scope "/subscriptions/" \

--params '{"logAnalyticsWorkspaceIdForVMAgentReporting": {"value": ""}}'

`

The compliance results feed directly into Microsoft Defender for Cloud's Regulatory Compliance blade, providing real-time control status across the 110 requirements, exportable to CSV, JSON, or streamed to a Log Analytics workspace.


4. AWS Config Rules and Conformance Packs

For organizations with multi-cloud or AWS-primary environments, AWS Config provides equivalent continuous compliance monitoring through managed rules and Conformance Packs.

AWS publishes an Operational Best Practices for NIST 800-171 Conformance Pack — a YAML template containing pre-mapped AWS Config managed rules. When deployed, the pack continuously evaluates resource configurations against 800-171 requirements.

Key NIST 800-171 to AWS Config Rule Mappings:

| Control ID | Control Description | AWS Config Rule |

|---|---|---|

| 3.1.1 | Limit access to authorized users | iam-user-mfa-enabled, iam-root-access-key-check, iam-no-inline-policy-check |

| 3.1.1 | Authorized user access | ec2-instance-profile-attached, ecs-task-definition-user-for-host-mode-check |

| 3.1.5 | Least privilege | iam-policy-no-statements-with-admin-access, iam-policy-no-statements-with-full-access |

| 3.3.1 | Audit logging | cloudtrail-enabled, cloud-trail-log-file-validation-enabled, multi-region-cloudtrail-enabled |

| 3.3.2 | User activity review | cloudwatch-alarm-action-check, cloud-trail-cloud-watch-logs-enabled |

| 3.4.1 | Baseline configurations | ec2-managedinstance-patch-compliance-status-check, ec2-instance-managed-by-systems-manager |

| 3.5.3 | Multi-factor authentication | mfa-enabled-for-iam-console-access, root-account-mfa-enabled |

| 3.13.8 | Cryptographic mechanisms | encrypted-volumes, rds-storage-encrypted, s3-bucket-ssl-requests-only |

Deploying the Conformance Pack:

`bash

aws configservice put-conformance-pack \

--conformance-pack-name "NIST-800-171-Pack" \

--template-s3-uri "s3://your-bucket/operational-best-practices-nist-800-171.yaml" \

--region us-east-1

`

AWS Audit Manager further extends this capability with a prebuilt NIST SP 800-171 Rev. 2 framework that collects evidence automatically from AWS Config, Security Hub, and CloudTrail — mapping findings to specific control objectives in a format usable directly for assessment preparation.


5. Terraform Sentinel and OPA for Infrastructure Guardrails

Azure Policy and AWS Config operate at the resource layer — they evaluate what exists in the cloud. Terraform Sentinel and OPA operate at the provisioning layer — they evaluate what is about to be deployed. Together, these two layers create defense in depth: block bad infrastructure before it is created, and alert on non-compliant resources that somehow slip through.

Terraform Sentinel

HashiCorp Sentinel is a policy-as-code framework embedded in HCP Terraform (formerly Terraform Cloud) and Terraform Enterprise. It evaluates policies against the Terraform plan, state, and configuration data between terraform plan and terraform apply.

Enforcement levels:

| Level | Behavior | Use Case |

|---|---|---|

| Advisory | Logs warning; does not block | New policies under evaluation |

| Soft Mandatory | Blocks by default; authorized users can override | Policies with legitimate exceptions |

| Hard Mandatory | Blocks with no override | Non-negotiable security requirements (e.g., encryption at rest for CUI data) |

Example Sentinel policy for 3.13.8 (encryption at rest):

`python

policy "cui-storage-must-be-encrypted" {

source = "./policies/cui-encryption.sentinel"

enforcement_level = "hard-mandatory"

}

`

`python

import "tfplan/v2" as tfplan

storage_accounts = filter tfplan.resource_changes as _, rc {

rc.type is "azurerm_storage_account" and

(rc.change.actions contains "create" or rc.change.actions contains "update")

}

encryption_enabled = rule {

all storage_accounts as _, sa {

sa.change.after.enable_https_traffic_only is true

}

}

main = rule { encryption_enabled }

`

Open Policy Agent (OPA)

OPA with Rego provides a vendor-neutral, declarative policy language that evaluates JSON-structured data. When used with Terraform, the workflow is: terraform planterraform show -json tfplan > plan.jsonopa eval --input plan.json --data policy.rego.

Example Rego policy for 3.1.1 (access control — deny public IPs):

`rego

package terraform.nist_800_171.ac_3_1_1

deny[msg] {

resource := input.planned_values.root_module.resources[_]

resource.type == "azurerm_network_interface"

resource.values.ip_configuration[_].public_ip_address_id != null

msg := sprintf("Resource '%v' violates 3.1.1: network interfaces must not have public IP addresses attached", [resource.address])

}

`

OPA's strength is its composability: a single policy file can enforce multiple control requirements, and a library of Rego policies becomes an organization's living compliance rulebook.


6. GitHub Actions for Continuous Policy Drift Detection

Policy drift occurs when the deployed state diverges from the intended policy state — a misconfigured change, a manual console override, or a new resource that was never scanned. GitHub Actions provides the CI/CD backbone for detecting drift continuously.

A complete GitHub Actions workflow for OPA-based drift detection:

`yaml

name: NIST 800-171 Policy Compliance Check

on:

pull_request:

branches: [main]

schedule:

  • cron: '0 6 *' # Daily drift scan at 06:00 UTC

jobs:

policy-compliance:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • name: Setup Terraform

uses: hashicorp/setup-terraform@v3

  • name: Setup OPA

uses: open-policy-agent/setup-opa@v2

with:

version: latest

  • name: Azure Login

uses: azure/login@v2

with:

creds: ${{ secrets.AZURE_CREDENTIALS }}

  • name: Terraform Plan

run: |

terraform init

terraform plan -out tfplan

terraform show -json tfplan | jq > plan.json

  • name: Run OPA Policy Check

run: |

opa eval \

--input plan.json \

--data policies/nist-800-171/ \

--format pretty \

"data.terraform.nist_800_171.deny" \

| tee opa-results.json

  • name: Post Results to PR

uses: actions/github-script@v7

with:

script: |

const fs = require('fs');

const results = fs.readFileSync('opa-results.json', 'utf8');

github.rest.issues.createComment({

issue_number: context.issue.number,

owner: context.repo.owner,

repo: context.repo.repo,

body: ## NIST 800-171 Policy Check Results\n\\\\n${results}\n\\\``

});

  • name: Fail on Policy Violations

run: |

violations=$(cat opa-results.json | jq '.result | length')

if [ "$violations" -gt 0 ]; then

echo "ERROR: $violations policy violations found"

exit 1

fi

`

The scheduled job is the key: it runs the same policy checks against the current deployed state daily, catching any drift introduced outside of Terraform (e.g., manual console changes, API calls, emergency fixes that were never reverted).


7. Control 3.1.1 in Practice: From Requirement to Enforced Policy

NIST SP 800-171 Rev. 2 control 3.1.1 states: "Limit system access to authorized users, processes acting on behalf of authorized users, and devices (including other information systems)."

In practical terms, this means: only authenticated and authorized identities should reach CUI systems. On Azure, this translates to Conditional Access policies enforced at the identity layer.

Step 1: Define the requirement in plain language

> Users accessing any application in the CUI boundary must authenticate via MFA. Access from non-compliant or unregistered devices must be blocked. Guest accounts must not have access to CUI subscriptions.

Step 2: Translate to Azure Conditional Access policy (ARM template snippet)

`json

{

"displayName": "NIST-3.1.1 - CUI Boundary MFA Enforcement",

"state": "enabled",

"conditions": {

"users": {

"includeGroups": [""]

},

"applications": {

"includeApplications": ["All"]

},

"locations": {

"includeLocations": ["All"]

}

},

"grantControls": {

"operator": "AND",

"builtInControls": ["mfa", "compliantDevice"]

}

}

`

Step 3: Enforce with Azure Policy

Assign the built-in policy "MFA should be enabled for accounts with write permissions on your subscription" (policy ID 9297c21d-2ed6-4474-b48f-163f75654ce3) in Deny mode for CUI subscriptions.

Step 4: Alert on violation

In Microsoft Sentinel, create an analytics rule that queries for Conditional Access policy failures against CUI-tagged resources:

`kql

SigninLogs

| where TimeGenerated > ago(1h)

| where ConditionalAccessStatus == "failure"

| where AppDisplayName has_any ("CUI", "CMMC")

| project TimeGenerated, UserPrincipalName, AppDisplayName,

ConditionalAccessStatus, ResultDescription, IPAddress

| extend ControlViolation = "3.1.1 - Unauthorized Access Attempt"

`

Step 5: Automate response

A Sentinel Playbook (Logic App) triggers on this alert:

1. Notify the Security Operations team via Teams

2. Log the incident to the evidence vault (SharePoint/Blob) with timestamp and full event data

3. If the user has 3+ failures within 1 hour, auto-revoke the session token via Microsoft Graph API

This complete chain — requirement → policy definition → enforcement → alert → automated response — is the operational definition of policy-as-code for NIST 800-171.


8. The Policy-as-Code Lifecycle

A mature policy-as-code implementation is not a one-time deployment. It follows a governance lifecycle:

`

Author → Review → Test → Deploy → Monitor → Remediate → Retire/Update

`

| Phase | Activities | Tooling |

|---|---|---|

| Author | Write policy definitions in Sentinel/Rego/ARM; map to control ID | VS Code, Git |

| Review | Pull request review by security team; legal/compliance sign-off | GitHub PRs, Policy review checklist |

| Test | Unit tests for policy logic; integration tests against staging tenant | OPA test framework, sentinel test CLI |

| Deploy | Apply policy definitions to production subscription scope; set enforcement mode | Azure CLI, Terraform, GitHub Actions |

| Monitor | Track compliance percentage per control; alert on drops below threshold | Azure Policy Compliance, Sentinel workbooks |

| Remediate | Automated remediation tasks correct non-compliant resources; exceptions logged as POA&M items | Azure Policy Remediation Tasks, Logic Apps |

| Retire/Update | When controls change (e.g., NIST Rev. 3 adoption), update policy and re-test | Git history, changelog |

Policy versioning is non-negotiable. Every policy change must be traceable: who changed it, why, what control it maps to, and what the before/after effects are. This traceability is itself evidence for assessors examining the Configuration Management (3.4.x) control family.


9. Remediation Automation

Detection without remediation creates alert fatigue. Policy-as-code platforms must close the loop by automatically correcting drift.

Azure Policy Remediation Tasks

For policies with DeployIfNotExists or Modify effects, Azure Policy creates remediation tasks that push compliant configurations to existing non-compliant resources:

`bash

az policy remediation create \

--name "remediate-mfa-3.1.1" \

--policy-assignment "nist-800-171-r2" \

--definition-reference-id "RequireMFAForSubscriptionOwnerRole" \

--resource-discovery-mode ReEvaluateCompliance

`

AWS Config Auto-Remediation

AWS Config supports automatic remediation using AWS Systems Manager Automation documents:

`yaml

ConfigRuleName: iam-user-mfa-enabled

RemediationConfiguration:

TargetType: SSM_DOCUMENT

TargetId: AWSConfigRemediation-EnableMFAForIAMUser

Automatic: true

MaximumAutomaticAttempts: 3

RetryAttemptSeconds: 60

`

Remediation Decision Matrix:

| Violation Type | Automated Action | Manual Action Required |

|---|---|---|

| MFA not enforced | Azure Policy Modify effect enables security defaults | None if auto-remediation succeeds |

| Public IP on CUI resource | Sentinel Playbook removes public IP association | CISO notification; root cause analysis |

| Overly broad IAM policy | Flag as Critical; alert; open POA&M | Human review before automated removal |

| Audit logging disabled | DeployIfNotExists enables diagnostic settings | None |

| Unencrypted storage | Alert; prevent new writes | Manual migration of existing data |

Automated remediation actions must themselves be logged as evidence — the remediation event, the before/after configuration state, and the operator (service principal) that executed it.


10. PolicyCortex: Operationalizing PaC for CMMC

Building the infrastructure described in this whitepaper from scratch — authoring 110+ policy definitions, mapping them to control objectives, building CI/CD pipelines, configuring Sentinel analytics rules, and maintaining them as frameworks evolve — requires significant engineering investment. Most DIB prime contractors and sub-tier suppliers lack the dedicated security engineering staff to do this in-house.

AeoliTech's PolicyCortex platform is the engine behind our speed. PolicyCortex is an AI-driven policy-as-code platform purpose-built for CMMC that:

  • Ships with pre-built policy libraries covering all 110 NIST 800-171 Rev. 2 controls across Azure Policy, AWS Config, and GCP Security Command Center
  • Maintains control-to-policy mappings that update automatically as Microsoft, AWS, and NIST publish definition changes
  • Deploys via Azure-native connectors into customer subscriptions with read-only or remediation-scoped service principals — no data leaves the customer tenant
  • Generates evidence packages on demand: compliance exports, remediation logs, and policy evaluation histories formatted for C3PAO review under the examine/test evidence categories

For CMMC Level 2 organizations, the alternative to PolicyCortex is typically 60–80% more engineering time to achieve equivalent coverage — time measured in months, not weeks. Our clients at the prime contractor tier typically complete the transition from manual compliance management to fully automated, continuously monitored policy-as-code in 8–12 weeks using PolicyCortex.

The LANL ATO engagement demonstrates this pattern at scale: complex multi-subscription Azure environments, hundreds of resources, and a finite assessment window. PolicyCortex's automated baseline scan identified gaps in days that would have taken weeks of manual review, and its continuous monitoring posture meant that the evidence vault was populated in real time — not assembled in a frantic pre-assessment sprint.


11. Conclusion

Policy-as-code is not a product to be purchased and forgotten — it is an engineering discipline that matures over time. For NIST 800-171 and CMMC compliance, it offers three irreplaceable capabilities:

1. Prevention: Blocking non-compliant infrastructure before it reaches production

2. Detection: Continuously monitoring control state and alerting on drift within minutes, not months

3. Evidence: Producing audit-ready records of policy evaluation, enforcement, and remediation automatically

Organizations that commit to policy-as-code stop dreading assessments. Their evidence vault is always current, their control state is always visible, and their security posture improves continuously rather than in pre-assessment sprints. The question is not whether to adopt policy-as-code — it is how quickly you can get there.


About the Author

Leonard Esere is a Senior Cybersecurity Engineer at AeoliTech with extensive experience designing and implementing security architectures for federal contractors and national laboratory environments. He holds a DoD Secret clearance and a DoE Q clearance, has contributed to security assessments at the MITRE Corporation, and led the Authorization to Operate (ATO) evidence vault architecture for a major LANL engagement. He also served as the security engineering lead for Frontier supercomputer PCI DSS compliance. Leonard specializes in translating complex regulatory requirements — NIST 800-171, CMMC, FedRAMP — into automated, scalable technical implementations.


References

1. Microsoft. Regulatory Compliance details for NIST SP 800-171 R2 – Azure Policy. February 2026. https://learn.microsoft.com/en-us/azure/governance/policy/samples/nist-sp-800-171-r2

2. AWS. Operational Best Practices for NIST 800-171 – AWS Config Conformance Pack. https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-nist_800-171.html

3. NIST. Special Publication 800-171 Revision 2: Protecting Controlled Unclassified Information in Nonfederal Systems and Organizations. February 2020. https://csrc.nist.gov/publications/detail/sp/800-171/rev-2/final

4. HashiCorp. Sentinel Policy-as-Code Framework. https://developer.hashicorp.com/sentinel/docs

5. Open Policy Agent. OPA Documentation. https://www.openpolicyagent.org/docs/latest/

6. Microsoft. CMMC – Azure Compliance Offerings. April 2023. https://learn.microsoft.com/en-us/azure/compliance/offerings/offering-cmmc

7. Spacelift. Enforcing Policy as Code in Terraform with Sentinel & OPA. October 2025. https://spacelift.io/blog/terraform-policy-as-code

8. DoD CIO. About CMMC. https://dodcio.defense.gov/CMMC/about/

9. AWS. NIST SP 800-171 Rev 2 – AWS Audit Manager. https://docs.aws.amazon.com/audit-manager/latest/userguide/NIST-800-171-r2-1.1.html

10. Microsoft. NIST SP 800-171 – Azure Compliance Offerings. https://learn.microsoft.com/en-us/azure/compliance/offerings/offering-nist-800-171


© 2026 AeoliTech. All rights reserved. This whitepaper is provided for informational purposes. Contact AeoliTech to schedule a PolicyCortex readiness assessment for your CMMC program.

Ready to Start Your CMMC Journey?

Our team of cleared engineers and compliance specialists can help you scope, plan, and execute your path to CMMC Level 2 certification.

Contact Us