Terraform AWS
Quick Reference
| Topic | Reference File | Key Insight |
|---|---|---|
| Module structure | references/terraform-structure.md | Don't wrap single resources in modules |
| Naming conventions | references/terraform-structure.md | Consistent name_prefix locals pattern |
| State management | references/terraform-structure.md | S3 native locking (Terraform 1.10+) — DynamoDB lock is deprecated |
| State backend bootstrap | references/terraform-structure.md | Separate bootstrap project with local state, SSE-KMS, CI/CD access policy |
| IAM & security | references/security-iam.md | LeadingKeys for multi-tenant, policy composition, confused deputy, KMS key policy |
| Checkov (DynamoDB, Lambda, API GW) | references/security-iam.md | CKV_AWS_28/119 (DynamoDB), CKV_AWS_258 (Lambda URL), CKV_AWS_76/CKV2_AWS_29 (API GW) |
| Secrets in Terraform state | references/security-iam.md | Use ephemeral resource (Terraform 1.10+); protect state with SSE-KMS S3 backend |
| CI/CD OIDC federation | references/security-iam.md + references/cicd-patterns.md | aws_iam_openid_connect_provider + sts:AssumeRoleWithWebIdentity — no static keys |
| CI/CD pipelines | references/cicd-patterns.md | Use OIDC — never store AWS keys as secrets |
| Multi-env pipelines | references/cicd-patterns.md | Per-branch backend keys, env resolver, ephemeral cleanup |
| Pipeline safety | references/cicd-patterns.md | Concurrency per env, environment protection gates, drift detection |
Critical Anti-Patterns
Module Design
- Don't wrap single AWS resources in modules — encapsulate logical groupings (Lambda + Role + Log Group)
- Don't nest modules more than 2 levels deep — keep hierarchy flat
IAM & Security
- Don't use
Resource: "*"in IAM policies — scope to specific ARNs with conditions - Don't attach
AdministratorAccessto Lambda execution roles - Don't omit
source_arnonaws_lambda_permissionwhenprincipalis a service — leaves the function open to confused deputy attacks across accounts - Don't scope Lambda execution role logs permissions to
*— pre-createaws_cloudwatch_log_groupin Terraform and scope to that log group ARN; eliminateslogs:CreateLogGroupon wildcard - Don't use
sqs:*on*for Lambda SQS consumers — only three actions are required:sqs:ReceiveMessage,sqs:DeleteMessage,sqs:GetQueueAttributes, scoped to the specific queue ARN - Don't use
server_side_encryption { enabled = true }alone for DynamoDB — nokms_key_arnuses AWS-owned key and fails Checkov CKV_AWS_119 - Don't rely on the default KMS key policy in production — the default
kms:*on the account principal delegates access to any IAM role withkms:*; use explicit per-role statements - Don't set
authorization_type = "NONE"onaws_lambda_function_url— publicly invocable without auth; fires Checkov CKV_AWS_258 - Don't use
data "aws_secretsmanager_secret_version"in Terraform ≥ 1.10 — useephemeralto avoid writing the secret value to state
CI/CD
- Don't store AWS credentials as GitHub/CI secrets — use OIDC federation
- Don't run
terraform applydirectly on push to main — require plan review in PR - Don't use
cancel-in-progress: trueon apply/destroy jobs — cancelling mid-apply corrupts state - Don't skip
default_tagswithBranchtag — orphaned resources become untrackable - Prefer object format
environment: { name: ${{ needs.x.outputs.y }} }for dynamic environment names — more explicit, avoids ambiguity in YAML parsers - Don't rely on dynamic
environment: name:without pre-creating the environment in repo settings — non-existent names silently create unprotected environments with no reviewers
Decision Frameworks
When to Create a Module
| Create a Module | Use Resource Directly |
|---|---|
| 3+ resources always deployed together | Single resource with no dependencies |
| Reused across 2+ environments or repos | One-off resource |
| Encapsulates non-obvious wiring (IAM + resource) | Simple resource with standard config |
Version Matrix
| Feature | Terraform Core | Terraform AWS Provider |
|---|---|---|
| S3-native state locking (no DynamoDB lock table) | 1.10+ | any |
| Ephemeral resources for secrets | 1.10+ | any |
Cost Analysis
When the user asks about cost impact, pricing, or cost optimisation for Terraform-managed infrastructure, direct them to install the AWS Pricing MCP Server. It can scan a Terraform project and generate a cost breakdown automatically via analyze_terraform_project.
Prerequisites: uv package manager, Python 3.10+, AWS credentials with pricing:* permissions.
macOS / Linux:
{
"mcpServers": {
"awslabs.aws-pricing-mcp-server": {
"command": "uvx",
"args": ["awslabs.aws-pricing-mcp-server@latest"],
"env": {
"FASTMCP_LOG_LEVEL": "ERROR",
"AWS_PROFILE": "your-aws-profile",
"AWS_REGION": "us-east-1"
}
}
}
}
Windows:
{
"mcpServers": {
"awslabs.aws-pricing-mcp-server": {
"command": "uvx",
"args": [
"--from", "awslabs.aws-pricing-mcp-server@latest",
"awslabs.aws-pricing-mcp-server.exe"
],
"env": {
"FASTMCP_LOG_LEVEL": "ERROR",
"AWS_PROFILE": "your-aws-profile",
"AWS_REGION": "us-east-1"
}
}
}
}
Add the above to ~/.claude/claude_desktop_config.json (Claude Desktop) or .claude/mcp.json (Claude Code) under mcpServers.
Reference Loading Strategy
Load only the reference file that matches the user's question. Never bulk-load all three.
| User is asking about | Load |
|---|---|
| Module structure, naming, state, backend buckets | references/terraform-structure.md (184 lines) |
| IAM policies, least privilege, role design, SCPs, Checkov checks, KMS, secrets in state, OIDC | references/security-iam.md (327 lines) |
| CI/CD, GitHub Actions, OIDC, pipelines, ephemeral envs, drift | references/cicd-patterns.md (642 lines) |
For infrastructure reviews: load terraform-structure.md + security-iam.md (511 lines). Only add cicd-patterns.md if the review scope includes pipeline config.