Infrastructure as Code (IaC) with Terraform is the standard approach for federal cloud deployments — it provides auditability, repeatability, and configuration drift detection critical for ATO maintenance. But uncontrolled Terraform module proliferation creates technical debt, security inconsistency, and compliance gaps.
A disciplined Terraform module reuse strategy solves these problems by encoding security baselines and compliance requirements into shareable, versioned modules.
Why Module Reuse Matters in Federal IaC
Every federal cloud deployment must implement the same core security patterns: logging enabled, encryption at rest, encryption in transit, tagging for asset inventory, and access controls. Without reusable modules, each team reimplements these patterns from scratch — inconsistently.
A compliant Terraform module encapsulates:
- Security defaults (encryption on by default, logging on by default)
- STIG-required configurations enforced at module level
- Required tagging for asset inventory (AU control family support)
- Output values needed by other modules (ARNs, resource IDs)
Module Structure for Federal Cloud
Organize modules with versioning, documentation, and testing built in:
modules/
aws-s3-compliant/
main.tf
variables.tf
outputs.tf
versions.tf
README.md
examples/
basic/
main.tf
with-replication/
main.tf
tests/
s3_compliance_test.go # Go-based Terratest Example: Compliant S3 Module
A reusable S3 module that enforces FedRAMP/FISMA requirements by default:
# modules/aws-s3-compliant/main.tf
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = merge(var.tags, {
Environment = var.environment
DataClass = var.data_classification
Owner = var.owner
CostCenter = var.cost_center
Compliance = "FedRAMP-${var.impact_level}"
})
}
# Block all public access - enforced, no override
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Encryption - CMK required for FedRAMP High/Moderate
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = var.kms_key_id != null ? "aws:kms" : "AES256"
kms_master_key_id = var.kms_key_id
}
bucket_key_enabled = true
}
}
# Versioning - required for many data integrity controls
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = var.enable_versioning ? "Enabled" : "Suspended"
}
}
# Logging to central audit bucket
resource "aws_s3_bucket_logging" "this" {
count = var.logging_bucket != null ? 1 : 0
bucket = aws_s3_bucket.this.id
target_bucket = var.logging_bucket
target_prefix = "s3-access-logs/${var.bucket_name}/"
}
# Lifecycle policy for cost management
resource "aws_s3_bucket_lifecycle_configuration" "this" {
count = length(var.lifecycle_rules) > 0 ? 1 : 0
bucket = aws_s3_bucket.this.id
dynamic "rule" {
for_each = var.lifecycle_rules
content {
id = rule.value.id
status = rule.value.enabled ? "Enabled" : "Disabled"
transition {
days = rule.value.transition_days
storage_class = rule.value.storage_class
}
}
}
} # modules/aws-s3-compliant/variables.tf
variable "bucket_name" {
type = string
description = "Globally unique S3 bucket name"
}
variable "data_classification" {
type = string
description = "Data classification level for tagging"
validation {
condition = contains(
["UNCLASSIFIED", "CUI", "SECRET"],
var.data_classification
)
error_message = "data_classification must be UNCLASSIFIED, CUI, or SECRET"
}
}
variable "impact_level" {
type = string
description = "FedRAMP impact level"
default = "Moderate"
validation {
condition = contains(["Low", "Moderate", "High"], var.impact_level)
error_message = "impact_level must be Low, Moderate, or High"
}
}
variable "kms_key_id" {
type = string
description = "KMS CMK ARN for encryption. Required for FedRAMP High."
default = null
} Module Versioning and Registry Strategy
Use a private Terraform module registry (Terraform Cloud, GitLab, or self-hosted):
# Reference versioned module from private registry
module "audit_logs" {
source = "registry.internal.gov/mission-platform/aws-s3-compliant/aws"
version = "~> 2.1" # Pin to minor version, allow patches
bucket_name = "mission-audit-logs-${var.environment}"
data_classification = "CUI"
kms_key_id = aws_kms_key.audit.arn
logging_bucket = null # Audit log bucket doesn't self-log
enable_versioning = true
tags = {
Purpose = "AuditLogs"
}
} Semantic versioning for federal modules:
- Patch (x.x.1): Bug fixes, no behavior change
- Minor (x.1.x): New optional features, backwards compatible
- Major (1.x.x): Breaking changes, compliance-impacting changes — require review
Testing Modules with Terratest
// modules/aws-s3-compliant/tests/s3_compliance_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestS3ComplianceModule(t *testing.T) {
t.Parallel()
opts := &terraform.Options{
TerraformDir: "../examples/basic",
Vars: map[string]interface{}{
"bucket_name": "test-compliance-bucket",
"data_classification": "UNCLASSIFIED",
"environment": "test",
},
}
defer terraform.Destroy(t, opts)
terraform.InitAndApply(t, opts)
bucketID := terraform.Output(t, opts, "bucket_id")
// Verify public access block
publicAccessBlock := aws.GetS3BucketPublicAccessBlock(t, "us-gov-east-1", bucketID)
assert.True(t, *publicAccessBlock.BlockPublicAcls)
assert.True(t, *publicAccessBlock.RestrictPublicBuckets)
// Verify encryption enabled
encryption := aws.GetS3BucketServerSideEncryptionConfig(t, "us-gov-east-1", bucketID)
assert.NotNil(t, encryption)
} See Rutagon's FedRAMP ConMon automation and NIST 800-53 cloud automation for the compliance context these modules support.
Explore Rutagon's cloud engineering capabilities.
FAQ
Should federal Terraform modules be stored in a public registry like the Terraform Registry?
No — federal cloud modules often contain agency-specific configurations, internal IP ranges, approved service lists, and compliance patterns that should not be public. Use a private module registry (Terraform Enterprise, GitLab's Terraform module registry, or an S3-backed solution with a Terraform-compatible API) that enforces authenticated access.
How do you handle Terraform state management for federal cloud environments?
Use remote state in an S3 backend with DynamoDB state locking, encrypted at rest with a CMK, versioned, and with access logging enabled. Never store Terraform state in source control. For multi-account architectures, use separate state buckets per environment with cross-account assume-role patterns for state access.
What is the right cadence for updating module versions in federal environments?
Balance currency (patching known vulnerabilities) against stability (avoiding unplanned changes). A reasonable approach: patch versions apply automatically, minor versions are reviewed quarterly, major versions go through a formal change review. For compliance-critical modules (IAM, networking), all version upgrades require a Terraform plan review before apply.
How do you enforce module usage in a federal program with multiple teams?
OPA/Sentinel policies in Terraform Enterprise can enforce that required modules are used (e.g., all S3 buckets must use the compliant-s3 module rather than the aws_s3_bucket resource directly). Git repository templates that include module references as a starting point also guide teams toward the approved patterns.
What's the compliance benefit of defining Terraform modules with validation blocks?
Terraform validation blocks enforce correct values at plan time, before resources are created. In federal environments, this means catching configuration errors (wrong data classification, missing KMS key for High impact) before they reach a real environment. Combined with automated testing, validation blocks are a low-effort, high-value compliance mechanism.