############################################################################### # Lab 01 — The Misplaced NAT Gateway # # Deploys a VPC whose private subnet cannot reach the internet. The break is # real infrastructure misconfiguration, not a simulation: every resource below # is valid and the apply succeeds cleanly. # # terraform init # terraform apply # # Interface endpoints for SSM are included so you can get a shell on the private # instance even while its internet egress is broken. That is deliberate — it is # also how you should build this in production. # # COST: a NAT Gateway (~$0.045/hr) and four interface endpoints (~$0.01/hr each) # accrue charges whether traffic flows or not. Run `terraform destroy` when done. ############################################################################### terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.region default_tags { tags = { Project = "venerable-networks-lab" Lab = "01-misplaced-nat-gateway" ManagedBy = "terraform" } } } variable "region" { description = "Region to deploy the lab into." type = string default = "us-east-1" } variable "name" { description = "Name prefix for lab resources." type = string default = "vn-lab-01" } data "aws_availability_zones" "available" { state = "available" } locals { az = data.aws_availability_zones.available.names[0] } ############################################################################### # VPC and subnets ############################################################################### resource "aws_vpc" "lab" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { Name = "${var.name}-vpc" } } resource "aws_internet_gateway" "lab" { vpc_id = aws_vpc.lab.id tags = { Name = "${var.name}-igw" } } # Public tier — has a route to the internet gateway. resource "aws_subnet" "public" { vpc_id = aws_vpc.lab.id cidr_block = "10.0.0.0/24" availability_zone = local.az map_public_ip_on_launch = true tags = { Name = "${var.name}-public-a", Tier = "public" } } # Private tier — application workloads live here. resource "aws_subnet" "private" { vpc_id = aws_vpc.lab.id cidr_block = "10.0.11.0/24" availability_zone = local.az tags = { Name = "${var.name}-private-a", Tier = "private" } } ############################################################################### # NAT Gateway ############################################################################### resource "aws_eip" "nat" { domain = "vpc" tags = { Name = "${var.name}-nat-eip" } } resource "aws_nat_gateway" "lab" { allocation_id = aws_eip.nat.id subnet_id = aws_subnet.private.id tags = { Name = "${var.name}-nat" } depends_on = [aws_internet_gateway.lab] } ############################################################################### # Route tables ############################################################################### resource "aws_route_table" "public" { vpc_id = aws_vpc.lab.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.lab.id } tags = { Name = "${var.name}-rtb-public" } } resource "aws_route_table_association" "public" { subnet_id = aws_subnet.public.id route_table_id = aws_route_table.public.id } resource "aws_route_table" "private" { vpc_id = aws_vpc.lab.id route { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.lab.id } tags = { Name = "${var.name}-rtb-private" } } resource "aws_route_table_association" "private" { subnet_id = aws_subnet.private.id route_table_id = aws_route_table.private.id } ############################################################################### # SSM access path # # Interface endpoints keep Session Manager working independently of internet # egress, so the instance stays reachable while you debug. ############################################################################### resource "aws_security_group" "endpoints" { name = "${var.name}-endpoints" description = "HTTPS from within the VPC to interface endpoints" vpc_id = aws_vpc.lab.id ingress { description = "HTTPS from VPC" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = [aws_vpc.lab.cidr_block] } tags = { Name = "${var.name}-sg-endpoints" } } resource "aws_vpc_endpoint" "ssm" { for_each = toset(["ssm", "ssmmessages", "ec2messages"]) vpc_id = aws_vpc.lab.id service_name = "com.amazonaws.${var.region}.${each.key}" vpc_endpoint_type = "Interface" subnet_ids = [aws_subnet.private.id] security_group_ids = [aws_security_group.endpoints.id] private_dns_enabled = true tags = { Name = "${var.name}-vpce-${each.key}" } } ############################################################################### # Test instance ############################################################################### resource "aws_security_group" "app" { name = "${var.name}-app" description = "Lab application instance" vpc_id = aws_vpc.lab.id egress { description = "All outbound" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-sg-app" } } data "aws_iam_policy_document" "ec2_assume" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } } } resource "aws_iam_role" "ssm" { name = "${var.name}-ssm-role" assume_role_policy = data.aws_iam_policy_document.ec2_assume.json } resource "aws_iam_role_policy_attachment" "ssm" { role = aws_iam_role.ssm.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } resource "aws_iam_instance_profile" "ssm" { name = "${var.name}-ssm-profile" role = aws_iam_role.ssm.name } data "aws_ssm_parameter" "al2023" { name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64" } resource "aws_instance" "app" { ami = data.aws_ssm_parameter.al2023.value instance_type = "t3.micro" subnet_id = aws_subnet.private.id vpc_security_group_ids = [aws_security_group.app.id] iam_instance_profile = aws_iam_instance_profile.ssm.name metadata_options { http_tokens = "required" } tags = { Name = "${var.name}-app" } } ############################################################################### # Outputs ############################################################################### output "instance_id" { description = "Start here: aws ssm start-session --target " value = aws_instance.app.id } output "nat_gateway_id" { value = aws_nat_gateway.lab.id } output "private_route_table_id" { value = aws_route_table.private.id } output "public_subnet_id" { value = aws_subnet.public.id } output "private_subnet_id" { value = aws_subnet.private.id } output "start_session_command" { description = "Copy/paste to get a shell on the private instance." value = "aws ssm start-session --region ${var.region} --target ${aws_instance.app.id}" }