Skip to content

Instantly share code, notes, and snippets.

@gluehbirnenkopf
Last active March 9, 2024 09:04
Show Gist options
  • Save gluehbirnenkopf/d0d9399dbd8e25520859bf6e575b0085 to your computer and use it in GitHub Desktop.
Save gluehbirnenkopf/d0d9399dbd8e25520859bf6e575b0085 to your computer and use it in GitHub Desktop.
This will create a VPC with public and private subnets including a EC2 based jumphost which can be accessed via SSM. It is very useful for building up a quick Lab environment in AWS. It cleanly creates and destroys with a single `terraform apply`
locals {
vpc_cidr = "192.168.0.0/16"
vpc_interface_endpoints = ["ssm", "ec2messages", "ssmmessages"]
region = "eu-central-1"
availability_zones = ["eu-central-1a", "eu-central-1b"]
ec2_bastion_ami = "ami-0b1a8f3698a954358"
ec2_bastion_instance_class = "t3a.micro"
public_subnet_offset = 0
public_subnet_network_bits = 8
private_subnet_offset = 2
private_subnet_network_bits = 8
}
# Minimal VPC Setup
resource "aws_vpc" "cisco_transit_vpc" {
cidr_block = local.vpc_cidr
enable_dns_hostnames = true
tags = {
Name = "cisco-transit-vpc"
}
}
resource "aws_default_security_group" "default" {
vpc_id = aws_vpc.cisco_transit_vpc.id
tags = {
Name = "cisco-transit-vpc"
}
}
resource "aws_internet_gateway" "primary" {
vpc_id = aws_vpc.cisco_transit_vpc.id
tags = {
Name = "cisco-transit-vpc-igw"
}
}
resource "aws_subnet" "public" {
for_each = toset(local.availability_zones)
vpc_id = aws_vpc.cisco_transit_vpc.id
availability_zone = each.value
cidr_block = cidrsubnet(
aws_vpc.cisco_transit_vpc.cidr_block,
local.public_subnet_network_bits,
index(local.availability_zones, each.value) + local.public_subnet_offset,
)
tags = {
Name = "public-cisco-transit-vpc-${each.key}"
}
}
resource "aws_subnet" "private" {
for_each = toset(local.availability_zones)
vpc_id = aws_vpc.cisco_transit_vpc.id
availability_zone = each.value
cidr_block = cidrsubnet(
aws_vpc.cisco_transit_vpc.cidr_block,
local.private_subnet_network_bits,
index(local.availability_zones, each.value) + local.private_subnet_offset,
)
tags = {
Name = "private-cisco-transit-vpc-${each.key}"
}
}
resource "aws_eip" "nat_gateway" {
for_each = toset(local.availability_zones)
tags = {
Name = "eip-nat-gateway-cisco-transit-vpc-${each.value}"
}
}
resource "aws_nat_gateway" "primary" {
for_each = toset(local.availability_zones)
allocation_id = aws_eip.nat_gateway[each.value].id
subnet_id = aws_subnet.public[each.key].id
tags = {
Name = "nat-gateway-cisco-transit-vpc-${each.value}"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.cisco_transit_vpc.id
tags = {
Name = "public-cisco-transit-vpc"
}
}
resource "aws_route_table" "private" {
for_each = toset(local.availability_zones)
vpc_id = aws_vpc.cisco_transit_vpc.id
tags = {
Name = "private-cisco-transit-vpc-${each.value}"
}
}
resource "aws_route" "nat_gateway_route" {
for_each = toset(local.availability_zones)
destination_cidr_block = "0.0.0.0/0"
route_table_id = aws_route_table.private[each.value].id
nat_gateway_id = aws_nat_gateway.primary[each.value].id
}
resource "aws_route" "igw_route" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.primary.id
}
resource "aws_route_table_association" "private" {
for_each = toset(local.availability_zones)
subnet_id = aws_subnet.private[each.value].id
route_table_id = aws_route_table.private[each.value].id
}
resource "aws_route_table_association" "public" {
for_each = toset(local.availability_zones)
subnet_id = aws_subnet.public[each.value].id
route_table_id = aws_route_table.public.id
}
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.cisco_transit_vpc.id
vpc_endpoint_type = "Gateway"
service_name = "com.amazonaws.${local.region}.s3"
route_table_ids = [for route_table in aws_route_table.private : route_table.id]
tags = {
Name = "cisco-transit-vpc-s3-gateway"
}
}
data "aws_vpc_endpoint_service" "transit-vpc" {
for_each = toset(local.vpc_interface_endpoints)
service = each.value
}
resource "aws_security_group" "calls-only-from-vpc" {
name = "cisco-transit-vpc-endpoints"
vpc_id = aws_vpc.cisco_transit_vpc.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.cisco_transit_vpc.cidr_block]
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_vpc_endpoint" "interface" {
for_each = toset(local.vpc_interface_endpoints)
vpc_id = aws_vpc.cisco_transit_vpc.id
service_name = data.aws_vpc_endpoint_service.transit-vpc[each.key].service_name
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = [for subnet in aws_subnet.private : subnet.id]
security_group_ids = [aws_security_group.calls-only-from-vpc.id]
tags = {
Name = "cisco-transit-vpc-${each.value}"
}
}
resource "aws_iam_role" "ec2_role" {
name = "cisco-transit-vpc-bastion-host"
assume_role_policy = data.aws_iam_policy_document.ec2_assume_role_policy.json
}
resource "aws_iam_instance_profile" "ec2_profile" {
name = "cisco-transit-vpc-bastion-host"
role = aws_iam_role.ec2_role.name
}
resource "aws_iam_role_policy_attachment" "ec2-for-ssm" {
policy_arn = aws_iam_policy.ec2-for-ssm.arn
role = aws_iam_role.ec2_role.id
}
resource "aws_instance" "ec2-bastion" {
ami = local.ec2_bastion_ami
instance_type = local.ec2_bastion_instance_class
subnet_id = aws_subnet.private[local.availability_zones[0]].id
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
tags = {
Name = "cisco-transit-vpc-bastion-host"
}
root_block_device {
encrypted = true
}
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 5
}
}
resource "aws_security_group" "ec2_sg" {
name = "cisco-transit-vpc-bastion-host"
description = "Security Group for private EC2 instance to allow private outbound traffic to other instances in vpc"
vpc_id = aws_vpc.cisco_transit_vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [aws_vpc.cisco_transit_vpc.cidr_block]
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id]
}
}
data "aws_iam_policy_document" "ec2_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
identifiers = ["ec2.amazonaws.com"]
type = "Service"
}
}
}
data "aws_iam_policy" "ec2_role_for_ssm_policy" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_policy" "ec2-for-ssm" {
name = "cisco-transit-vpc-bastion-host-allow-ssm"
policy = data.aws_iam_policy.ec2_role_for_ssm_policy.policy
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment