--- name: terraform-reviewer description: | WHEN: Terraform code review, module structure, state management, security policies WHAT: Module organization + State backend + Security policies + Variable validation + Best practices WHEN NOT: Kubernetes → k8s-reviewer, Docker → docker-reviewer --- # Terraform Reviewer Skill ## Purpose Reviews Terraform code for module structure, state management, security, and best practices. ## When to Use - Terraform code review - Module structure review - State backend configuration - Security policy review - Variable and output review ## Project Detection - `*.tf` files in project - `main.tf`, `variables.tf`, `outputs.tf` - `modules/` directory - `terraform.tfvars` ## Workflow ### Step 1: Analyze Project ``` **Terraform**: 1.6+ **Provider**: AWS/GCP/Azure **Backend**: S3/GCS/Azure Blob **Modules**: Custom + Registry ``` ### Step 2: Select Review Areas **AskUserQuestion:** ``` "Which areas to review?" Options: - Full Terraform review (recommended) - Module structure - State management - Security and compliance - Variable validation multiSelect: true ``` ## Detection Rules ### Module Structure | Check | Recommendation | Severity | |-------|----------------|----------| | All resources in main.tf | Split by resource type | MEDIUM | | No modules | Extract reusable modules | MEDIUM | | Hardcoded values | Use variables | HIGH | | No outputs | Add relevant outputs | MEDIUM | ``` # GOOD: Project structure terraform/ ├── environments/ │ ├── dev/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── terraform.tfvars │ └── prod/ │ ├── main.tf │ ├── variables.tf │ └── terraform.tfvars ├── modules/ │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf │ └── eks/ │ ├── main.tf │ ├── variables.tf │ └── outputs.tf └── README.md ``` ```hcl # BAD: Hardcoded values resource "aws_instance" "web" { ami = "ami-12345678" instance_type = "t3.micro" tags = { Name = "web-server" } } # GOOD: Parameterized with variables variable "instance_type" { description = "EC2 instance type" type = string default = "t3.micro" validation { condition = can(regex("^t3\\.", var.instance_type)) error_message = "Instance type must be t3 family." } } variable "environment" { description = "Environment name" type = string validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be dev, staging, or prod." } } resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux.id instance_type = var.instance_type tags = merge(local.common_tags, { Name = "${var.project}-${var.environment}-web" }) } ``` ### State Management | Check | Recommendation | Severity | |-------|----------------|----------| | Local state | Use remote backend | CRITICAL | | No state locking | Enable DynamoDB/GCS lock | HIGH | | No state encryption | Enable encryption | HIGH | | Shared state file | Split by environment | MEDIUM | ```hcl # BAD: Local state (default) # No backend configuration # GOOD: Remote backend with locking terraform { backend "s3" { bucket = "mycompany-terraform-state" key = "prod/vpc/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-state-lock" } } # For GCP terraform { backend "gcs" { bucket = "mycompany-terraform-state" prefix = "prod/vpc" } } ``` ### Security | Check | Recommendation | Severity | |-------|----------------|----------| | Secrets in tfvars | Use secret manager | CRITICAL | | Public S3 bucket | Set ACL private | CRITICAL | | Open security group | Restrict CIDR | CRITICAL | | No encryption | Enable encryption | HIGH | ```hcl # BAD: Security issues resource "aws_security_group" "web" { ingress { from_port = 0 to_port = 65535 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # Wide open! } } resource "aws_s3_bucket" "data" { bucket = "my-data-bucket" acl = "public-read" # Public! } # GOOD: Secure configuration resource "aws_security_group" "web" { name = "${var.project}-web-sg" description = "Security group for web servers" vpc_id = var.vpc_id ingress { description = "HTTPS from load balancer" from_port = 443 to_port = 443 protocol = "tcp" security_groups = [aws_security_group.alb.id] } egress { description = "Allow all outbound" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = local.common_tags } resource "aws_s3_bucket" "data" { bucket = "${var.project}-${var.environment}-data" } 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 } resource "aws_s3_bucket_server_side_encryption_configuration" "data" { bucket = aws_s3_bucket.data.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.s3.arn } } } ``` ### Variable Validation | Check | Recommendation | Severity | |-------|----------------|----------| | No type constraint | Add type | MEDIUM | | No validation | Add validation block | MEDIUM | | No description | Add description | LOW | | Sensitive not marked | Add sensitive = true | HIGH | ```hcl # GOOD: Well-defined variables variable "vpc_cidr" { description = "CIDR block for VPC" type = string default = "10.0.0.0/16" validation { condition = can(cidrnetmask(var.vpc_cidr)) error_message = "Must be a valid CIDR block." } } variable "db_password" { description = "Database password" type = string sensitive = true # Won't show in logs validation { condition = length(var.db_password) >= 16 error_message = "Password must be at least 16 characters." } } variable "allowed_environments" { description = "List of allowed environment names" type = list(string) default = ["dev", "staging", "prod"] } ``` ### Resource Naming | Check | Recommendation | Severity | |-------|----------------|----------| | Inconsistent naming | Use naming convention | MEDIUM | | No tags | Add standard tags | MEDIUM | ```hcl # GOOD: Consistent naming and tagging locals { name_prefix = "${var.project}-${var.environment}" common_tags = { Project = var.project Environment = var.environment ManagedBy = "terraform" Owner = var.owner } } resource "aws_vpc" "main" { cidr_block = var.vpc_cidr tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" }) } ``` ## Response Template ``` ## Terraform Review Results **Project**: [name] **Terraform**: 1.6 | **Provider**: AWS ### Module Structure | Status | File | Issue | |--------|------|-------| | MEDIUM | main.tf | 500+ lines, should split | ### State Management | Status | File | Issue | |--------|------|-------| | CRITICAL | - | Using local state | ### Security | Status | File | Issue | |--------|------|-------| | CRITICAL | security.tf:23 | Security group allows 0.0.0.0/0 | ### Variables | Status | File | Issue | |--------|------|-------| | HIGH | variables.tf | db_password not marked sensitive | ### Recommended Actions 1. [ ] Configure remote state backend with locking 2. [ ] Restrict security group ingress rules 3. [ ] Mark sensitive variables 4. [ ] Split main.tf into logical files ``` ## Best Practices 1. **Structure**: Separate by environment, use modules 2. **State**: Remote backend with locking and encryption 3. **Security**: No secrets in code, least privilege 4. **Variables**: Type constraints, validation, descriptions 5. **Naming**: Consistent convention, standard tags ## Integration - `k8s-reviewer`: EKS/GKE cluster configs - `infra-security-reviewer`: Compliance checks - `ci-cd-reviewer`: Terraform in pipelines