S3 과금 사고 — 버킷 이름만으로 파산하는 법
이 토픽을 마치면
S3의 요청 기반 과금 구조가 왜 위험할 수 있는지, 그리고 어떻게 방지하는지 이해하게 됩니다.
S3는 저렴하다고 들었는데
AWS S3는 파일 저장 서비스입니다. 저장 비용이 GB당 월 $0.023 정도라서 "거의 공짜"라는 인식이 있습니다. 실제로 1TB를 저장해도 월 $23입니다.
그런데 S3에는 저장 비용 말고 요청 비용이 있습니다. 파일을 읽을 때(GET), 목록을 조회할 때(LIST), 파일을 올릴 때(PUT) — 요청 하나당 비용이 청구됩니다.
GET 요청 1만 건당 $0.004. 별것 아닌 것 같지만, 1억 건이면 $40,000입니다.
실제 사고: 버킷 이름이 공개됐습니다
2024년에 실제로 일어난 사례입니다. 개발자가 S3 버킷을 만들고 이름을 소스 코드에 하드코딩했습니다. 이 코드가 공개 GitHub 저장소에 올라갔습니다.
누군가가 이 버킷 이름을 발견하고, 봇을 돌려서 존재하지 않는 파일에 대한 GET 요청을 수억 건 보냈습니다.
GET https://my-bucket.s3.amazonaws.com/random-file-001
GET https://my-bucket.s3.amazonaws.com/random-file-002
... (수억 번 반복)파일이 없어도 S3는 "404 Not Found"를 응답합니다. 이 404 응답도 요청 1건으로 과금됩니다. 파일이 없으니 저장 비용은 0이지만, 요청 비용으로 하룻밤에 수천 달러가 청구됐습니다.
왜 이런 구조인가
S3는 요청을 처리하는 데 서버 자원을 씁니다. 파일이 있든 없든 요청을 받고, 권한을 확인하고, 응답을 보내는 과정에 컴퓨팅 비용이 발생합니다. 그래서 404도 과금하는 것입니다.
문제는 누구나 버킷 이름만 알면 요청을 보낼 수 있다는 것입니다. S3 버킷 이름은 전 세계적으로 고유하지만, URL 패턴이 예측 가능합니다:
https://{버킷이름}.s3.{리전}.amazonaws.com/버킷 이름이 my-company-assets 같은 추측 가능한 이름이면, 봇이 자동으로 찾아낼 수 있습니다. 이름을 알면 요청을 보낼 수 있고, 요청 비용은 버킷 소유자에게 청구됩니다.
방지하는 방법
버킷 이름을 예측 불가능하게 — my-app-prod 대신 a7f3k9x2-prod-assets 같은 무작위 문자열을 포함합니다. 추측이 어려워집니다.
버킷 이름을 코드에 하드코딩하지 않기 — 환경 변수나 AWS Parameter Store에 넣고 참조합니다. 소스 코드에 버킷 이름이 남으면 GitHub에 올라갈 위험이 있습니다.
CloudFront를 앞에 두기 — S3에 직접 접근하지 않고 CloudFront(CDN)를 통해서만 접근하게 설정합니다. S3 버킷은 CloudFront에서 오는 요청만 허용하고 나머지는 거부합니다.
요청 과금 알림 설정 — AWS Budget에서 일별 비용 임계값을 설정합니다. 비정상적인 요청이 발생하면 즉시 알림을 받을 수 있습니다.
Requester Pays 버킷 — 요청 비용을 요청한 사람에게 청구하는 옵션입니다. 다만 인증되지 않은 요청은 이 옵션이 적용되지 않아서 만능은 아닙니다.
더 넓은 교훈: 클라우드 비용은 무한합니다
S3 사고는 클라우드 비용의 근본적인 특성을 보여줍니다. 온프레미스(자체 서버)는 서버를 10대 사면 최대 비용이 정해져 있습니다. 하지만 클라우드는 사용량에 비례해서 무한히 청구됩니다.
Lambda, API Gateway, DynamoDB 모두 요청 기반 과금입니다. 비정상 트래픽이 들어오면 자동 확장이 되면서 비용도 같이 확장됩니다. 이걸 Billing DDoS 또는 Economic Denial of Service라고 부릅니다.
클라우드를 쓸 때는 "서비스가 잘 동작하는지"만 모니터링하면 안 됩니다. **"비용이 정상 범위인지"**도 반드시 모니터링해야 합니다.
핵심
S3는 저장 비용 외에 요청 비용이 있으며, 파일이 없는 404 응답도 과금됩니다. 버킷 이름이 노출되면 누구나 요청을 보낼 수 있고, 비용은 소유자에게 청구됩니다. 버킷 이름 비공개 + CloudFront 전면 배치 + 비용 알림 설정이 핵심 방어책입니다.