아키텍처 케이스 · 4
퍼블릭 서브넷에 데이터베이스를 뒀다
"보안 그룹으로 내 IP만 열어 뒀으니 괜찮다"는 판단이 왜 부족한지 분해합니다. 노출은 라우팅 테이블 · 퍼블릭 주소 · 보안 그룹 세 조건이 동시에 성립할 때 생기고, 그중 하나만 실수로 넓어져도 즉시 인터넷에 열립니다. 이 케이스는 세 조건을 하나씩 제거하면서 무엇이 남는지 확인합니다.
학습 목표
- 퍼블릭 서브넷을 정의하는 것이 이름이 아니라 라우팅 테이블이라는 점을 설명할 수 있습니다.
- 노출이 성립하는 세 조건을 분리하고, 각각을 어떻게 제거하는지 설계할 수 있습니다.
- 보안 그룹과 네트워크 ACL의 적용 계층과 상태 저장 여부를 구분해 배치할 수 있습니다.
- Amazon RDS의 퍼블릭 액세스 설정과 DB 서브넷 그룹의 관계를 설명할 수 있습니다.
- 시험 문항에서 "보안 그룹으로 특정 IP를 차단한다"류 선택지를 즉시 소거할 수 있습니다.
상황
스타트업의 초기 인프라를 그대로 쓰고 있는 서비스입니다. VPC에 서브넷이 두 개뿐이고, 둘 다 라우팅 테이블에 인터넷 게이트웨이로 향하는 기본 경로가 있습니다. 애플리케이션 서버와 Amazon RDS 인스턴스가 같은 서브넷에 있습니다.
RDS 인스턴스는 퍼블릭 액세스가 켜진 상태로 만들어졌습니다. 당시 개발자가 사무실 노트북에서 직접 접속해 스키마를 만들어야 했기 때문입니다. 보안 그룹은 데이터베이스 포트에 대해 사무실 고정 IP 하나만 허용하고 있었고, 여기까지는 팀도 알고 있었습니다.
문제는 지난 분기에 생겼습니다.
- 재택근무가 늘면서 "임시로" 보안 그룹에
0.0.0.0/0규칙이 추가되었고, 그 규칙이 지워지지 않았습니다. - 서브넷의
MapPublicIpOnLaunch가 켜져 있어 새로 만든 인스턴스마다 퍼블릭 주소가 붙습니다. - 데이터베이스 사용자 하나가 여전히 초기 설정 비밀번호를 쓰고 있었습니다.
보안 점검에서 데이터베이스 엔드포인트가 인터넷에서 조회 가능한 주소로 해석된다는 사실이 확인되었습니다. 유출 여부는 아직 확인되지 않았습니다.
노출은 세 조건의 결합이다
인터넷에서 데이터베이스에 도달하려면 아래 셋이 모두 성립해야 합니다. 하나만 끊어도 경로는 성립하지 않습니다. 그래서 처방을 고를 때 "어느 조건을 끊는가"로 이야기하면 논의가 명확해집니다.
| 조건 | 무엇을 의미하는가 | 어떻게 확인하는가 | 어떻게 제거하는가 |
|---|---|---|---|
| 1. 경로 | 서브넷의 라우팅 테이블에 인터넷 게이트웨이로 가는 경로가 있다 | 라우팅 테이블의 0.0.0.0/0 대상이 igw-인지 |
인터넷 게이트웨이 경로가 없는 서브넷으로 옮긴다 |
| 2. 주소 | 리소스에 퍼블릭 IP 또는 퍼블릭으로 해석되는 엔드포인트가 있다 | RDS의 퍼블릭 액세스 설정, 서브넷의 MapPublicIpOnLaunch |
퍼블릭 액세스를 끄고, 자동 퍼블릭 IP 할당을 끈다 |
| 3. 허용 | 보안 그룹이 그 출발지에서의 접속을 허용한다 | 인바운드 규칙의 소스가 CIDR인지 보안 그룹인지 | 소스를 애플리케이션 계층의 보안 그룹 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 연결과 하이브리드에 있습니다.
후보 설계안 비교
| 항목 | A · 보안 그룹만 조인다 | B · 네트워크 ACL로 인터넷 대역을 거부 | C · 프라이빗 서브넷으로 이전 + 퍼블릭 액세스 해제 | D · C + 세션 기반 접속 경로 |
|---|---|---|---|---|
| 끊는 조건 | 3(허용)만 | 3의 보강 | 1(경로)과 2(주소) | 1·2 + 요구 3까지 해결 |
| 규칙 하나 실수의 영향 | 즉시 인터넷 노출 | 두 겹이라 완화되지만 여전히 규칙 의존 | 경로가 없어 노출되지 않는다 | 동일 |
| 개발자 임시 접속 | 규칙 추가로 처리 — 재발 원인 | 동일 | 별도 수단이 필요하다 | 세션 기반 접속으로 제공 |
| 다운타임 | 없음 | 없음 | 서브넷 그룹 변경 시 계획된 창이 필요 | 동일 |
| 운영 부담 | 규칙을 계속 감시해야 한다 | 상태 비저장이라 규칙 설계가 까다롭다 | 초기 구성 후 낮다 | 접속 도구 운영이 추가된다 |
| 판정 | 불충분 — 원인을 그대로 둔다 | 보조 수단으로만 | 필수 조치 | 선택 — 요구 3까지 만족 |
보안 그룹과 네트워크 ACL의 역할 구분
B안이 왜 단독 해법이 아닌지는 두 통제의 성질 차이에서 나옵니다.
| 항목 | 보안 그룹 | 네트워크 ACL |
|---|---|---|
| 적용 계층 | 인스턴스(네트워크 인터페이스) | 서브넷 |
| 적용 범위 | 연결된 리소스에만 | 서브넷 안의 모든 리소스에 |
| 규칙 종류 | 허용 규칙만 | 허용과 거부 모두 |
| 평가 방식 | 모든 규칙을 평가 | 번호가 낮은 규칙부터 순서대로 |
| 상태 | 상태 저장 — 반환 트래픽 자동 허용 | 상태 비저장 — 반환 트래픽을 명시 허용해야 함 |
선택과 근거
D안 — 프라이빗 서브넷으로 옮기고, 퍼블릭 액세스를 끄고, 개발자 접속은 세션 기반 경로로 제공합니다.
근거는 하나로 요약됩니다. A안과 B안은 규칙에 의존하고, C·D안은 구조에 의존합니다. 규칙은 사람이 바꿀 수 있고 실제로 이 케이스에서 바뀌었습니다. 경로 자체가 없으면 규칙이 잘못 열려도 인터넷에서 도달할 수 없습니다.
목표 구성
| 계층 | 서브넷 | 아웃바운드 경로 | 인바운드 허용 소스 |
|---|---|---|---|
| 로드 밸런서 | 퍼블릭 (가용 영역마다 하나) | 인터넷 게이트웨이 | 인터넷 (HTTPS) |
| 애플리케이션 | 프라이빗 | NAT 게이트웨이 (가용 영역마다 하나) | 로드 밸런서의 보안 그룹 |
| 데이터베이스 | 프라이빗 (인터넷 경로 없음) | 없음 | 애플리케이션의 보안 그룹 |
데이터베이스 계층 서브넷에는 NAT 게이트웨이 경로조차 두지 않는 것이 기본입니다. 데이터베이스가 인터넷으로 나갈 이유가 없기 때문입니다. 패치는 관리형 서비스가 처리하고, 백업은 서비스 내부 경로로 갑니다.
보안 그룹을 CIDR이 아니라 보안 그룹으로 참조한다
데이터베이스 보안 그룹의 인바운드 소스를 애플리케이션 서버의 CIDR로 적으면, 서브넷을 늘리거나 인스턴스를 옮길 때마다 규칙을 고쳐야 합니다. 소스를 보안 그룹 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"}]
}]'
프라이빗 서브넷으로 옮기기
# 프라이빗 서브넷만으로 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)
- 탐지 — AWS Config 규칙으로 퍼블릭 액세스가 켜진 데이터베이스와
0.0.0.0/0이 허용된 보안 그룹을 상시 감시합니다. - 차단 — 조직 수준에서 서비스 제어 정책으로 퍼블릭 액세스를 켜는 변경을 막는 것도 가능합니다.
- 가시성 — VPC 흐름 로그로 데이터베이스 인터페이스에 대한 거부·수락 기록을 남겨 둡니다. 다만 흐름 로그는 5-튜플과 수락·거부만 기록하고 페이로드는 남기지 않습니다. "통신 내용을 감사하라"는 요구에는 답이 되지 않습니다.
비용과 운영에 미치는 영향
| 항목 | 변화 | 설명 |
|---|---|---|
| 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 흐름 로그(페이로드를 남기지 않는다) |
이어서 볼 곳
- 3장 · VPC 네트워킹 기초 CIDR 설계, 라우팅 테이블, 보안 그룹과 네트워크 ACL, 흐름 로그를 정면으로 다룹니다.
- 4장 · VPC 연결과 하이브리드 VPC 엔드포인트와 PrivateLink로 인터넷을 거치지 않는 경로를 만드는 방법입니다.
- 실습 1 · 3계층 VPC 직접 구성 퍼블릭·프라이빗 서브넷과 NAT, 라우팅을 CLI로 직접 만들어 봅니다.
- 실습 10 · VPC 엔드포인트로 S3 비공개 접근 게이트웨이 엔드포인트와 인터페이스 엔드포인트의 구현 위치 차이를 확인합니다.
- 케이스 1 · EC2에 액세스 키를 심어 배포했다 네트워크가 아니라 자격 증명 쪽에서 같은 종류의 노출이 어떻게 생기는지 비교합니다.
- 보안 아키텍처 설계 (30%) 가중치가 가장 큰 도메인의 출제 포인트와 함정을 정리했습니다.
공식 문서 출처
- Amazon VPC 사용 설명서 — VPC의 보안 — 보안 그룹과 네트워크 ACL의 적용 계층, 허용·거부 규칙 지원 여부, 상태 저장 여부 비교표
- Amazon VPC 사용 설명서 — 네트워크 ACL — 규칙이 번호 순서로 평가되고 반환 트래픽을 명시 허용해야 한다는 서술
- Amazon VPC 사용 설명서 — NAT 게이트웨이 — 지정한 서브넷에 네트워크 인터페이스를 만든다는 동작과 퍼블릭·프라이빗 NAT의 구분
- Amazon VPC 사용 설명서 — VPC 흐름 로그 — 기록되는 필드와 수락·거부 표시, 페이로드가 포함되지 않는다는 범위
- Amazon RDS 사용 설명서 — VPC의 DB 인스턴스 — DB 서브넷 그룹이 서로 다른 가용 영역의 서브넷을 포함해야 한다는 요건
- Amazon RDS API 레퍼런스 — CreateDBInstance
—
PubliclyAccessible의 엔드포인트 해석 동작과 DB 서브넷 그룹에 따른 기본값 - Amazon EC2 API 레퍼런스 — ModifySubnetAttribute
—
MapPublicIpOnLaunch속성의 의미 - Amazon EC2 API 레퍼런스 — AuthorizeSecurityGroupIngress — 인바운드 소스를 CIDR 대신 보안 그룹으로 지정하는 방법