When hosting a scalable, multi-AZ 3-tier web application (like WordPress), storing media files locally on the EC2 instances is an architectural dead end. The moment your auto-scaler destroys an instance, your user uploads vanish.
The standard fix is offloading media to Amazon S3. But this introduces a massive security risk: if your instance profile or an IAM key leaks, your entire storage layer is exposed to the public internet.
In this post, we’ll build a production-ready, highly available media storage layer that implements a Zero-Trust Network Perimeter around S3 using Terraform. Even if someone steals your IAM credentials, they cannot access the data unless they are sitting inside your private VPC.
The Strategy: Network-Level Isolation
Instead of relying solely on identity (who is asking?), we enforce network-level isolation (where are they asking from?). Even if someone steals your IAM credentials, they cannot access the data unless they are sitting inside your specific VPC.
1. The Infrastructure
A highly available network across multiple Availability Zones (AZs) with isolated private subnets where our application servers live.
2. The Gateway
An AWS VPC Endpoint (S3 Gateway) configured inside our route tables so traffic to S3 never traverses the public internet.
3. The Lock
A restrictive S3 Bucket Policy that explicitly denies any API calls (s3:GetObject, s3:PutObject) unless the request originates from our specific VPC Endpoint ID.
## Create the VPC Endpoint for S3 ##
resource "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.aws_region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = var.private_route_table_ids
tags = {
Name = "s3-gateway-endpoint"
}
}
## Enforce the Zero-Trust Bucket Policy ##
resource "aws_s3_bucket_policy" "secure_perimeter" {
bucket = aws_s3_bucket.media_assets.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AccessFromVpceOnly"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.media_assets.arn,
"${aws_s3_bucket.media_assets.arn}/*"
]
Condition = {
StringNotEquals = {
"aws:sourceVpce" = aws_vpc_endpoint.s3.id
}
}
}
]
})
}
Conclusion
By shifting our security model from identity-only to identity + network perimeter, we eliminate entire classes of credential-theft attacks. It doesn't matter if an attacker finds an exposed AWS key on GitHub—if they aren't executing code from within your specific VPC subnets, the data remains dark.
Top comments (0)