Build the gate chain that makes AI-drafted Terraform safe to review: terraform validate → terraform plan → Checkov → Infracost → a human review checklist. You will run a deliberately AI-flawed module through the chain and catch three seeded defects — one per mechanical gate — plus one judgment call no scanner flags.
The January 2026 TerraFormer paper showed that even frontier models hallucinate HCL resource types and attributes; the skill this lab trains is not writing Terraform, it is never trusting generated Terraform that has not survived the chain.
~90 minutes hands-on · Core tier (maps to module M2).
Everything runs offline against a mock AWS provider — no cloud account, no credentials, $0. Install: Terraform ≥ 1.5 (or OpenTofu), pip install checkov, and the Infracost CLI (free tier; skippable — the lab notes what it would have caught).
Create a directory webstack/ with these four files. This is your "AI output" — it simulates a typical assistant draft of a 3-tier stack and contains three seeded defects you must not fix until the gates find them.
# provider.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" {
region = "us-east-1"
access_key = "mock"
secret_key = "mock"
skip_credentials_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
}
# network.tf
variable "vpc_cidr" {
type = string
default = "10.42.0.0/24"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
tags = { Name = "webstack-vpc" }
}
resource "aws_subnet" "app" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 12, count.index)
availability_zone = ["us-east-1a", "us-east-1b"][count.index]
tags = { Name = "webstack-app-${count.index}" }
}
resource "aws_security_group" "app" {
name = "webstack-app"
vpc_id = aws_vpc.main.id
ingress {
description = "ssh for ops"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "app traffic"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# compute.tf
resource "aws_instance" "app" {
count = 2
ami = "ami-0c02fb55956c7d316"
instance_type = "m5.4xlarge"
subnet_id = aws_subnet.app[count.index].id
security_group_ids = [aws_security_group.app.id]
tags = { Name = "webstack-app-${count.index}" }
}
# storage.tf
resource "aws_s3_bucket" "assets" {
bucket = "webstack-assets-bucket"
}
resource "aws_db_instance" "orders" {
identifier = "webstack-orders"
engine = "postgres"
engine_version = "16.3"
instance_class = "db.t3.medium"
allocated_storage = 20
username = "app"
password = "changeme123"
skip_final_snapshot = true
publicly_accessible = false
}
The mock provider trick (skip_credentials_validation + fake keys) lets plan run fully offline because create-only plans never call AWS APIs. Avoid data sources and it works for any module.
defect-log.md with columns: defect · which gate caught it · why earlier gates missed it · fix. You will fill one row per gate.terraform init -input=false && terraform validate. It fails with Unsupported argument … "security_group_ids" is not expected here in compute.tf. This is a classic hallucination: the model blended aws_instance's real argument vpc_security_group_ids with the security_group_ids attribute other resources use. Ask your AI assistant to explain and fix it, then confirm the fix against the provider docs — not against the assistant's word. Log it. Re-run validate until green.terraform plan -input=false. Validate passed, but plan fails: Error in function call … cidrsubnet — carving 12 new bits out of a /24 would need a /36, which cannot exist. Validate missed it because expression evaluation only happens at plan. Fix (cidrsubnet(var.vpc_cidr, 4, count.index) gives two /28s), log it, re-plan until you see Plan: 8 to add.checkov -d . --quiet --compact. Expect roughly 30 findings across 6 resources. Now triage like a reviewer, not a checkbox-clicker: the seeded defect is CKV_AWS_24 — SSH open to 0.0.0.0/0. Also treat CKV_SECRET_6 (the hardcoded DB password) as must-fix: move it to variable "db_password" { sensitive = true }. Restrict SSH to a management CIDR. Log the defect.# checkov:skip=CKV_AWS_144:single-region by design style annotations so the pipeline is green and auditable.infracost breakdown --path . (or, without an account, price it manually). Two m5.4xlarge instances is roughly $1,100/month for an app tier no requirement justifies — a number, not an error, which is exactly why no earlier gate fired. Right-size (e.g., t3.medium) and log it as the judgment catch.review-checklist.md with at least six questions a human must answer before apply (blast radius? state file location? deletion protection? tags for cost attribution? who approved the SG exceptions? rollback plan?). Then wire gates 1–4 into one verify.sh that exits non-zero on any failure.Each gate catches a class the previous gate structurally cannot: syntax/schema → expression evaluation → policy → economics → judgment. Order is cheap-to-expensive; never skip ahead.
terraform validate and terraform plan both succeed (plan shows 8 resources).checkov -d . --quiet reports zero unaddressed failures (fixed or annotated-suppressed).defect-log.md has ≥ 3 rows, each naming the gate that caught it and why earlier gates missed it.verify.sh runs the full chain and exits 0; deliberately re-introduce the SSH rule and confirm it exits non-zero.The webstack/ repo containing the fixed module, verify.sh, review-checklist.md, and defect-log.md. Self-grading rubric (pass = 8/10):
init fails offline: provider download needs internet once; afterwards everything is local.plan demands credentials: you removed one of the three skip_* flags or added a data source.--check CKV_AWS_24,CKV_SECRET_6 first, then widen.On client engagements where IaC cannot enter public AI tools: run the same chain unchanged — validate, plan, Checkov and Infracost are all local OSS and see no LLM. For the AI-assist steps, use the client-approved tenancy (Bedrock/Azure OpenAI) or a local model via Ollama, and redact account IDs/CIDRs before prompting. The gate chain is precisely what makes AI assistance defensible under an MSA: every generated line passes deterministic verification you can show an auditor.
security_group_ids on aws_instance — hallucinated attribute; real one is vpc_security_group_ids.cidrsubnet(var.vpc_cidr, 12, …) on a /24 — arithmetic impossible; only fails when expressions are evaluated.0.0.0.0/0 (CKV_AWS_24); the hardcoded password = "changeme123" (CKV_SECRET_6) is a legitimate fourth catch.m5.4xlarge × 2 (~$1,100/mo) with no stated requirement — legal, secure, and wrong.