Dev 네트워크와 접근 제어 - 100-hours-a-week/KTB4-2nd-Cloud GitHub Wiki

네트워크와 접근 제어

V1은 서울 리전의 VPC 하나, Public Subnet 하나에 App과 Worker를 함께 둡니다. NAT Gateway 없이 두 서버 모두 Public Subnet에 있는 대신, 인터넷에서 들어오는 경로는 Security Group으로 App의 80, 443만 남겼습니다.

사람의 접근과 서버의 AWS 접근도 장기 Access Key 없이 구성했습니다.


네트워크

flowchart LR
  INET["인터넷"] <--> IGW["Internet Gateway"]
 
  subgraph VPC["VPC 10.20.0.0/16"]
    subgraph SUB["Public Subnet 10.20.1.0/24 (ap-northeast-2a)"]
      APP["App EC2<br/>Elastic IP<br/>Inbound 80, 443"]
      WK["Worker EC2<br/>실행 중 Public IPv4<br/>Inbound 8000 (App SG만)"]
    end
    RT["Public Route Table"]
    EP["S3 Gateway Endpoint"]
  end
 
  S3[("S3")]
 
  APP -- "8000" --> WK
  SUB --- RT
  RT -- "0.0.0.0/0" --> IGW
  RT -- "S3 Prefix List" --> EP --> S3
Loading
라우팅 대상
10.20.0.0/16 local
0.0.0.0/0 Internet Gateway
S3 Prefix List S3 Gateway Endpoint

CIDR은 예제에서 흔히 쓰는 10.0.0.0/16 대신 10.20.0.0/16을 썼습니다. 나중에 다른 클라우드나 다른 프로젝트 망과 연결하게 돼도 주소가 겹칠 가능성이 낮습니다.

VPC는 /16으로 잡았지만 실제로 만든 Subnet은 /24 하나입니다. Private Subnet이 필요해지면 남은 범위에 추가할 수 있게 여유만 두고, 미리 만들지는 않았습니다.

Main Route Table을 고치지 않고 Public 전용 Route Table을 따로 만들어 Subnet에 연결했습니다. Private Subnet이 생기면 라우팅을 섞지 않고 나눌 수 있습니다.

사진 원본이 오가는 S3 경로에는 Gateway Endpoint를 붙였습니다. S3 트래픽은 Endpoint로, GHCR과 외부 API 호출은 Internet Gateway로 나갑니다.


Security Group

대상 방향 포트 상대 용도
App Inbound 80 0.0.0.0/0 HTTP, 인증서 발급
App Inbound 443 0.0.0.0/0 HTTPS
Worker Inbound 8000 App Security Group Backend의 AI 호출
App, Worker Outbound 전체 0.0.0.0/0 S3, SSM, GHCR, 외부 API

Worker 8000번은 App의 IP가 아니라 App Security Group을 출처로 지정했습니다. App의 사설 IP가 바뀌어도 규칙을 고칠 필요가 없습니다.

22번은 어느 서버에도 열지 않았습니다. Frontend, Backend, MySQL 포트도 Security Group에 열지 않고 서버 안에서만 연결합니다.

적용 뒤 AWS API로 규칙을 조회해서 App에는 80, 443만, Worker에는 App SG에서 오는 8000만 있는 것을 확인했습니다. 외부에서 Worker의 8000번으로 직접 접근하면 실패하는 것도 확인했습니다.


사람의 접근

AWS 작업은 루트 계정 대신 IAM Identity Center의 관리자 사용자로 합니다.

IAM Identity Center
└─ CloudAdmins 그룹
   └─ leo-admin (MFA)
      └─ AdministratorAccess Permission Set
         └─ 로컬 CLI 프로필 yeodam-admin

권한은 사용자에게 직접 붙이지 않고 그룹에 붙였습니다. 콘솔과 로컬 Terraform이 같은 SSO 세션을 쓰기 때문에 로컬에 Access Key를 발급하지 않았습니다.

서버 접속은 SSM Session Manager만 씁니다. SSH 키 페어는 만들지 않았습니다.


서버와 CI의 IAM Role

권한 App EC2 Role Worker EC2 Role GitHub 배포 Role
SSM 접속 ✅ ✅ -
CloudWatch Agent ✅ - -
CloudWatch Logs 쓰기 /yeodam/v1/backend /yeodam/v1/ai -
S3 trip-uploads/ 읽기, 쓰기, 태그, 삭제 읽기 -
S3 trip-downloads/ 읽기, 쓰기, 태그 - -
Parameter Store app, deploy worker, deploy -
Worker EC2 제어 조회, 시작 - 시작, 중지
SSM 명령 실행 - - 지정한 App, Worker만

같은 버킷을 쓰더라도 App은 사진을 저장하고 지우고, Worker는 처리할 사진을 읽기만 해서 Role과 정책을 나눴습니다. 적용 뒤 IAM Simulation으로 Prefix 밖 접근과 Worker의 쓰기가 거부되는 것을 확인했습니다.

App Role의 Worker 제어 권한은 이렇게 좁혔습니다.

  • ec2:DescribeInstances: 리소스 단위 제한이 안 돼서 서울 리전 조건만 붙임
  • ec2:StartInstances: Worker 인스턴스 ARN 하나로 제한
  • 중지 권한은 주지 않음 (Worker가 유휴 상태에서 스스로 종료)

이 권한을 처음 추가한 뒤 확인 과정에서 한 번 헤맸습니다. 과정은 IAM Policy Simulation 오진에 있습니다.

GitHub 배포 Role은 Cloud 저장소의 production 환경에서 온 요청만 받고, Parameter Store나 S3는 읽을 수 없습니다. 자세한 내용은 배포 파이프라인에 있습니다.


컨테이너에서 Instance Role 사용

EC2 메타데이터는 IMDSv2만 허용하고, hop limit을 2로 두었습니다.

metadata_options {
  http_endpoint               = "enabled"
  http_tokens                 = "required"
  http_put_response_hop_limit = 2
}

Backend와 AI가 컨테이너 안에서 S3와 CloudWatch에 접근할 때 Instance Role을 그대로 쓰게 하려고 hop limit을 2로 맞췄습니다. 토큰 없이 메타데이터를 요청하면 401이 나오고, 토큰을 쓰면 인스턴스 ID가 조회되는 것을 확인했습니다.

⚠️ **GitHub.com Fallback** ⚠️