Skip to main content
  1. Posts/

What is Bastion Host?

·818 words

Introduction #

In AWS, we can divide one VPC to several subnets. And EC2 instances can be created on each subnets. FE/BE server which is connected to internet or exposed to user must be public. However, there are some instances that shouldn’t be public on the internet (like database). In other words, these instances must be private. So, we make two different kinds of subnets, public subnet and private subent.

As we cannot directly connect to instances that goes on private subnet, we should make an public instance only for accessing private instances. This instance is called Bastion Host. Using Bastion Host, we can protect private instances from public, and in same time it is easy to manage history and authority about connection of private instances.

In this section, we’ll compose our instances using Terrafrom in AWS.

VPC and Subnets #

resource "aws_vpc" "default_vpc" {
  # cidr_block don't need to be "10.0.0.0/16"
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "terraform_vpc"
  }
}

resource "aws_subnet" "public_subnet" {
  vpc_id     = aws_vpc.default_vpc.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "ap-northeast-2a"
  tags = {
    Name = "terraform_subnet_a"
  }
}
resource "aws_subnet" "private_subnet" {
  vpc_id     = aws_vpc.default_vpc.id
  cidr_block = "10.0.3.0/24"
  availability_zone = "ap-northeast-2a"
  tags = {
    Name = "terraform_subnet_c"
  }
}

Subnet itself can’t be distinguished as public subnet and private subnet. We should define route table and security groups to decide subnets whether its public or private.

Route table & Internet Gateway #

# default route table is made when VPC is created
resource "aws_route_table" "public_route_table" {
	vpc_id = aws_vpc.default_vpc.id
	tags = {
    Name = "public_route_table"
  }
}

# Internet Gateway is needed for public_route_table
# for using Internet in public subnets
resource "aws_internet_gateway" "default_igw" {
	vpc_id = aws_vpc.default_vpc.id
	tags = {
    Name = "terraform_igw"
  }
}
resource "aws_route" "igw_association" {
	route_table_id = aws_route_table.public_route_table.id
	destination_cidr_block = "0.0.0.0/0"
	gateway_id = aws_internet_gateway.default_igw.id
}

# allocate public subnets to public_route_table
resource "aws_route_table_association" "public_subnet_association" {
	subnet_id = aws_subnet.public_subnet.id
	route_table_id = aws_route_table.public_route_table.id
}

The route table contains existing routes with targets other than a network interface, Gateway Load Balancer endpoint, or the default local route. ****The route table contains existing routes to CIDR blocks outside of the ranges in your VPC.

One subnet can be allocated by only one route table only. If association is not explicitly defined, the subnet is connected to default route table (created when VPC is made).

# create private_route table also
resource "aws_route_table" "private_route_table" {
	vpc_id = aws_vpc.default_vpc.id
	tags = {
    Name = "private_route_table"
  }
}
# allocate private subnets to private_route_table
resource "aws_route_table_association" "public_subnet_association" {
	subnet_id = aws_subnet.private_subnet.id
	route_table_id = aws_route_table.private_route_table.id
}

Public Route table #

public route table

10.0.0.0/16 represents the default_vpc’s CIDR blocks. Which means it’ll communicate locally in same VPC. Other targets except our own VPC will go through default_igw which is Internet Gateway that goes through public internet.

Private Route table #

private route table

Compare to public_route_table, private_route_table can only communicate with local instances which located in same VPC.

Public & Private Instance #

# security groups for public and private instances
resource "aws_security_group" "default_security_group" {
  name       = "default_security_group"
  vpc_id      = aws_vpc.default_vpc.id

  ingress {
    description = "Allow SSH traffic"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    description = "Allow HTTP traffic"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    description = "Allow HTTPS traffic"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "test_public_instance" {
  # Amazon Machine Image for ubuntu_22_04
  ami = "ami-058165de3b7202099"
  instance_type = "t2.micro"
  key_name = aws_key_pair.dev_key.key_name
  vpc_security_group_ids = [aws_security_group.default_security_group.id]
  # on public subnet
  subnet_id = aws_subnet.public_subnet.id
  associate_public_ip_address = true
  tags = {
    Name = "test_public_instance"
  }
}
resource "aws_instance" "test_private_instance" {
  # Amazon Machine Image for ubuntu_22_04
  ami = "ami-058165de3b7202099"
  instance_type = "t2.micro"
  key_name = aws_key_pair.dev_key.key_name
  vpc_security_group_ids = [aws_security_group.default_security_group.id]
  # on private subnet
  subnet_id = aws_subnet.private_subnet.id
  # don't allocate public_ip -> need to be private
  associate_public_ip_address = false
  tags = {
    Name = "test_private_instance"
  }
}

# create own key_pair
resource "aws_key_pair" "dev_key" {
  key_name   = "dev_key"
  # SSH public key file path
  public_key = file("~/Desktop/server-key/dev_key.pub")
}

Public Instance #

public instance

For public instance, we have public IPv4 address as we have set associate_public_ip_address = true. We can also give Elastic IP for instance using aws_eip in Terraform.

Private Instance #

private instance

For private instance, we cannot access through public IPv4 address. We need to use private IPv4 address rather than public ip. However, private ip address(10.0.3.206) cannot be located in public internet. So, we should access public instance(bastion host) first, then now it can locate 10.0.3.206 which located in same VPC.

Access Private Instance using Bastion Host #

Now you can access private instance through bastion host. It is now quite simple. Remote SSH access to bastion host, and the SSH access again to private host in bastion host. Which makes accessing private instance possible and more secure.