Files
obsidian/history/2026-04-23-netbis-npm-client-ip-fix.md

6.7 KiB

date, topic, areas, tags
date topic areas tags
2026-04-23 Netbis NPM 로그 파이프라인 — client_ip 실 IP 추출 정비
services/netbis.md
infra/security/crowdsec-safeline.md
netbis
nginx
vector
victorialogs
real-ip
cloudflare

배경

2026-04-23-netbis-npm-vl-collection 의 VL 샘플을 확인한 결과 client IP 필드가 CF edge IP 또는 CF Pseudo IPv4(254.x) 로 찍혀 AI 분석·rate 집계·LAPI ban 후보 선정에 사용 불가. 실 IP 추출로 정비.

샘플 (정비 전):

[Client 254.152.57.112] [Real 172.64.213.21] [XFF 254.152.57.112]
Client = 254.x (CF Pseudo 또는 CF edge) — 익명화/CF 노드

현황 조사

docker exec <npm_container> nginx -T | grep -E "log_format|real_ip_header":

Host log_format (before) real_ip_header (before) 비고
npm-1 [Client $remote_addr] X-Real-IP Rewrite 무효 (CF는 X-Real-IP 안 보냄)
npm-2 [Client $remote_addr] X-Real-IP 동일 — psd777 트래픽 Client=CF edge
npm-3 [Client $remote_addr] X-Real-IP 동일
npm-4 [Client $remote_addr] [Real $realip_remote_addr] [XFF $http_x_forwarded_for] CF-Connecting-IP 이미 정상. 이 포맷을 기준으로 통일
npm-5 [Client $remote_addr] X-Real-IP 동일
npm-6 [Client $remote_addr] X-Real-IP 동일 (lepresidente image)

set_real_ip_from: 6대 전부 Cloudflare + CloudFront + Docker 사설 대역 포함 (총 258 entries). trusted proxy 쪽은 문제 없음. 문제는 real_ip_header 가 X-Real-IP (CF 미매칭) 이라는 점 + log_format 에 Real/XFF 필드가 빠져있다는 점.

수정 — nginx (npm-1/2/3/5/6 5대)

npm-4 기준으로 /etc/nginx/nginx.conf 패치 + nginx -s reload. 패치 로직 (idempotent sed):

# log_format proxy/standard에 Real+XFF 삽입
sed -i 's|\[Client \$remote_addr\] \[Length|\[Client \$remote_addr\] \[Real \$realip_remote_addr\] \[XFF \$http_x_forwarded_for\] \[Length|g'
# real_ip_header 교체
sed -i 's|real_ip_header X-Real-IP;|real_ip_header CF-Connecting-IP;|g'

실행: docker cp out → sed → docker cp in → docker exec nginx -t && nginx -s reload.

주의: NPM 컨테이너의 /etc/nginx/nginx.conf 는 이미지 파일 레이어에 있어 컨테이너 재생성 시 원복됨. 영속화 필요 시 docker-compose volumes: 에 custom nginx.conf 를 host bind mount 로 추가해야 함. 본 작업은 runtime-only 패치 (재부팅/재생성 후 재적용 필요).

재실행 안전 — 패치 스크립트는 [Real $realip_remote_addr] 마커 체크로 중복 실행 방지.

수정 — Vector VRL (6대 전부)

/etc/vector/vector.yamlparse_npm_access transform 재작성. 핵심:

  1. 정규식 4단 fallback: proxy_v2 (Real+XFF 포함) → proxy_v1 (legacy) → standard_v2standard_v1raw
  2. 필드 추출: remote_addr(raw Client), cf_edge_ip(Real = $realip_remote_addr, TCP peer), xff_chain(XFF 원본)
  3. client_ip 유도: nginx real_ip 재작성 결과인 Client 값을 CF IP 대역(15개 CIDR)과 대조. 대역 밖이면 client_ip = remote_addr, 대역 안이면 client_ip = null + client_ip_is_cf_edge=true + client_ip_source="cf_edge_rewrite_failed" (rewrite 실패 케이스 명시)
  4. CF Pseudo IPv4 254.0.0.0/8 은 CF CIDR 리스트에 포함 안 함 — IPv6 방문자에 대한 CF 매핑 ID로 동작하므로 식별자로 유효

CF IPv4 CIDR 리스트 (VRL 내 inline):

173.245.48.0/20, 103.21.244.0/22, 103.22.200.0/22, 103.31.4.0/22,
141.101.64.0/18, 108.162.192.0/18, 190.93.240.0/20, 188.114.96.0/20,
197.234.240.0/22, 198.41.128.0/17, 162.158.0.0/15, 104.16.0.0/13,
104.24.0.0/14, 172.64.0.0/13, 131.0.72.0/22

배포: 6대 전부 vector.yaml 재작성(전체 파일 푸시), systemctl kill -s SIGKILL vector && reset-failed && start (기존 inflight retry 회피).

검증

LogsQL (VL):

service:npm log_format:proxy_v2 _time:3m             # 총 1000+ 건
service:npm log_format:proxy_v2 client_ip_is_cf_edge:true _time:3m   # 0 건

샘플 (npm-2 psd777.com, 정비 후):

{
  "host": "npm-2",
  "domain": "psd777.com",
  "remote_addr": "182.208.67.53",
  "client_ip": "182.208.67.53",
  "cf_edge_ip": "172.70.123.209",
  "xff_chain": "182.208.67.53",
  "client_ip_is_cf_edge": "false",
  "client_ip_source": "remote_addr",
  "log_format": "proxy_v2"
}

상위 client_ip (5분 윈도우):

 77  106.101.11.143     (KR)
 62  106.101.2.46       (KR)
 44  247.138.125.110    (CF Pseudo, IPv6)
 42  251.48.101.40      (CF Pseudo)
 37  254.205.147.150    (CF Pseudo)
 36  182.225.199.226    (KR)
 35  247.242.90.88      (CF Pseudo)
 34  251.166.27.129     (CF Pseudo)
 33  250.160.168.101    (CF Pseudo)
 30  121.131.161.96     (KR)

전부 실 클라이언트 (KR IPv4 또는 CF Pseudo). CF edge (172.64.x, 162.158.x, …) 하나도 없음.

호스트별 proxy_v2 수신량(3분 창):

npm-1: 0   (shared/no traffic)
npm-2: 317 (psd777 실트래픽)
npm-3: 510 (rss-555/7790)
npm-4: 562 (fall-vip/mvp/vip7)
npm-5: 0
npm-6: 0

운영 주의

  • NPM 컨테이너 재생성 시 nginx 패치 소실docker-compose down && up 후에는 log_format + real_ip_header 원복. 영속 대응: docker-compose에 nginx.conf custom mount 추가 (후속 과제)
  • Vector VRL에서 ip_cidr_contains 호출 — VRL 0.12+ 에서 제공. Vector 0.55 기준 사용 가능
  • client_ip_is_cf_edge=true 모니터링: 이 플래그가 진실이면 nginx real_ip 재작성 실패 의미. 플래그 상승 추이 감지 시 원인 조사 (CF IP 대역 추가 / real_ip_recursive off / CF-Connecting-IP 헤더 누락 등)
  • Pseudo IPv4 254/247/250/251.x: CF Enterprise 없는 플랜에서 IPv6 방문자 → IPv4 역양식. 동일 방문자에 대해 stateful하므로 rate limit/ban 대상으로 사용 가능
  • CF Pseudo 차단 시 부수효과: CF Pseudo IP 는 같은 CF 지역 방문자 여럿이 공유할 수 있음 (매핑 해시에 따라). 차단 결정 시 XFF chain 교차 확인 권장

후속 과제

  • NPM docker-compose에 custom nginx.conf bind mount 추가 (재생성 영속화)
  • client_ip_is_cf_edge=true 알람 — VictoriaMetrics LogsQL alert (임계: 시간당 10건)
  • CrowdSec scenarios/parser에 client_ip 필드 매핑 — VL service:npm acquisition 추가 시 IP 기반 bouncer 판단 가능
  • xff_chain 다중 IP 케이스 (여러 proxy 경유) 전용 파싱 — 현재는 문자열 그대로 저장

롤백

# nginx original:
# sed -i '[Real $realip_remote_addr] [XFF $http_x_forwarded_for] ' 제거 + real_ip_header CF-Connecting-IP → X-Real-IP
# Vector: vector.yaml에서 client_ip 관련 블록 제거, parse_regex 를 v1 만 사용