Bastion Host란?
![](/posts/aws/bastion-host/cover.png)
목차
서론 #
AWS에서는 하나의 VPC를 여러 서브넷으로 나눌 수 있으며, 각 서브넷에 EC2 인스턴스를 생성할 수 있습니다. 인터넷에 연결되거나 사용자에게 노출되어야 하는 FE/BE 서버는 public이어야 합니다. 그러나 데이터베이스와 같이 인터넷에 공개되어서는 안 되는 인스턴스도 있습니다. 즉, 이러한 인스턴스는 private이어야 합니다. 따라서 public 서브넷과 private 서브넷 두 가지 유형의 서브넷을 만듭니다.
private 서브넷에 있는 인스턴스에 직접 연결할 수 없기 때문에 private 인스턴스에 접근하기 위해서만 public 인스턴스를 만들어야 합니다. 이 인스턴스를 bastion host라고 합니다. bastion host를 사용하면 public에서 private 인스턴스를 보호할 수 있고 동시에 private 인스턴스의 연결에 대한 이력과 권한을 관리하기 쉽습니다.
이 섹션에서는 AWS에서 Terraform을 사용하여 인스턴스를 구성할 것입니다.
VPC와 서브넷 #
resource "aws_vpc" "default_vpc" {
# cidr_block은 "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"
}
}
서브넷 자체는 public 서브넷과 사설 서브넷으로 구분될 수 없습니다. 서브넷이 public인지 private인지 결정하려면 라우팅 테이블과 보안 그룹을 정의해야 합니다.
라우팅 테이블 및 인터넷 게이트웨이 #
# VPC가 생성될 때 기본 라우트 테이블이 만들어집니다
resource "aws_route_table" "public_route_table" {
vpc_id = aws_vpc.default_vpc.id
tags = {
Name = "public_route_table"
}
}
# public 라우팅 테이블에는 인터넷 게이트웨이가 필요합니다
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
}
# public 서브넷을 public 라우팅 테이블에 할당
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
}
라우팅 테이블은 네트워크 인터페이스, 게이트웨이 로드 밸런서 엔드포인트 또는 기본 로컬 라우트 이외의 대상을 가진 기존 라우트를 포함합니다. 라우팅 테이블은 VPC의 범위 밖 CIDR 블록으로의 기존 라우트를 포함합니다.
한 서브넷은 하나의 라우팅 테이블에만 할당될 수 있습니다. 명시적으로 연결이 정의되지 않은 경우, 서브넷은 VPC 생성 시 만들어진 기본 라우트 테이블에 연결됩니다.
# private 라우팅 테이블도 생성
resource "aws_route_table" "private_route_table" {
vpc_id = aws_vpc.default_vpc.id
tags = {
Name = "private_route_table"
}
}
# private 서브넷을 private 라우팅 테이블에 할당
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 라우팅 테이블 #
10.0.0.0/16은 default_vpc
의 CIDR 블록을 나타내며, 동일 VPC 내에서 로컬로 통신한다는 것을 의미합니다. 우리 VPC를 제외한 다른 대상은 default_igw
를 통해 public 인터넷으로 나갑니다. 이는 인터넷 게이트웨이입니다.
Private 라우팅 테이블 #
public_route_table
과 비교할 때, private_route_table
은 동일 VPC에 위치한 로컬 인스턴스와만 통신할 수 있습니다.
Public/Private Instance #
# public/private 인스턴스를 위한 보안 그룹
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
# 공용 IP를 할당하지 않도록 합니다
associate_public_ip_address = false
tags = {
Name = "test_private_instance"
}
}
# 자체 키 페어 생성
resource "aws_key_pair" "dev_key" {
key_name = "dev_key"
# SSH 공개 키 파일 경로
public_key = file("~/Desktop/server-key/dev_key.pub")
}
Public 인스턴스 #
Public 인스턴스의 경우, associate_public_ip_address를 true로 설정했기 때문에 공용 IPv4 주소가 있습니다. Terraform에서 aws_eip를 사용하여 인스턴스에 탄력적 IP를 부여할 수도 있습니다.
Private 인스턴스 #
Private 인스턴스의 경우, 공용 IPv4 주소를 통해 접근할 수 없습니다. 대신 private IPv4 주소를 사용해야 합니다. 그러나 private IP 주소(10.0.3.206
)는 공용 인터넷에 위치할 수 없습니다. 따라서 먼저 public 인스턴스(bastion host)에 접근한 다음, 동일 VPC에 위치한 10.0.3.206
을 찾을 수 있습니다.
Bastion host를 사용하여 private 인스턴스에 접근 #
이제 bastion host를 통해 private 인스턴스에 접근할 수 있습니다. 이제 매우 간단합니다. bastion host에 원격 SSH 접속을 한 다음, bastion host에서 private 호스트에 다시 SSH 접속을 합니다. 이렇게 하면 private 인스턴스에 접근할 수 있으며 보안이 더욱 강화됩니다.