Why Terraform is the Future of CMMC Compliance
If you are managing AWS infrastructure with Terraform and need CMMC Level 2 certification, you are in a stronger position than you might realize. Infrastructure-as-Code (IaC) is not just a DevOps best practice—it is the gold standard for compliance evidence.
Here is why IaC-native compliance matters:
- Machine-verifiable evidence: Your Terraform configurations can be parsed and validated automatically, unlike screenshots or manual attestations.
- Version-controlled audit trail: Git history shows exactly when security configurations were implemented and who approved them.
- Shift-left prevention: Catch compliance issues in pull requests before they reach production.
- Repeatable deployments: Every environment is provably configured the same way.
NIST 800-171 Controls Mappable from Terraform
CMMC Level 2 requires compliance with all 110 NIST SP 800-171 controls. Of these, approximately 40-50 controls can be partially or fully evidenced through Terraform configurations. Below are the most important control families with specific Terraform resource mappings.
Access Control (AC) Family
The Access Control family contains 22 controls focused on limiting system access to authorized users and transactions.
Manage information system accounts, including establishing, activating, modifying, reviewing, disabling, and removing accounts.
Enforce approved authorizations for logical access to information and system resources.
Employ the principle of least privilege, including for specific security functions and privileged accounts.
Audit and Accountability (AU) Family
These controls ensure you create, protect, and retain system audit records.
Determine that the information system is capable of auditing specific events.
Produce audit records containing information that establishes what type of event occurred, when it occurred, where it occurred, the source of the event, the outcome of the event, and the identity of any individuals or subjects associated with the event.
Review and analyze information system audit records for indications of inappropriate or unusual activity.
System and Communications Protection (SC) Family
These controls protect information in transit and at rest.
Monitor and control communications at the external boundary of the system and at key internal boundaries within the system.
Protect the confidentiality and integrity of transmitted CUI.
Protect the confidentiality and integrity of CUI at rest.
Compliant Terraform Configurations
Below are example Terraform configurations that satisfy multiple NIST 800-171 controls. Use these as templates for your own infrastructure.
Compliant S3 Bucket (SC-28, AU-3, AC-3)
This configuration creates an S3 bucket with encryption at rest, access logging, versioning, and public access blocking.
# Compliant S3 bucket for CUI storage
# Satisfies: SC-28 (encryption), AU-3 (logging), AC-3 (access control)
resource "aws_s3_bucket" "cui_storage" {
bucket = "company-cui-storage-${var.environment}"
tags = {
DataClassification = "CUI"
Compliance = "NIST-800-171"
}
}
# SC-28: Encryption at rest with KMS
resource "aws_s3_bucket_server_side_encryption_configuration" "cui_encryption" {
bucket = aws_s3_bucket.cui_storage.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.cui_key.arn
}
bucket_key_enabled = true
}
}
# AU-3: Access logging
resource "aws_s3_bucket_logging" "cui_logging" {
bucket = aws_s3_bucket.cui_storage.id
target_bucket = aws_s3_bucket.access_logs.id
target_prefix = "cui-storage-logs/"
}
# AC-3: Block all public access
resource "aws_s3_bucket_public_access_block" "cui_public_block" {
bucket = aws_s3_bucket.cui_storage.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning for integrity
resource "aws_s3_bucket_versioning" "cui_versioning" {
bucket = aws_s3_bucket.cui_storage.id
versioning_configuration {
status = "Enabled"
}
}
Compliant VPC Configuration (SC-7, AU-2)
A properly segmented VPC with private subnets, flow logs, and restrictive NACLs.
# Compliant VPC for CUI workloads
# Satisfies: SC-7 (boundary protection), AU-2 (audit events)
resource "aws_vpc" "cui_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "cui-vpc-${var.environment}"
Compliance = "NIST-800-171"
}
}
# AU-2: VPC Flow Logs for network monitoring
resource "aws_flow_log" "cui_vpc_flow_log" {
vpc_id = aws_vpc.cui_vpc.id
traffic_type = "ALL"
log_destination_type = "cloud-watch-logs"
log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
iam_role_arn = aws_iam_role.vpc_flow_log_role.arn
max_aggregation_interval = 60
tags = {
Name = "cui-vpc-flow-logs"
}
}
# Private subnets (no direct internet access)
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.cui_vpc.id
cidr_block = cidrsubnet(aws_vpc.cui_vpc.cidr_block, 8, count.index)
availability_zone = var.availability_zones[count.index]
# SC-7: No public IPs by default
map_public_ip_on_launch = false
tags = {
Name = "cui-private-${var.availability_zones[count.index]}"
Tier = "Private"
}
}
# SC-7: Restrictive security group
resource "aws_security_group" "cui_app" {
name = "cui-application-sg"
description = "Security group for CUI application servers"
vpc_id = aws_vpc.cui_vpc.id
# Only allow HTTPS from internal ALB
ingress {
description = "HTTPS from ALB"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
# Restrict egress to necessary endpoints only
egress {
description = "HTTPS to AWS services"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Consider VPC endpoints instead
}
tags = {
Name = "cui-app-sg"
}
}
Compliant CloudTrail (AU-2, AU-3, AU-6)
# Compliant CloudTrail for audit logging
# Satisfies: AU-2 (audit events), AU-3 (content), AU-6 (review)
resource "aws_cloudtrail" "compliance_trail" {
name = "compliance-audit-trail"
s3_bucket_name = aws_s3_bucket.cloudtrail_logs.id
include_global_service_events = true
is_multi_region_trail = true
enable_logging = true
# AU-3: Detailed logging with data events
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3"]
}
}
# SC-28: Encrypt logs with KMS
kms_key_id = aws_kms_key.cloudtrail_key.arn
# Enable log file validation for integrity
enable_log_file_validation = true
# Send to CloudWatch for AU-6 alerting
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*"
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch.arn
tags = {
Compliance = "NIST-800-171"
}
}
# AU-6: Alerting on security events
resource "aws_cloudwatch_log_metric_filter" "unauthorized_api_calls" {
name = "UnauthorizedAPICalls"
pattern = "{ ($.errorCode = \"*UnauthorizedAccess*\") || ($.errorCode = \"AccessDenied*\") }"
log_group_name = aws_cloudwatch_log_group.cloudtrail.name
metric_transformation {
name = "UnauthorizedAPICallCount"
namespace = "CloudTrailMetrics"
value = "1"
}
}
resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls_alarm" {
alarm_name = "UnauthorizedAPICallsAlarm"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "UnauthorizedAPICallCount"
namespace = "CloudTrailMetrics"
period = "300"
statistic = "Sum"
threshold = "1"
alarm_description = "Unauthorized API calls detected"
alarm_actions = [aws_sns_topic.security_alerts.arn]
}
Common Terraform Compliance Mistakes
These misconfigurations are the most common reasons Terraform-based infrastructure fails CMMC assessments:
1. Public S3 Buckets
# BAD: Missing public access block
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}
# This bucket could be made public accidentally!
# GOOD: Explicit public access blocking
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
2. Overly Permissive Security Groups
# BAD: SSH open to the world
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # NEVER do this!
}
# GOOD: Restrict to bastion or VPN
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion.id
}
3. Missing Encryption
# BAD: Unencrypted RDS
resource "aws_db_instance" "database" {
engine = "postgres"
instance_class = "db.t3.medium"
# Missing: storage_encrypted = true
}
# GOOD: Encrypted with KMS
resource "aws_db_instance" "database" {
engine = "postgres"
instance_class = "db.t3.medium"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
}
4. No Audit Logging
# BAD: No logging configured
resource "aws_lb" "app" {
name = "app-lb"
load_balancer_type = "application"
subnets = var.public_subnets
}
# GOOD: Access logs enabled
resource "aws_lb" "app" {
name = "app-lb"
load_balancer_type = "application"
subnets = var.public_subnets
access_logs {
bucket = aws_s3_bucket.lb_logs.id
prefix = "app-lb"
enabled = true
}
}
Automating Compliance Checks
Manual compliance reviews do not scale. Here is how to automate Terraform compliance checks in your CI/CD pipeline.
Pre-commit Hooks
Catch issues before code is even committed:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_checkov
args:
- --args=--framework=nist_800_171
CI/CD Integration
Block non-compliant infrastructure in your pipeline:
# GitHub Actions example
name: Terraform Compliance Check
on:
pull_request:
paths:
- "terraform/**"
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: nist_800_171
soft_fail: false # Block PR on failure
- name: Run InfraProof Scan
uses: infraproof/scan-action@v1
with:
api_key: ${{ secrets.INFRAPROOF_API_KEY }}
framework: nist-800-171
fail_on_high: true
Automate Your CMMC Evidence Collection
InfraProof scans your Terraform in every PR and generates audit-ready evidence reports. Catch compliance gaps before they reach production.
Start Free TrialGenerating Evidence for Assessors
When your C3PAO arrives, you need to provide evidence for each control. Here is how to generate evidence from your Terraform:
What Assessors Want to See
| Control | Evidence Type | How to Generate |
|---|---|---|
SC-28 |
Encryption configuration | terraform show output showing KMS keys and encryption settings |
SC-7 |
Network boundaries | VPC diagrams + security group rules from state |
AU-2 |
Logging enabled | CloudTrail and flow log configurations |
AC-6 |
Least privilege | IAM policies from Terraform + git history of changes |
Evidence Export Commands
# Export current state as evidence
terraform show -json > evidence/terraform_state_$(date +%Y%m%d).json
# Generate resource-specific evidence
terraform state show aws_s3_bucket.cui_storage > evidence/s3_cui_config.txt
terraform state show aws_cloudtrail.compliance_trail > evidence/cloudtrail_config.txt
# Export all IAM policies
aws iam list-policies --scope Local --output json > evidence/iam_policies.json
Next Steps
Getting your Terraform infrastructure CMMC-compliant is a journey. Here is how to proceed:
- Audit your current Terraform: Run Checkov or InfraProof against your existing code to identify gaps.
- Prioritize fixes: Address SC (encryption, boundaries) and AU (logging) controls first—these are most commonly failed.
- Implement CI/CD checks: Prevent new non-compliant code from being merged.
- Generate evidence: Use your Terraform state and git history as primary evidence sources.
- Document exceptions: Some controls cannot be satisfied by IaC alone—document these in your SSP.
Ready to Automate Your CMMC Compliance?
InfraProof maps your Terraform to NIST 800-171 controls automatically and generates evidence reports your C3PAO will accept.