콘텐츠로 이동

ADR-0034: Bouncer IP Intelligence Provider

Bouncer 도메인의 IP 인텔리전스는 MaxMind GeoLite2 (국가 판정) + ipquery.io (VPN/Proxy/Tor 플래그) + Tor Project 공식 exit-node 목록 (Tor 확정) 의 무료 조합으로 시작한다. 세 소스는 단일 outbound port IPIntelligence 뒤로 통합되며, 향후 유료 MaxMind GeoIP2 Anonymous IP 로 adapter 만 교체 가능하다.

Status

Proposed

  • Decided at — 2026-04-18
  • Decided by — Pablo

Context

ADR-0033 에서 신설한 engine/bouncer/ 는 IP 주소로부터 다음 두 종류의 정보가 필요하다.

  • 국가 코드(ISO 3166-1 alpha-2) — 국가 블랙리스트 판정
  • 익명화 플래그 — VPN / Public Proxy / Tor exit-node 여부

업계 표준인 MaxMind 제품군은 두 DB 로 나뉘어 있다.

제품 가격 국가/도시 VPN/Proxy/Tor
GeoLite2 무료 (계정 등록만) ✅ Country, City, ASN 없음
GeoIP2 Anonymous IP 유료 ($100+/mo 또는 CSV 라이선스) ✅ VPN, Public Proxy, Tor, Hosting, Residential Proxy

즉, GeoLite2 무료만으로는 VPN/Proxy/Tor 감지가 불가능 하다. 이 간극을 메울 무료 옵션은 제한적이다.

  • ipquery.io — 무료 REST API. is_vpn, is_proxy, is_tor 플래그 제공. 정확도는 MaxMind 대비 낮지만 충분히 실용적.
  • Tor Project 공식 exit-node listhttps://check.torproject.org/torbulkexitlist 또는 https://www.dan.me.uk/torlist/?exit. Tor 는 100% 판정 가능한 유일한 카테고리.
  • ASN 블랙리스트 (자체 유지) — AWS / DigitalOcean / OVH / Hetzner 등 Hosting provider ASN. 대부분의 VPN 이 여기서 나오지만 오탐 위험(정당한 회사 네트워크도 클라우드일 수 있음).

공급자를 하나만 고르는 대신 역할별로 최적 소스를 조합 하는 것이 무료 범위에서 최고 정확도를 얻는 방법이다.

초기 비용 감내 한계는 "무료 범위 안" 이다. 운영 지표(오탐률, 미감지율) 를 관찰 후 유료 전환 여부를 재평가한다.

Decision

다음 세 소스의 무료 조합 을 MVP 의 IP 인텔리전스로 채택한다.

Source 1 — MaxMind GeoLite2 Country

  • 역할 — 국가 판정
  • 배포 — 임베디드 binary DB (.mmdb). apps/apiapps/worker 컨테이너 이미지에 포함
  • 갱신 — asynq cron 으로 주 1회 자동 다운로드 (MaxMind 라이선스 키 필요)
  • 지연 — 0 (로컬 lookup)
  • 라이선스 — GeoLite2 EULA, 계정 등록 후 무료. Attribution 요구 (docs/ 에 반영)

Source 2 — ipquery.io

  • 역할 — VPN / Public Proxy / Tor 플래그 획득
  • 배포 — REST API 호출. 공식 client 없어 platform/httpclient 기반 자체 어댑터
  • 갱신 — 실시간 lookup (API 호출)
  • 지연 — 200~500ms (외부 API)
  • fallback — API 장애 시 fail-open (정책 평가를 통과시키되 decisions.provider_error=true 로 기록)
  • rate limit — 공개 문서상 무료 한도 존재. Redis 캐시 (IP 당 24h) 로 호출량 억제

Source 3 — Tor Project exit-node list

  • 역할 — Tor 확정 판정 (ipquery.io 누락분 보강)
  • 배포 — 공식 목록을 시간당 fetch → Redis Set 으로 저장
  • 갱신 — asynq 시간별 cron
  • 지연 — 0 (Redis 조회)
  • 근거 — Tor 는 사실상 "공식 목록이 곧 진실" 이라 자체 판정이 외부 API 보다 신뢰성 높음

통합 port

세 소스는 하나의 outbound port 뒤에 숨긴다.

type IPIntelligence interface {
    Lookup(ctx context.Context, ip netip.Addr) (*IPProfile, error)
}

type IPProfile struct {
    CountryCode string  // ISO 3166-1 alpha-2
    IsVPN       bool
    IsProxy     bool
    IsTor       bool
    Source      string  // 'geolite2+ipquery+tor-list'
    LookupAt    time.Time
}

Adapter 구현 (engine/bouncer/adapter/ipintel/) 은 세 소스를 내부에서 조합하여 단일 IPProfile 을 반환. Bouncer 도메인 코어는 세 소스의 존재를 모름.

Caching

  • Key — bouncer:ip:{ip}
  • TTL — 24h
  • 목적 — ipquery.io 호출 억제, Tor list 조회 스킵, 판정 일관성 (같은 방문자가 분 단위로 재시도 시 동일 결과)

Fail-open vs fail-close

공급자(특히 ipquery.io) 장애 시 정책:

  • MVP — fail-open — 판정을 allow 로 통과시키되 decisions.provider_status='degraded' 기록. 이유: 정상 사용자가 공급자 장애로 가입 불가해지는 UX 가 오남용 차단보다 더 큰 리스크
  • 옵션 — 길드별 strict_mode 토글로 fail-close (공급자 장애 시 deny). Phase 2 고려

Consequences

Positive

  • 무료 범위에서 최대 정확도 — Tor 는 공식 목록으로 확정, VPN/Proxy 는 ipquery, 국가는 MaxMind 임베디드
  • 국가 판정 무지연 — 임베디드 mmdb 는 RTT 0. 대다수 판정이 네트워크 없이 완결
  • Provider 교체 용이 — port 경계 하에 adapter 교체만으로 유료 GeoIP2 Anonymous IP 로 전환 가능
  • 비용 0 (MVP) — MaxMind 무료, ipquery 무료, Tor list 무료
  • 감사 용이decisions.source 필드로 어떤 소스 조합이 판정에 쓰였는지 추적

Negative

  • 정확도 한계 — ipquery 는 MaxMind 유료 대비 오탐·미감지 존재 (구체 수치는 운영 후 관찰)
  • 외부 API 의존 — ipquery.io 장애 영향. fail-open 으로 완화하지만 "장애 중엔 VPN 이 뚫린다" 는 리스크
  • 세 소스 동기화 부담 — MaxMind mmdb 주 1회 갱신, Tor list 시간별 갱신, ipquery 실시간. 각각의 cron 과 실패 복구 로직 필요
  • 라이선스 제약 — MaxMind GeoLite2 는 Attribution 의무가 있다

Neutral

  • ipquery.io 무료 한도 초과 시 유료 전환 필요 — 월간 호출량 모니터링
  • Residential proxy 감지는 모든 무료 소스에서 불가능 (유료 GeoIP2 Anonymous IP 만 커버). MVP 수용 범위 밖

Alternatives considered

Alternative 1: MaxMind GeoIP2 Anonymous IP (유료 전면 채택)

Pros

  • 업계 최고 정확도 (VPN, Public Proxy, Tor, Hosting, Residential Proxy 전 카테고리)
  • 단일 공급자로 통합 간단
  • SLA 명시된 안정성

Cons

  • $100+/mo 고정 비용 — pre-MVP 단계에 과도
  • 무료로 충분히 가능한 범위(국가 판정·Tor 확정) 까지 유료로 커버
  • Residential Proxy 는 공개 웹 조인 가입자 중 드물어 ROI 낮음

Why rejected — 무료 조합으로 MVP 정확도 요구를 충족 가능. 오탐률이 실제 운영에서 문제되면 Revisit trigger 발동.

Alternative 2: ipquery.io 단일 공급자

Pros

  • 가장 단순 — 국가와 VPN/Proxy/Tor 모두 하나의 API
  • 어댑터 하나만 유지

Cons

  • 국가 판정마다 외부 API 호출 — 지연 누적
  • Rate limit 빠르게 소진 (국가 판정은 모든 방문자에 대해 필요)
  • 공급자 장애 시 모든 판정 기능 중단

Why rejected — 국가 판정은 무료 mmdb 로 완결 가능한데 굳이 외부 의존을 만들 이유 없음. 지연·rate limit 모두 이득이 없다.

Alternative 3: 자체 VPN/Proxy 판정 구현

Pros

  • 외부 의존 0
  • 커스터마이즈 자유

Cons

  • Hosting ASN 리스트, Tor list, VPN 서비스 IP range 를 직접 수집·갱신 필요
  • 데이터 품질은 필연적으로 전문 업체에 뒤짐
  • 유지보수 비용 높음 (Umbra 의 차별화 영역도 아님)

Why rejected — 핵심 도메인이 아닌 영역의 자체 구현은 ROI 없음. 품질 좋은 무료 소스를 조합하는 것이 합리적.

Alternative 4: Cloudflare Workers 에서 CF-IPCountry 헤더만 활용

Pros

  • CF 가 이미 앞에 있으므로 zero-effort 국가 판정
  • 비용 0

Cons

  • VPN/Proxy/Tor 플래그 없음 (CF 유료 Bot Management 플랜 필요)
  • per-guild 정책 평가 로직이 Worker 에 들어가면 코드 경계 흐려짐
  • 차단 이력 저장·감사 어려움

Why rejected — 국가만 커버하고 VPN 은 못 막는다. 도메인 경계를 CF Worker 로 흘리는 건 ADR-0017 프로세스 분리 원칙에도 역행.

Compliance

  • IP 인텔리전스 호출은 반드시 engine/bouncer/adapter/ipintel/ 를 경유
  • 다른 도메인·프로세스에서 maxmind, ipquery, tor-list 라이브러리를 직접 import 금지 (grep 로 CI 검증)
  • MaxMind GeoLite2 Attribution 은 apps/web Footer 또는 docs/ 에 명시
  • API 키(MAXMIND_LICENSE_KEY, IPQUERY_API_KEY 가 유료 전환 시) 는 Fly secrets 에만 저장
  • Redis 캐시 key 는 prefix bouncer:ip: 로 네임스페이스 격리

Revisit triggers

  • 운영 중 VPN/Proxy 오탐 또는 미감지 빈도가 decisions 분석에서 유의미하게 높게 관찰되면 MaxMind GeoIP2 Anonymous IP 유료 전환 재평가
  • ipquery.io 무료 한도 초과 또는 SLA 문제 발생 시 대체 공급자(IPinfo, proxycheck.io) 검토
  • Residential proxy 를 이용한 조직적 Raid 가 반복되면 유료 공급자 필요성 증대
  • 국가 판정 외에 "도시 단위" 정책 요구가 생기면 GeoLite2 → GeoLite2 City 또는 GeoIP2 City 로 확장

References