들아가며
이번에는 VPC Endpoint 에 대해 이해하고 실습을 진행할 것이다.
VPC Endpoint
VPC Endpoint 란 VPC 내부 리소스가 VPC 외부에 접근할때 Private Link 를 사용해 사설 통신이 가능하게 해주는 서비스이다. 즉 IG(인터넷 게이트웨이), NAT 게이트웨이 등을 사용하지 않고 사설 통신 구간을 이용해 AWS 서비스와 통신하는것이다.
VPC EndPoint는 2가지 종류가 있다.
Gateway Endpoint는 쉽게 말해 라우팅 테이블로 길을 바꿔치기하는것이다.
S3, DynamoDB를 대상으로 Gateway Endpoint를 생성하면 선택한 RT에 S3/DDB로 가는 프라이빗 경로가 추가된다. 즉 RT 가 붙은 서브넷에서 나오는 S3/DDB 로 가는 트래픽은 NAT/IGW 로 가지않고 Endpoint로 라우팅 된다.
Interface Endpoint 는 VPC 안에 사설 출입구(ENI)를 설치하는것이다.
privateLink를 지원하는 대부분의 AWS 서비스에서 사용되며 Interface Endpoint를 만들면 VPC의 선택한 서브넷들에 Endpoint ENI(Elastic Network Interface) 가 생성되고 사설 IP가 붙게 된다. 또한 Private DNS를 사용하면 서비스 도메인이 공인 IP가 아닌 ENI의 사설 IP로 DNS 가 해석돼서 사설 경로로 접속이 된다. 또한 SG(보안그룹)으로 접근제어도 가능하다.
VPC Endpoint 도 실습을 통해 이해하면 빠르게 이해할 수 있다.
실습
VPC 내부(Private Subnet)의 리소스가 S3에 접근할 때
NAT Gateway를 통해 인터넷 경로로 나가서 접근하는 방식이 아니라
S3 Gateway Endpoint를 통해 AWS 내부 사설 경로로만 접근하도록 강제하고
S3 Bucket Policy를 aws:sourceVpce 조건으로 제한해서 Endpoint로 들어오는 트래픽만 허용되는지 확인한다.
S3 버킷용 VPC Endpoint 생성 (Gateway Type)
Web Server S3 접근 테스트
Web Server 접속
S3 Bucket Endpoint 생성
VPC 콘솔 메인 화면 → 엔드포인트 리소스 → 엔드포인트 생성
엔드포인트 생성 정보 입력
이름: lab-edu-endpoint-s3
서비스 이름 검색 창에 com.amazonaws.ap-northeast-2.s3 입력
유형 필드의 값이 Gateway인 항목 선택

VPC: lab-edu-vpc-ap-01
정책: 전체 액세스

여기서 정책: 전체 액세스는 Endpoint Policy를 말한다.
Gateway Endpoint는 생성만 해서는 끝이 아니고 반드시 라우팅 테이블 연결이 되어야
실제 트래픽이 Endpoint를 탄다
S3 Bucket Policy 수정
Bucket Policy를 설정하는 이유
- 앞에서 버킷에 설정 한 Policy는 Nat Gateway IP를 기반으로 접근하는 경우 Allow 하도록 설정했다.
- 해당 정책을 수정해서 VPC Endpoint로 접근하는 경우만 Allow 하도록 수정해서 실제로 트래픽이 내부 트래픽만 허용하는지 확인한다.
Bucket Policy 설정
lab-edu-bucket-image-{ACCOUNT_ID} 버킷 클릭 → 권한 탭버킷 정책 필드의 삭제 버튼 클릭삭제 텍스트 입력 → 삭제 버튼 클릭정책을 삭제하는 순간 버킷 접근은 IAM 권한(Identity-based policy) 위주로만 결정되기 쉬워진다.
버킷 정책을 일부러 비워 둔 상태에서 각 서버의 IAM 권한 차이를 확인하는 과정이 바로 다음 테스트다.
Bucket 접근 제한 확인
VS Code 서버와 Web 서버의 S3 접근 권한이 있는지 확인
S3 접근 권한을 확인하는 이유
- VS Code Server에는 S3 접근 권한이 존재있기 때문에 Bucket Policy에 추가 설정이 없어도 객체 조회가 가능하다.
- Web Server에는 S3 접근 권한이 없기 때문에 Nat Gateway를 이용해서 접근해도 객체 리스트 조회가 불가능 하다.
여기서 가능/불가능은 네트워크 경로(NAT/Endpoint) 이전에 IAM 권한(s3:ListBucket 등)에서 먼저 갈린다.
aws s3 ls s3://버킷은 내부적으로 ListBucket 권한이 필요하다. Web Server가 이 권한이 없다면 네트워크가 열려 있어도 AccessDenied가 나게 된다.
web-server
[ec2-user@ip-10-0-40-82 ~]$ aws s3 ls "s3://$BUCKET_NAME"
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:sts::593927188341:assumed-role/let-edu-role-ec2/i-0fc80c4eba7c7f4c6 is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::lab-edu-bucket-image-593927188341" because no identity-based policy allows the s3:ListBucket action
[ec2-user@ip-10-0-40-82 ~]$
vscode
ubuntu@ip-10-0-1-211:/Workshop$ ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
ubuntu@ip-10-0-1-211:/Workshop$ BUCKET_NAME="lab-edu-bucket-image-$ACCOUNT_ID"
ubuntu@ip-10-0-1-211:/Workshop$ aws s3 ls "s3://$BUCKET_NAME"
2026-01-15 08:20:47 47921 cocker_spaniel.jpg
2026-01-15 08:20:47 64638 corgi.jpg
2026-01-15 08:20:47 74181 german_shepherd.jpg
2026-01-15 08:20:47 88081 golden_retriever.jpg
2026-01-15 08:20:47 41784 husky.jpg
2026-01-15 08:20:47 33828 jack_russell_terrier.jpg
2026-01-15 08:20:47 44155 jack_terrier.jpg
2026-01-15 08:20:47 65919 pug.jpg
2026-01-15 08:20:47 52571 shiba_inu.jpg
ubuntu@ip-10-0-1-211:/Workshop$
VPC Endpoint에서 접근하는 트래픽만 허용하는 S3 Bucket Policy 생성
ubuntu@ip-10-0-1-211:/Workshop$ cd /Workshop/support_files/policy
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ sudo sh ./s3_bucket_policy_endpoint.sh
Contents of /Workshop/support_files/policy/s3_bucket_policy_endpoint_output.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::lab-edu-bucket-image-593927188341",
"arn:aws:s3:::lab-edu-bucket-image-593927188341/*"
],
"Condition": {
"StringEquals": {
"aws:sourceVpce": "vpce-019e1ad68fa325f7e"
}
}
}
]
}ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$
정책 반영 명령어 실행
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text))
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ BUCKET_NAME="lab-edu-bucket-image-$ACCOUNT_ID"
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ aws s3api put-bucket-policy --bucket $BUCKET_NAME --policy file://s3_bucket_policy_endpoint_output.json
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$
VPC Endpoint 통신 설정
VPC 콘솔 메인 화면 → 엔드포인트 리소스 탭 → lab-edu-endpoint-s3 선택 → 작업 → 라우팅 테이블 관리 버튼 클릭
lab-edu-rtb-pri-01, lab-edu-rtb-pri-02 선택 → 라우팅 테이블 수정 버튼 클릭

Gateway Endpoint는 인터페이스(ENI)가 생기는 게 아니라, 선택한 Route Table에 S3로 가는 경로가 추가되면서 S3 트래픽이 Endpoint로 빠지도록 바뀐다.
그래서 pri-01/pri-02에 연결된 서브넷(Private Subnet) 트래픽만 이 Endpoint를 탄다.
Bucket 접근 제한 확인
web server
[ec2-user@ip-10-0-40-82 ~]$ ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
[ec2-user@ip-10-0-40-82 ~]$ BUCKET_NAME="lab-edu-bucket-image-$ACCOUNT_ID"
[ec2-user@ip-10-0-40-82 ~]$ aws s3 ls s3://$BUCKET_NAME
2026-01-15 08:20:47 47921 cocker_spaniel.jpg
2026-01-15 08:20:47 64638 corgi.jpg
2026-01-15 08:20:47 74181 german_shepherd.jpg
2026-01-15 08:20:47 88081 golden_retriever.jpg
2026-01-15 08:20:47 41784 husky.jpg
2026-01-15 08:20:47 33828 jack_russell_terrier.jpg
2026-01-15 08:20:47 44155 jack_terrier.jpg
2026-01-15 08:20:47 65919 pug.jpg
2026-01-15 08:20:47 52571 shiba_inu.jpg
[ec2-user@ip-10-0-40-82 ~]$
외부에서 접근한게 아닌 내부에 있는 vpc 게이트웨이 엔드포인트를 통해서 접근하였다.
버킷 정책이 aws:sourceVpce=vpce-... 조건을 강제하므로 NAT/인터넷 경로로 접근했다면 조건 불일치로 AccessDenied가 떠야 한다. 그런데 성공했으므로 Endpoint 경로로 들어온 요청임을 확인할 수 있다
SSM Session Manager용 VPC Endpoint 생성 (Interface Type)
Web Server에 System Manager 접근 권한 할당
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ aws iam attach-role-policy --role-name let-edu-role-ec2 --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ aws iam list-attached-role-policies --role-name let-edu-role-ec2
{
"AttachedPolicies": [
{
"PolicyName": "AmazonSSMManagedInstanceCore",
"PolicyArn": "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
},
{
"PolicyName": "AmazonEC2FullAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
},
{
"PolicyName": "IAMFullAccess",
"PolicyArn": "arn:aws:iam::aws:policy/IAMFullAccess"
},
{
"PolicyName": "CloudWatchFullAccess",
"PolicyArn": "arn:aws:iam::aws:policy/CloudWatchFullAccess"
}
]
}
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$ WEB_SERVER_NAME=lab-edu-ec2-web
aws ec2 reboot-instances --instance-ids $(aws ec2 describe-instances --filters "Name=tag:Name,Values=$WEB_SERVER_NAME" --query "Reservations[].Instances[].InstanceId" --output text)
ubuntu@ip-10-0-1-211:/Workshop/support_files/policy$
Session Manager 테스트용 서버 생성
ubuntu@ip-10-0-1-211:/Workshop/scripts$ ssh network-server
A newer release of "Amazon Linux" is available.
Version 2023.10.20260105:
Version 2023.9.20251208:
Run "/usr/bin/dnf check-release-update" for full release and version update info
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
[ec2-user@ip-10-0-41-101 ~]$
SSM 이용 Network Server 접속 테스트
Web Server가 배치된 Private Subnet 01은 Nat Gateway Route 정보가 반영되어 있기 때문에 Session Manager로 접속이 가능하고,Network Server가 배치된 Private Subnet 02은 Nat Gateway Route 정보가 없기 때문에 외부에서 Session Manager로 접속이 불가능한 상태다. 각 서버에 Session Manager 접속 테스트를 통해 접속 가능 여부를 확인한다.
Web Server SSM 접속 테스트
인스턴스 리소스 탭 → lab-edu-ec2-web 선택 → 연결 버튼 클릭Session Manager 탭으로 이동 → 연결 버튼 클릭

연결되는것을 볼 수 있다.
여기서 접속이 되는 이유는 SSM 권한 + (현 상태에선) NAT 라우트가 있어서다.
아직 Endpoint를 만들기 전이므로 Web Server는 사실상 NAT를 통해 외부로 나가 SSM 서비스에 도달할 수 있는 상태다
Network Server SSM 접속 테스트
인스턴스 리소스 탭 → lab-edu-ec2-network-ap-02 선택 → '연결' 버튼 클릭Session Manager 탭으로 이동 → 연결 버튼 비활성화 상태 확인
SM VPC Endpoint용 Security Group 생성
보안 그룹 리소스 탭 → 보안 그룹 생성 버튼 클릭보안 그룹 생성 버튼 클릭
VPC Endpoint 생성
엔드포인트 리소스 탭 → 엔드포인트 생성 버튼 클릭
엔드포인트 생성 정보 입력 (ssmessages)
엔드포인트 생성 정보 입력 (ec2messages)
이제 NAT 라우트가 없는 Private Subnet 02의 Network Server도 Session Manager 연결 버튼이 활성화되고 접속이 되어야 한다. 접속이 된다면 SSM 통신이 NAT가 아니라 Interface Endpoint(사설 ENI) 를 통해 이루어졌음을 의미한다.


Session Manager는 콘솔에서 연결 버튼만 누르면 끝처럼 보이지만 인스턴스 안의 SSM Agent가 AWS 쪽과 여러 종류의 통신을 동시에 해야 된다.
그 통신이 기능별로 서로 다른 서비스 엔드포인트로 나뉘어 있어서 보통 3개(SSM / SSMMessages / EC2Messages) 를 만들어야한다. 이번 실습에서는 3개가 필요하다는것만 알고 안에 세세한 내용은 넘어갔다. 추후에 다시 공부 할 예정이다.
마무리
Subnet 01(=NAT 라우트 있음)에서는 접속이 되고 Subnet 02(=NAT 라우트 없음)에서는 막히는 걸 직접 비교하니까 원인을 쉽게 알 수 있었고 이해가 쉽게 되었다. 현재는 실습을 통해 흐름을 이해하는것을 위주로 하였지만 추후에는 각각이 정확히 어떤 메커니즘인지 더욱 자세히 공부할 것이다.