Terraform Security and Best Practices
/ 6 min read
Series Navigation
- Part 1: Terraform Fundamentals
- Part 2: Resource Management and State
- Part 3: Essential Terraform Functions
- Part 4: Variables, Outputs, and Dependencies
- Part 5: Terraform Modules and Workspace Management
- Part 6: Managing Remote State and Backend
- Part 7: Testing and CI/CD Integration
- Part 8: Terraform Security and Best Practices (Current)
Security Best Practices
Secure State Management
terraform { backend "s3" { bucket = "terraform-state-bucket" key = "prod/terraform.tfstate" region = "us-west-2" encrypt = true kms_key_id = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
dynamodb_table = "terraform-lock" }}
# state-bucket.tfresource "aws_s3_bucket" "terraform_state" { bucket = "terraform-state-bucket"
versioning { enabled = true }
server_side_encryption_configuration { rule { apply_server_side_encryption_by_default { kms_master_key_id = aws_kms_key.terraform_state.id sse_algorithm = "aws:kms" } } }
lifecycle { prevent_destroy = true }}
resource "aws_s3_bucket_public_access_block" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true}Secrets Management
# Using AWS Secrets Managerdata "aws_secretsmanager_secret" "database" { name = "prod/database"}
data "aws_secretsmanager_secret_version" "database" { secret_id = data.aws_secretsmanager_secret.database.id}
locals { db_creds = jsondecode(data.aws_secretsmanager_secret_version.database.secret_string)}
resource "aws_db_instance" "main" { username = local.db_creds.username password = local.db_creds.password
# Other configuration...}
# Using HashiCorp Vaultprovider "vault" { address = "https://vault.example.com:8200"}
data "vault_generic_secret" "database" { path = "secret/database"}
resource "aws_db_instance" "main" { username = data.vault_generic_secret.database.data["username"] password = data.vault_generic_secret.database.data["password"]
# Other configuration...}IAM Best Practices
# Least Privilege IAM Policyresource "aws_iam_role" "app" { name = "app-role"
assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } } ] })}
resource "aws_iam_role_policy" "app" { name = "app-policy" role = aws_iam_role.app.id
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:ListBucket" ] Resource = [ aws_s3_bucket.app.arn, "${aws_s3_bucket.app.arn}/*" ] } ] })}Code Organization Best Practices
Project Structure
.├── environments/│ ├── dev/│ │ ├── main.tf│ │ ├── variables.tf│ │ └── terraform.tfvars│ └── prod/│ ├── main.tf│ ├── variables.tf│ └── terraform.tfvars├── modules/│ ├── networking/│ │ ├── main.tf│ │ ├── variables.tf│ │ └── outputs.tf│ └── compute/│ ├── main.tf│ ├── variables.tf│ └── outputs.tf├── policies/│ ├── iam/│ └── security/└── scripts/ ├── deploy.sh └── cleanup.shModule Design Patterns
variable "vpc_config" { description = "VPC configuration" type = object({ cidr_block = string name = string subnets = list(object({ cidr_block = string zone = string public = bool })) })
validation { condition = can(cidrhost(var.vpc_config.cidr_block, 0)) error_message = "VPC CIDR block must be valid." }}
# modules/networking/main.tfresource "aws_vpc" "main" { cidr_block = var.vpc_config.cidr_block
tags = { Name = var.vpc_config.name }}
resource "aws_subnet" "main" { for_each = { for idx, subnet in var.vpc_config.subnets : "${subnet.zone}-${subnet.public ? "public" : "private"}" => subnet }
vpc_id = aws_vpc.main.id cidr_block = each.value.cidr_block availability_zone = each.value.zone
tags = { Name = "${var.vpc_config.name}-${each.key}" Type = each.value.public ? "public" : "private" }}Resource Management
Resource Naming Convention
locals { name_prefix = "${var.environment}-${var.project}" common_tags = { Environment = var.environment Project = var.project ManagedBy = "terraform" Owner = "infrastructure-team" }}
resource "aws_instance" "web" { count = var.instance_count
ami = var.ami_id instance_type = var.instance_type
tags = merge( local.common_tags, { Name = "${local.name_prefix}-web-${count.index + 1}" Role = "web" } )}Resource Dependencies
# Explicit Dependenciesresource "aws_security_group" "web" { # Configuration...}
resource "aws_instance" "web" { depends_on = [aws_security_group.web]
vpc_security_group_ids = [aws_security_group.web.id] # Other configuration...}
# Implicit Dependenciesresource "aws_db_instance" "main" { # Configuration...}
resource "aws_ssm_parameter" "db_endpoint" { name = "/app/database/endpoint" type = "String" value = aws_db_instance.main.endpoint}Cost Optimization
Resource Scheduling
# Auto Scaling Based on Scheduleresource "aws_autoscaling_schedule" "scale_down" { scheduled_action_name = "scale-down" min_size = 1 max_size = 1 desired_capacity = 1 recurrence = "0 18 * * MON-FRI" # 6 PM on weekdays autoscaling_group_name = aws_autoscaling_group.app.name}
resource "aws_autoscaling_schedule" "scale_up" { scheduled_action_name = "scale-up" min_size = 2 max_size = 4 desired_capacity = 2 recurrence = "0 8 * * MON-FRI" # 8 AM on weekdays autoscaling_group_name = aws_autoscaling_group.app.name}Cost Tagging
# Cost Allocation Tagslocals { cost_tags = { CostCenter = var.cost_center Project = var.project_name Environment = var.environment }}
resource "aws_instance" "app" { # ... other configuration ...
tags = merge( local.cost_tags, { Name = "${var.environment}-app" } )}Compliance and Governance
Policy as Code
# AWS Organizations Service Control Policyresource "aws_organizations_policy" "compliance" { name = "compliance-policy"
content = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Deny" Action = [ "s3:PutBucketPublicAccessBlock", "s3:PutBucketPolicy", "s3:DeleteBucketPolicy" ] Resource = "*" Condition = { StringNotEquals = { "aws:PrincipalOrgID": aws_organizations_organization.main.id } } } ] })}
# AWS Config Rulesresource "aws_config_config_rule" "encrypted_volumes" { name = "encrypted-volumes"
source { owner = "AWS" source_identifier = "ENCRYPTED_VOLUMES" }
scope { compliance_resource_types = ["AWS::EC2::Volume"] }}Compliance Monitoring
# CloudWatch Log Metric Filtersresource "aws_cloudwatch_log_metric_filter" "unauthorized_api_calls" { name = "unauthorized-api-calls" pattern = "{ $.errorCode = \"*UnauthorizedOperation\" }" log_group_name = aws_cloudwatch_log_group.cloudtrail.name
metric_transformation { name = "UnauthorizedAPICalls" namespace = "CloudTrailMetrics" value = "1" }}
resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls" { alarm_name = "unauthorized-api-calls" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" metric_name = "UnauthorizedAPICalls" namespace = "CloudTrailMetrics" period = "300" statistic = "Sum" threshold = "1"
alarm_actions = [aws_sns_topic.security_alerts.arn]}Documentation Best Practices
Module Documentation
/** * # VPC Module * * This module creates a VPC with public and private subnets * across multiple availability zones. * * ## Usage * * ```hcl * module "vpc" { * source = "./modules/vpc" * * vpc_config = { * cidr_block = "10.0.0.0/16" * name = "main" * subnets = [ * { * cidr_block = "10.0.1.0/24" * zone = "us-west-2a" * public = true * } * ] * } * } * ``` */
variable "vpc_config" { description = "VPC configuration object" type = object({ cidr_block = string name = string subnets = list(object({ cidr_block = string zone = string public = bool })) })}README Templates
# Infrastructure Module
## Overview
This module manages core infrastructure components including VPC, subnets, and security groups.
## Requirements
| Name | Version ||------|---------|| terraform | >= 1.0.0 || aws | >= 4.0.0 |
## Providers
| Name | Version ||------|---------|| aws | >= 4.0.0 |
## Resources
| Name | Type ||------|------|| aws_vpc.main | resource || aws_subnet.public | resource || aws_subnet.private | resource |
## Inputs
| Name | Description | Type | Default | Required ||------|-------------|------|---------|:--------:|| vpc_cidr | CIDR block for VPC | string | n/a | yes || environment | Environment name | string | n/a | yes |
## Outputs
| Name | Description ||------|-------------|| vpc_id | ID of the VPC || public_subnet_ids | List of public subnet IDs |Conclusion
This concludes our Terraform series! We’ve covered everything from the basics to advanced concepts, security practices, and best practices for managing infrastructure as code. Here’s a quick recap of what we’ve learned:
- Terraform fundamentals and basic concepts
- Resource management and state handling
- Functions and expressions for dynamic configurations
- Variables, outputs, and dependency management
- Modules and workspace organization
- Remote state and backend configuration
- Testing and CI/CD integration
- Security and best practices
Remember to always follow security best practices, maintain clean and organized code, and document your infrastructure well. Happy infrastructure coding!