학습 목표

상황

스타트업의 초기 인프라를 그대로 쓰고 있는 서비스입니다. VPC에 서브넷이 두 개뿐이고, 둘 다 라우팅 테이블에 인터넷 게이트웨이로 향하는 기본 경로가 있습니다. 애플리케이션 서버와 Amazon RDS 인스턴스가 같은 서브넷에 있습니다.

RDS 인스턴스는 퍼블릭 액세스가 켜진 상태로 만들어졌습니다. 당시 개발자가 사무실 노트북에서 직접 접속해 스키마를 만들어야 했기 때문입니다. 보안 그룹은 데이터베이스 포트에 대해 사무실 고정 IP 하나만 허용하고 있었고, 여기까지는 팀도 알고 있었습니다.

문제는 지난 분기에 생겼습니다.

보안 점검에서 데이터베이스 엔드포인트가 인터넷에서 조회 가능한 주소로 해석된다는 사실이 확인되었습니다. 유출 여부는 아직 확인되지 않았습니다.

데이터베이스 노출이 성립하는 세 조건과 하나씩 제거했을 때 위쪽은 인터넷에서 데이터베이스까지 실제로 닿는 경로입니다. 인터넷에서 인터넷 게이트웨이를 지나 데이터베이스에 도달하려면 가운데 상자의 세 조건이 동시에 참이어야 합니다. 첫째, 그 서브넷의 라우팅 테이블에 인터넷 게이트웨이로 나가는 경로가 있어야 합니다. 둘째, 인스턴스에 퍼블릭 IP 주소가 붙어 있어야 합니다. 셋째, 보안 그룹이 데이터베이스 포트를 0.0.0.0/0 으로 열어 두어야 합니다. 셋 중 하나라도 거짓이면 경로는 성립하지 않습니다. 아래 세 줄은 조건을 하나씩 제거했을 때 무엇이 남는지 보여 줍니다. 라우팅 경로를 빼면 인터넷에서 오는 길 자체가 사라져 그 서브넷이 프라이빗이 됩니다. 퍼블릭 IP 를 떼면 인터넷 쪽에서 지목할 목적지 주소가 없어집니다. 보안 그룹을 애플리케이션 보안 그룹 참조로 좁히면 포트가 닫혀 접속이 거부되고 앱 계층만 통과합니다. 결론은 서브넷의 이름이 아무것도 결정하지 않으며 이 세 조건의 결합만이 노출을 만든다는 것입니다. 노출이 성립하는 경로 — 세 조건이 모두 참일 때만 인터넷 IGW 세 조건이 함께 참 라우팅 테이블에 IGW 경로 인스턴스에 퍼블릭 IP 보안 그룹이 0.0.0.0/0 허용 데이터베이스 인터넷에서 접근 가능 조건을 하나씩 제거하면 무엇이 남는가 라우팅 테이블의 IGW 경로를 뺀다 인터넷에서 오는 길 자체가 사라집니다 — 프라이빗 서브넷 인스턴스의 퍼블릭 IP 를 뗀다 인터넷 쪽에서 지목할 목적지 주소가 없어집니다 보안 그룹을 앱 SG 참조로 좁힌다 포트가 닫혀 거부되고 앱 계층만 통과합니다 서브넷 이름은 아무것도 결정하지 않습니다 — 세 조건의 결합이 노출을 만듭니다
라우팅 테이블의 인터넷 게이트웨이 경로, 인스턴스에 붙은 퍼블릭 주소, 보안 그룹의 개방 규칙 세 조건이 결합해 데이터베이스 노출이 성립하는 경로와, 조건을 하나씩 제거했을 때 어디까지 막히는지의 대비

노출은 세 조건의 결합이다

인터넷에서 데이터베이스에 도달하려면 아래 셋이 모두 성립해야 합니다. 하나만 끊어도 경로는 성립하지 않습니다. 그래서 처방을 고를 때 "어느 조건을 끊는가"로 이야기하면 논의가 명확해집니다.

노출 성립 조건 3종과 각각의 확인·제거 방법
조건 무엇을 의미하는가 어떻게 확인하는가 어떻게 제거하는가
1. 경로 서브넷의 라우팅 테이블에 인터넷 게이트웨이로 가는 경로가 있다 라우팅 테이블의 0.0.0.0/0 대상이 igw-인지 인터넷 게이트웨이 경로가 없는 서브넷으로 옮긴다
2. 주소 리소스에 퍼블릭 IP 또는 퍼블릭으로 해석되는 엔드포인트가 있다 RDS의 퍼블릭 액세스 설정, 서브넷의 MapPublicIpOnLaunch 퍼블릭 액세스를 끄고, 자동 퍼블릭 IP 할당을 끈다
3. 허용 보안 그룹이 그 출발지에서의 접속을 허용한다 인바운드 규칙의 소스가 CIDR인지 보안 그룹인지 소스를 애플리케이션 계층의 보안 그룹 ID로 바꾼다
inspect-exposure.sh — 세 조건을 순서대로 확인 (리소스 ID는 예시입니다)
# 1) 이 서브넷이 실제로 퍼블릭인지 — 라우팅 테이블의 경로를 본다
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0123456789abcdef0 \
  --query 'RouteTables[].Routes[].[DestinationCidrBlock,GatewayId]' \
  --output table

# 2) 서브넷이 시작 시 퍼블릭 IP 를 자동 할당하는지
aws ec2 describe-subnets \
  --subnet-ids subnet-0123456789abcdef0 \
  --query 'Subnets[0].[SubnetId,MapPublicIpOnLaunch,AvailabilityZone]' \
  --output table

# 3) 데이터베이스가 퍼블릭 액세스로 설정되어 있는지
aws rds describe-db-instances \
  --db-instance-identifier shop-prod \
  --query 'DBInstances[0].[PubliclyAccessible,DBSubnetGroup.DBSubnetGroupName,Endpoint.Address]' \
  --output table

# 4) 어떤 소스가 데이터베이스 포트에 허용되어 있는지
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[0].IpPermissions'

요구사항과 제약

요구사항과 제약
구분 내용 설계에 주는 함의
요구 1 데이터베이스가 인터넷에서 도달 불가능해야 한다 세 조건 중 최소 두 개(경로·주소)를 구조적으로 제거한다
요구 2 애플리케이션 서버는 계속 접속할 수 있어야 한다 같은 VPC 안의 경로는 유지된다
요구 3 개발자의 임시 접속 경로가 필요하다 인터넷 개방이 아닌 다른 수단으로 제공한다
요구 4 같은 실수가 재발하지 않아야 한다 규칙 준수가 아니라 구조와 자동 탐지로 막는다
제약 1 데이터베이스 다운타임을 최소화한다 서브넷 이동은 계획된 창에서 수행한다
제약 2 애플리케이션은 엔드포인트 이름으로 접속한다 IP가 바뀌어도 설정 변경이 필요 없다

배경 개념은 3장 VPC 네트워킹 기초와 4장 VPC 연결과 하이브리드에 있습니다.

후보 설계안 비교

후보 설계안 4종 비교 — 어느 조건을 끊는가로 판정
항목 A · 보안 그룹만 조인다 B · 네트워크 ACL로 인터넷 대역을 거부 C · 프라이빗 서브넷으로 이전 + 퍼블릭 액세스 해제 D · C + 세션 기반 접속 경로
끊는 조건 3(허용)만 3의 보강 1(경로)과 2(주소) 1·2 + 요구 3까지 해결
규칙 하나 실수의 영향 즉시 인터넷 노출 두 겹이라 완화되지만 여전히 규칙 의존 경로가 없어 노출되지 않는다 동일
개발자 임시 접속 규칙 추가로 처리 — 재발 원인 동일 별도 수단이 필요하다 세션 기반 접속으로 제공
다운타임 없음 없음 서브넷 그룹 변경 시 계획된 창이 필요 동일
운영 부담 규칙을 계속 감시해야 한다 상태 비저장이라 규칙 설계가 까다롭다 초기 구성 후 낮다 접속 도구 운영이 추가된다
판정 불충분 — 원인을 그대로 둔다 보조 수단으로만 필수 조치 선택 — 요구 3까지 만족

보안 그룹과 네트워크 ACL의 역할 구분

B안이 왜 단독 해법이 아닌지는 두 통제의 성질 차이에서 나옵니다.

보안 그룹과 네트워크 ACL 비교
항목 보안 그룹 네트워크 ACL
적용 계층 인스턴스(네트워크 인터페이스) 서브넷
적용 범위 연결된 리소스에만 서브넷 안의 모든 리소스에
규칙 종류 허용 규칙만 허용과 거부 모두
평가 방식 모든 규칙을 평가 번호가 낮은 규칙부터 순서대로
상태 상태 저장 — 반환 트래픽 자동 허용 상태 비저장 — 반환 트래픽을 명시 허용해야 함

선택과 근거

D안 — 프라이빗 서브넷으로 옮기고, 퍼블릭 액세스를 끄고, 개발자 접속은 세션 기반 경로로 제공합니다.

근거는 하나로 요약됩니다. A안과 B안은 규칙에 의존하고, C·D안은 구조에 의존합니다. 규칙은 사람이 바꿀 수 있고 실제로 이 케이스에서 바뀌었습니다. 경로 자체가 없으면 규칙이 잘못 열려도 인터넷에서 도달할 수 없습니다.

목표 구성

계층별 서브넷 배치와 라우팅
계층 서브넷 아웃바운드 경로 인바운드 허용 소스
로드 밸런서 퍼블릭 (가용 영역마다 하나) 인터넷 게이트웨이 인터넷 (HTTPS)
애플리케이션 프라이빗 NAT 게이트웨이 (가용 영역마다 하나) 로드 밸런서의 보안 그룹
데이터베이스 프라이빗 (인터넷 경로 없음) 없음 애플리케이션의 보안 그룹

데이터베이스 계층 서브넷에는 NAT 게이트웨이 경로조차 두지 않는 것이 기본입니다. 데이터베이스가 인터넷으로 나갈 이유가 없기 때문입니다. 패치는 관리형 서비스가 처리하고, 백업은 서비스 내부 경로로 갑니다.

보안 그룹을 CIDR이 아니라 보안 그룹으로 참조한다

데이터베이스 보안 그룹의 인바운드 소스를 애플리케이션 서버의 CIDR로 적으면, 서브넷을 늘리거나 인스턴스를 옮길 때마다 규칙을 고쳐야 합니다. 소스를 보안 그룹 ID로 지정하면 그 보안 그룹에 속한 리소스가 늘어나도 규칙은 그대로입니다.

chain-security-groups.sh — 애플리케이션 보안 그룹만 데이터베이스 포트를 허용 (리소스 ID와 포트는 예시입니다)
# 잘못 추가된 전체 개방 규칙을 제거합니다.
aws ec2 revoke-security-group-ingress \
  --group-id sg-0db0000000000000d \
  --protocol tcp --port 3306 --cidr 0.0.0.0/0

# 소스를 CIDR 이 아니라 애플리케이션 계층의 보안 그룹으로 지정합니다.
aws ec2 authorize-security-group-ingress \
  --group-id sg-0db0000000000000d \
  --ip-permissions '[{
      "IpProtocol": "tcp",
      "FromPort": 3306,
      "ToPort": 3306,
      "UserIdGroupPairs": [{"GroupId": "sg-0app000000000000"}]
    }]'

프라이빗 서브넷으로 옮기기

move-db-private.sh — DB 서브넷 그룹 교체와 퍼블릭 액세스 해제 (식별자는 예시입니다)
# 프라이빗 서브넷만으로 DB 서브넷 그룹을 만듭니다.
# 서로 다른 가용 영역의 서브넷을 최소 두 개 넣습니다.
aws rds create-db-subnet-group \
  --db-subnet-group-name shop-private-subnets \
  --db-subnet-group-description "private subnets only" \
  --subnet-ids subnet-0aaa000000000000a subnet-0bbb000000000000b

# 퍼블릭 액세스를 끄고 서브넷 그룹을 교체합니다.
# 적용 시점은 유지 관리 창을 이용하거나 계획된 창에서 즉시 적용합니다.
aws rds modify-db-instance \
  --db-instance-identifier shop-prod \
  --no-publicly-accessible \
  --db-subnet-group-name shop-private-subnets \
  --apply-immediately

# 새로 만드는 서브넷이 퍼블릭 IP 를 자동 할당하지 않게 합니다.
aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0aaa000000000000a \
  --no-map-public-ip-on-launch

개발자 접속 경로 (요구 3)

"임시로 열어 두는" 규칙이 재발의 원인이므로, 규칙을 열지 않고도 접속할 수 있는 경로를 미리 제공합니다.

개발자 접속 경로 후보 비교
방법 인바운드 포트 개방 감사 비고
퍼블릭 배스천 호스트(SSH) 필요 — 22번 포트를 인터넷에 연다 호스트 로그에 의존 관리 대상 인스턴스가 하나 늘어난다
AWS Systems Manager 세션 기반 접속 불필요 — 아웃바운드만으로 동작 세션 기록과 API 호출 기록이 남는다 포트 전달로 데이터베이스까지 연결할 수 있다
Site-to-Site VPN 또는 Client VPN 불필요 연결 로그 사내망 연동이 이미 있으면 자연스럽다

어느 쪽을 고르든 원칙은 같습니다 — 사람이 접속하기 위해 인터넷 인바운드 포트를 열지 않습니다.

재발 방지 (요구 4)

비용과 운영에 미치는 영향

구조 변경이 만드는 비용·운영 항목의 변화
항목 변화 설명
NAT 게이트웨이 늘어난다 애플리케이션 계층의 아웃바운드용으로 가용 영역마다 필요하다. 시간과 처리량 모두 과금 항목이다
VPC 엔드포인트 선택적으로 늘어난다 Amazon S3·DynamoDB 트래픽을 NAT에서 걷어내면 NAT 처리량이 줄어든다. 게이트웨이 엔드포인트 경로가 자주 유리하다
배스천 호스트 줄어들거나 사라진다 세션 기반 접속을 쓰면 상시 인스턴스가 필요 없다
가용 영역 간 전송 설계에 따라 달라진다 NAT를 하나만 두고 다중 가용 영역에서 쓰면 가용 영역을 넘는 전송이 발생한다
사고 대응 시간 줄어든다 노출 경로가 구조적으로 없으므로 규칙 감사 범위가 좁아진다

데이터 전송 요금의 구조는 케이스 6 · 데이터 전송 요금이 예산을 넘겼다에서 자세히 다룹니다.

설계 체크리스트

네트워크 배치

접근 제어

탐지와 재발 방지

관련 시험 포인트

문제문의 신호와 정답 방향
문제문의 신호 고르는 것 소거하는 것
데이터베이스가 인터넷에서 접근 가능하다 프라이빗 서브넷 이전 + 퍼블릭 액세스 해제 보안 그룹만 좁히는 안
특정 IP 대역을 차단해야 한다 네트워크 ACL의 거부 규칙 보안 그룹으로 차단하는 안(거부 규칙이 없다)
요청은 나가는데 응답이 오지 않는다 네트워크 ACL 인바운드에 임시 포트 범위 허용 보안 그룹을 의심하는 안(상태 저장이라 자동 허용된다)
인스턴스가 늘어도 규칙을 고치고 싶지 않다 소스를 보안 그룹 ID로 지정 CIDR로 지정하는 안
must not traverse the internet VPC 엔드포인트 · PrivateLink NAT 게이트웨이 경유(인터넷을 지난다)
SSH 포트를 열지 않고 인스턴스에 접속 Systems Manager 세션 기반 접속 퍼블릭 배스천 호스트
통신 내용을 감사해야 한다 패킷 미러링 등 별도 수단 VPC 흐름 로그(페이로드를 남기지 않는다)

이어서 볼 곳

공식 문서 출처