728x90

주말에 HN 올라온 글 하나 읽고 한참 멍하니 있었다. 2018년에 "장난삼아" 산 sondehub.org 도메인 하나가, 결국 미 국방부(Office of the Secretary of War)와 NTSB, FAA 관제탑, 그리고 우크라이나 심층타격팀까지 엮이는 인프라가 되어버린 이야기다.

기술적으로 대단한 스택이 나오는 글은 아니다. 그런데 "내가 만든 공개 API를 내가 통제할 수 없는 사람들이 쓰기 시작했을 때 무슨 일이 벌어지는가"에 대해서는, 내가 본 어떤 아키텍처 문서보다 생생하다. 5년차 인프라 하다 보면 사이드 프로젝트 하나쯤은 공개 엔드포인트로 열어두게 되는데, 그 엔드포인트가 어디까지 갈 수 있는지 감각을 잡아두는 게 좋다.

장난 도메인이 어쩌다 전략 자산이 됐나

흐름을 요약하면 이렇다.

  • 2018.05 — Habhub(아마추어 고고도 풍선 추적 사이트)가 기상 관측용 라디오존데를 기본 필터로 감췄다. 필터 해제 쿼리 파라미터를 매번 치기 귀찮아서 sondehub.org를 사면서 Habhub으로 리다이렉트만 걸었다. 진짜 그게 전부였다.
  • 2018.07 — 리다이렉트만으로는 안 되겠어서 데이터 수집을 프록시로 받기 시작. 자체 OpenSearch 클러스터에 쌓았지만 노출은 안 했다. 저자 본인 표현으로 "AWS 서비스 놀아보는 장난감" 수준.
  • 2019 — Habhub과 aprs.fi 서버가 버티질 못함. 자체 서비스로 전환 시작. 이때부터 정부기관 데이터 요청이 들어온다. (라디오존데가 말에 맞아서 말이 울타리 뚫고 도망간 보험 청구 건... 이런 게 온다)
  • 2021 — 역예측(reverse prediction) 기능 도입. 그리고 군에서 이메일이 온다.
  • 2023.02 — 중국 정찰풍선 사태 + 미군이 아마추어 무선 풍선을 AIM-9X로 격추. Washington Post에 링크되면서 트래픽 폭증.
  • 2024.12 — 예측 API에 알람 폭탄. 추적해보니 우크라이나 쪽 심층타격 작전이었다.

여기서 결정적 분기점이 역예측이다. 원래 예측기는 "지금 이 풍선이 어디로 갈까"를 바람 모델로 계산한다. 이걸 시간축을 뒤집어서 돌리면, 이미 관측된 풍선의 발사 지점이 역산된다. 문서화 안 된 발사장을 여럿 찾아냈고 — 문제는, 상층 바람 데이터는 기상 예보용만이 아니라 포병 사거리 계산에도 쓰인다는 것.

즉 이들이 무심코 만든 건 "미문서화 기상 관측소 지도"가 아니라 포병 진지 지도였다. 2021년에 군에서 "민감/군사 시설이니 지도에 명시적으로 표시하지 말아달라"는 메일을 받는다. 역예측 기능은 유지하되, 정당한 요청이 오면 해당 발사장을 삭제하는 방식으로 타협했다고 한다.

핵심 원리: 파생 데이터는 원본보다 위험하다

이 사례의 기술적 교훈은 딱 하나로 압축된다. 공개 데이터를 모아서 가공하면, 개별 데이터에는 없던 민감도가 생긴다. 인프라 쪽 용어로는 aggregation / inference 문제다.

비유하자면 이렇다. 사내 액세스 로그 한 줄은 아무 의미 없다. 근데 6개월치를 모아서 "이 IP대역이 새벽 3시에만 특정 엔드포인트를 친다"는 패턴을 뽑으면, 그건 배치 스케줄이자 곧 인프라 구조도다. 라디오존데도 똑같다. 개별 좌표 하나는 공개 기상 데이터인데, 수만 건을 역방향 물리 모델에 넣으면 발사 원점이 나온다.

SondeHub이 왜 기존 도구보다 데이터가 좋았냐면 — 지면에 닿을 때까지 끝까지 추적했기 때문이다. 당시 공식 소프트웨어는 그렇지 않았다. 그래서 보험 청구 조사까지 가능했던 거고, 그래서 역예측 정밀도도 나온 거다. 관측 커버리지를 늘렸더니 부작용으로 정보 가치가 폭발한 케이스.

본인 인프라에서 이걸 확인하고 싶다면, 로그에서 "단일 소스가 예측 API만 반복 호출"하는 패턴을 뽑아보는 게 첫 단계다. CloudFront/ALB 액세스 로그 기준 예시:

# ALB 액세스 로그에서 예측 엔드포인트 상위 호출자 뽑기
awk '$13 ~ /predict/ {split($4,a,":"); print a[1]}' alb.log \
  | sort | uniq -c | sort -rn | head -5

  48213 13.54.x.x
    902 203.0.113.9
    311 198.51.100.44
     87 192.0.2.17
     52 172.16.8.3

1등과 2등이 50배 차이 나면 그건 사람이 아니다. 원문에서도 로깅을 켜자마자 단일 IP에서 오고 있었다고 나온다. 처음엔 어떤 사설 업체가 백엔드를 무단으로 쓰는 줄 알고 항의 메일까지 보냈는데, 그쪽은 진범이 아니었다. LLM 스크래퍼 봇이겠거니 하고 예측 요청 좌표를 지도에 찍어봤더니 — 그림이 나왔다.

실무 관점: 공개 API 운영자가 실제로 마주치는 것들

1. 차단이 정답이 아닌 경우가 있다

보통 이런 트래픽 보면 WAF 룰 하나 박고 끝낸다. 근데 이 케이스에서 운영자가 AWS 서포트에 보낸 문장이 이거다.

"HTTP 요청 데이터가 배포되지 않는 것이 대단히 중요합니다. 또한 소스 AWS 계정이 차단·레이트리밋·해지되지 않는 것도 중요합니다 — 인명 손실이 발생할 수 있습니다."

서포트 티켓에 이런 문장을 쓸 일이 있으리라곤 본인도 상상 못 했다고 한다. 요구사항이 두 개인데 서로 충돌한다. (a) 서비스를 끊으면 안 된다 (b) 요청 로그가 새어나가면 발사 지점이 노출된다. 결국 AWS를 통해 "당신 람다가 스크래핑으로 플래그됐으니 운영자에게 연락하라"는 형태로 채널이 열렸고, 로컬 예측기 실행 문서를 넘겨주는 걸로 정리됐다.

2. 진짜 해법은 "떠나게 만드는 것"

여기서 제일 실무적인 판단이 나온다. 저자는 차단 대신 docker compose 파일을 급하게 만들어서 누구나 자기 예측기를 돌릴 수 있게 풀었다. 헤비 유저를 내쫓는 게 아니라 셀프호스팅 경로를 열어준 것.

이건 사내 공용 API 운영할 때도 그대로 적용된다. 특정 팀이 공용 계산 API를 초당 수백 번 때리면, 429 때리기 전에 "그 계산 라이브러리를 너희 파드 안에서 돌려라"를 먼저 제안하는 게 총 비용이 싸다. 사이드카/로컬 배포 경로가 있으면 대부분 알아서 옮겨간다.

# 헤비 유저에게 넘길 최소 셀프호스팅 스택 예시
cat > compose.yml <<'EOF'
services:
  predictor:
    image: ghcr.io/example/predictor:latest
    ports: ["8080:8080"]
    environment:
      - GFS_CACHE_DIR=/data/gfs
    volumes: ["./gfs:/data/gfs"]
EOF

docker compose up -d
curl -s localhost:8080/healthz

{"status":"ok","wind_model":"loaded"}

※ 위 이미지 경로는 예시다. SondeHub 실제 이미지 태그는 공식 저장소 확인 필요.

3. 흔한 함정: 레이트리밋 걸고 나서 벌어지는 일

API Gateway나 CloudFront 앞단에 usage plan 걸면 이런 게 뜬다.

HTTP/1.1 429 Too Many Requests
x-amzn-ErrorType: LimitExceededException
{"message":"Too Many Requests"}

문제는 이걸 받는 쪽 클라이언트가 대개 백오프를 구현 안 했다는 것이다. 429를 받으면 재시도를 더 빨리 돌려서 부하가 오히려 튄다. 원문에서도 "API를 올바르게 사용하지 않는 것 같다"는 의심이 나온다. 그리고 이런 상황에서 진짜 답답한 건, 연락할 방법이 없다는 점이다. User-Agent도 없고 API 키도 없으면 그냥 IP 하나뿐이다.

결국 이들은 우크라이나어로 밀리터리 채팅방에 메시지를 뿌려서 컨택을 잡았다. "파이썬 스크립트로 오픈소스 바람 예측 엔진 쓰는 심층타격팀 아시는 분 연락 주세요, 차단될 수 있으니 조치가 필요합니다" — 이게 실제 컨택 경로였다.

여기서 얻어갈 실무 룰:

  • 익명 접근을 허용하더라도 키 발급은 무료로라도 열어둬라. 차단이 목적이 아니라 연락 채널 확보가 목적이다.
  • 429 본문에 사람이 읽을 수 있는 컨택 정보와 셀프호스팅 링크를 반드시 넣어라. 봇 운영자도 결국 사람이 로그를 본다.
  • 레이트리밋은 계정 단위로, 익명은 IP 단위로. NAT 뒤에 있으면 IP 단위는 애먼 사람 잡는다.

4. 로그 자체가 기밀이 되는 순간

평소엔 액세스 로그를 S3에 던져놓고 라이프사이클 정책만 걸어둔다. 근데 이 케이스에선 요청 파라미터(위경도)가 곧 작전 정보였다. 클라우드 벤더 서포트에게 로그를 넘기는 순간 그게 유출이 된다.

지도에 찍을 때도 좌표 정밀도를 의도적으로 낮췄고, 데이터셋은 상당히 오래된 것만, 블로그 발행 자체를 "풍선 전쟁이 충분히 널리 알려질 때까지" 미뤘다고 한다. 이 판단 자체가 운영 프로세스의 일부다.

대응 원칙 정하고 가자:

  • 쿼리 파라미터를 액세스 로그에 통째로 남길지 여부를 설계 시점에 결정. 좌표·검색어·식별자는 기본적으로 마스킹 후보다.
  • 서드파티(벤더 서포트, APM SaaS)로 나가는 로그 필드를 화이트리스트로 관리. 블랙리스트는 언젠가 샌다.
  • 보존 기간 짧게. 없는 데이터는 유출도 안 되고 소환장에도 안 걸린다.

5. 예상 못 한 이해관계자들

운영하다 보면 상상 못 한 곳에서 메일이 온다. 원문에 나온 것만 봐도:

  • NTSB(2025.09) — "10/16/2025 1200~1300 UTC에 유타 지역 풍선 정보 있나요?" 데이터를 넘겼고, SondeHub 추적 풍선과는 안 맞았지만 Windborne 풍선이 근처에 있었다는 정보를 전달. 나중에 Windborne이 항공기 충돌 가능성을 확인하고 시스템을 수정했다.
  • FAA 관제탑 — "하늘에 항공기가 많은데 이 풍선들 가까이 가고 싶지 않다. 통제 주체와 직접 조율하거나 사전에 임무 정보를 받을 수 있나?" → 기상 풍선은 정기 스케줄로 올라가고 조종되지 않으며 FAA 규정 Part 101.D 범위라고 설명해야 했다. 저자 표현: "FAA에 기상 풍선의 존재를 설명하게 될 줄은 몰랐다."
  • 뺑소니 민원 — 아이오와 아나모사에서 누가 라디오존데 회수하다가 건물 들이받고 쪽지도 안 남기고 도망감. 건물주가 범인 찾아달라고 연락.
  • 미 국방부(2025) — 데이터 요청. 커뮤니티 상호 이익이 있으면 보통 무료로 가공해서 푸는데, 이건 그게 아니라 인보이스를 끊었다. 결과: 결제도 후속 연락도 없었다.

이게 왜 실무 얘기냐면, 공개 서비스를 운영한다는 건 지원 채널을 운영한다는 뜻이기 때문이다. 스펙에도 없고 SLA도 없는 요청이 계속 들어온다. 답변 템플릿과 에스컬레이션 기준, "이건 우리 소관 아님"이라고 말할 수 있는 문서화된 경계선이 없으면 운영자 한 명이 갈려나간다.

정리

한 줄 요약: 공개 API는 어느 순간 당신이 승인하지 않은 용도로 쓰이며, 그때는 차단보다 셀프호스팅 경로 제공과 연락 채널 확보가 더 나은 해법일 수 있다.

체크리스트로 남긴다.

  • 공개 엔드포인트를 열 거면 1일차에 로깅·식별·컨택 경로를 같이 설계해라. 사고 터진 뒤 켜는 로깅은 이미 늦다.
  • 원본 데이터가 공개라고 파생 데이터도 공개해도 되는 건 아니다. 집계·역산으로 새 정보가 생기는지 한 번은 따져봐라.
  • 헤비 유저는 적이 아니라 배포 형태를 잘못 고른 사용자다. 도커 컴포즈 하나가 WAF 룰 열 개보다 낫다.
  • 액세스 로그 필드는 유출 자산으로 취급하고, 서드파티로 나가는 항목은 화이트리스트로.
  • 취미 프로젝트라도 인보이스 끊을 각오는 해두되, 정부기관이 그걸 낼 거라는 기대는 접어라.

솔직히 나는 이 글에서 제일 인상 깊었던 게 아키텍처가 아니라 "AWS 서포트에게 이 계정을 차단하지 말아달라, 인명 손실이 발생할 수 있다"는 문장이었다. 우리가 짜는 레이트리밋 룰 한 줄이 어디까지 가닿을 수 있는지, 가끔은 생각해볼 만하다.

참고 자료

728x90

+ Recent posts