Back to Infraproof

Terraform CMMC Compliance Guide

Learn how to map your Terraform configurations to NIST 800-171 controls, write compliant infrastructure code, and automate evidence collection for your CMMC Level 2 assessment.

Updated December 2025 15 min read

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.
The IaC Advantage C3PAO assessors increasingly prefer IaC evidence over screenshots. Your Terraform code IS your evidence—it shows exactly how your infrastructure is configured, with full change history.

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.

AC-2 Account Management

Manage information system accounts, including establishing, activating, modifying, reviewing, disabling, and removing accounts.

aws_iam_user aws_iam_group aws_iam_group_membership aws_iam_user_policy_attachment
AC-3 Access Enforcement

Enforce approved authorizations for logical access to information and system resources.

aws_iam_policy aws_iam_role aws_s3_bucket_policy aws_kms_key_policy
AC-6 Least Privilege

Employ the principle of least privilege, including for specific security functions and privileged accounts.

aws_iam_policy_document aws_iam_role_policy aws_security_group aws_network_acl

Audit and Accountability (AU) Family

These controls ensure you create, protect, and retain system audit records.

AU-2 Audit Events

Determine that the information system is capable of auditing specific events.

aws_cloudtrail aws_cloudwatch_log_group aws_flow_log aws_config_configuration_recorder
AU-3 Content of Audit Records

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.

aws_cloudtrail aws_s3_bucket_logging aws_lb_listener aws_rds_cluster
AU-6 Audit Review, Analysis, and Reporting

Review and analyze information system audit records for indications of inappropriate or unusual activity.

aws_cloudwatch_metric_alarm aws_cloudwatch_log_metric_filter aws_guardduty_detector aws_securityhub_account

System and Communications Protection (SC) Family

These controls protect information in transit and at rest.

SC-7 Boundary Protection

Monitor and control communications at the external boundary of the system and at key internal boundaries within the system.

aws_vpc aws_security_group aws_network_acl aws_waf_web_acl aws_flow_log
SC-8 Transmission Confidentiality and Integrity

Protect the confidentiality and integrity of transmitted CUI.

aws_lb_listener aws_acm_certificate aws_cloudfront_distribution aws_api_gateway_domain_name
SC-28 Protection of Information at Rest

Protect the confidentiality and integrity of CUI at rest.

aws_s3_bucket_server_side_encryption_configuration aws_ebs_encryption_by_default aws_rds_cluster aws_kms_key

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
  }
}
Assessor Red Flags C3PAO assessors specifically look for these patterns. Even one public S3 bucket or open security group can result in assessment findings that block certification.

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 Trial

Generating 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:

  1. Audit your current Terraform: Run Checkov or InfraProof against your existing code to identify gaps.
  2. Prioritize fixes: Address SC (encryption, boundaries) and AU (logging) controls first—these are most commonly failed.
  3. Implement CI/CD checks: Prevent new non-compliant code from being merged.
  4. Generate evidence: Use your Terraform state and git history as primary evidence sources.
  5. 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.