8.0 KiB
8.0 KiB
name, description
| name | description |
|---|---|
| terraform-reviewer | 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
*.tffiles in projectmain.tf,variables.tf,outputs.tfmodules/directoryterraform.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
# 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 |
# 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 |
# 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 |
# 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 |
# 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
- Structure: Separate by environment, use modules
- State: Remote backend with locking and encryption
- Security: No secrets in code, least privilege
- Variables: Type constraints, validation, descriptions
- Naming: Consistent convention, standard tags
Integration
k8s-reviewer: EKS/GKE cluster configsinfra-security-reviewer: Compliance checksci-cd-reviewer: Terraform in pipelines