Files
obsidian/services/bunnycdn-security.md

8.6 KiB

title, updated, tags
title updated tags
BunnyCDN 엣지 보안 (CrowdSec + 국가 차단) 2026-04-10
cdn
bunnycdn
security
crowdsec
edge-script

구성

  • 엣지 스크립트: crowdsec-bouncer-middleware (ID 64811, ScriptType 2 Middleware)
  • 연결된 Pull Zone: iron-kr (5555227), iron-jp (5555247) — 둘 다 MiddlewareScriptId: 64811 공유
  • 미장착 풀존: iron-git (git smart HTTP 보호 우회, 의도), iron-kr-waf (SafeLine 다운스트림), i-gate (idle)
  • bloom filter 동기화: jp1 infra-tool 컨테이너의 /opt/crowdsec-bouncer/bouncer.py (3분 delta + 매시 full sync)
  • 소스: crowdsec-safeline bloom filter (FNV-1a 임베디드 BLOOM_B64) + Cloudflare Turnstile 캡차

cfb-manager (K8s default/cfb-manager) 는 Cloudflare Worker bouncer 관리 API 이며 BunnyCDN 미들웨어 64811 와 무관.

동작 순서

요청 → BunnyCDN 엣지
  1. 국가 차단 (Cdn-RequestCountryCode 헤더 기반)
     - 차단 국가 → 403 반환
  2. CrowdSec bloom filter (악성 IP 판별)
     - hit → 캡차 또는 403
     - miss → 통과
  3. 캡차 (Cloudflare Turnstile)
     - bloom hit IP 에게 캡차 제시
     - 통과 시 4시간 허용 (DB + 쿠키)
  4. 정상 → Origin 으로 전달

차단 국가

북미·유럽·구소련 권역:

US, CA, GB, DE, FR, IT, ES, NL, BE, AT, CH, SE, NO, DK, FI, PL, CZ, PT, IE, RO, HU, BG, HR, SK, SI, LT, LV, EE, LU, MT, CY, GR, IS, UA, RU, BY

주의사항

  • BunnyCDN Traffic Manager 국가 차단은 사용하지 않음 — Let's Encrypt 검증 및 Origin Shield fetch 를 막아서 Free SSL 발급 실패 + 500 에러 유발
  • 엣지 스크립트의 국가 차단은 Cdn-RequestCountryCode 헤더 기반 → CDN 내부 요청 (Let's Encrypt, Origin Shield) 에는 이 헤더가 없으므로 영향 없음
  • Free SSL 자동 발급/갱신 정상 동작

Pull Zone

ID 이름 OriginUrl Middleware Smart Cache IgnoreQS BlockNoRef 비고
5555227 iron-kr https://220.120.65.245 64811 false true 메인 inouter.com 계열
5555247 iron-jp https://172.233.93.180 64811 false true 미사용 (2026-04-17 호스트네임 전부 제거)
5555224 iron-kr-waf https://220.120.65.245:9443 null true true SafeLine WAF 다운스트림 보호
5584382 iron-git https://220.120.65.245 null true false git smart HTTP 보호 우회 (의도)
5557897 i-gate https://172.233.93.180 null true true idle 슬롯

공통 설정: EnableLogging: true, EnableWebSockets: true (max 500), EnableGeoZone*: true (전 지역), BlockedCountries: [], BlockedIps: [], EdgeRules: [] (모든 분기는 미들웨어 코드 안에서), OriginType: 0 (URL), VerifyOriginSSL: false, EnableOriginShield: false. OptimizerEnablediron-kr / iron-jptrue.

호스트네임 인벤토리

Pull Zone Hostname 시스템?
iron-kr iron-kr.b-cdn.net
iron-kr actions.it.com
iron-kr n8n.inouter.com
iron-kr jarvis.inouter.com
iron-kr telegram-webhook.inouter.com
iron-kr vault.inouter.com
iron-kr outline.inouter.com
iron-jp iron-jp.b-cdn.net (시스템만 남음, 사용자 호스트 전부 제거 2026-04-17)
iron-kr-waf iron-kr-waf.b-cdn.net
iron-kr-waf juiceshop.keepanker.cv
iron-git iron-git.b-cdn.net
iron-git gitea.inouter.com
i-gate i-gate.b-cdn.net

총 13 (사용자 8 + 시스템 5). 전부 HasCertificate: true (Let's Encrypt 자동 발급/갱신). Bunny API 응답에는 인증서 만료일이 노출되지 않아 만료 모니터링은 외부 cert 체크 또는 Bunny 대시보드 알림에 의존.

Edge Script / Middleware attach

Script ID 이름 Type 부착 풀존
64811 crowdsec-bouncer-middleware 2 (Middleware) iron-kr, iron-jp

기타 풀존 미장착 사유: iron-git 은 git smart HTTP 바이너리 보호 불가라 의도적 우회, iron-kr-waf 는 SafeLine 이 담당, i-gate 는 idle.

CrowdSec bloom filter 동기화

CrowdSec LAPI (jp1, 10.253.100.240:8080) → decision stream
    ↓
infra-tool 컨테이너 /opt/crowdsec-bouncer/bouncer.py
    ├ 3분 delta sync
    └ 매시 full sync
    ↓
bloom filter (FNV-1a) base64 → BunnyCDN compute API
    → Edge Script 64811 코드의 BLOOM_B64 / BLOOM_VERSION 상수 정규식 교체
    → publish
    ↓
엣지 스크립트가 요청마다 client IP 를 bloom filter 로 체크
  hit  → Turnstile 캡차 (verified 4h 쿠키)
  miss → origin 통과

Turnstile sitekey

미들웨어 64811 의 TURNSTILE_SITE_KEYTURNSTILE_SECRET_KEY 는 env 변수로 주입 (코드에 hardcoded 아님). 현재 값은 cloudflare#Turnstile 위젯inouter-bunny-middleware (0x4AAAAAAC3otPWhldI96Aks). 이 위젯은 cs-cf-worker-bouncer 의 이름 패턴 (crowdsec-cloudflare-worker-bouncer-widget) 과 다르므로 bouncer rotation 대상 외 — 수동 관리.

위젯 교체 절차:

  1. 신규 위젯 생성 (이름에 bouncer 포함 금지)
  2. Vault secret/cloud/cloudflare/turnstile-inouter-bunnysitekey, secret, name 저장
  3. 미들웨어 64811 의 TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY env 두 변수 동시 갱신 + publish
  4. 옛 sitekey 가 미들웨어 env 에 남아있지 않은지 검증 (grep), 옛 위젯 삭제 가능

특이사항

  • i-gate (5557897): 시스템 호스트 1개, 0 byte 트래픽. 빈 슬롯 — 용도 부여 또는 제거 필요
  • Turnstile sitekey zone gap: 2026-04-17 해결. inouter-bunny-middleware 위젯 domains에 inouter.com, anvil.it.com, actions.it.com, n8n.anvil.it.com, tg.anvil.it.com, linode.actions.it.com, iron-kr.b-cdn.net, iron-jp.b-cdn.net 등록 완료. Cloudflare Turnstile는 루트 도메인 등록 시 서브도메인 자동 커버 (inouter.com → *.inouter.com). 위젯 도메인 상한 10개.

DDoS 과금 정책 (2026-04-17 지원팀 확인, Ticket #386429)

  • WAF 차단 요청: 과금됨. 차단 응답도 outgoing bandwidth로 집계. 단, Origin 응답보다 크기가 작아 비용은 적음
  • DDoS PoW 챌린지 페이지: 과금 안 됨. 챌린지는 Bunny 소유 별도 도메인에서 호스팅되어 내 계정 대역폭 미포함
  • DDoS 크레딧/보호: 공식 보장 없음. Shield가 정상 동작했음에도 피해가 큰 경우 케이스별 goodwill 크레딧 검토 가능 (보장 아님)
  • 대응: Rate Limiting으로 불필요한 차단 응답 자체를 줄이고, MonthlyBandwidthLimit으로 비용 상한 설정

대역폭 한도 (2026-04-17 설정)

Pull Zone MonthlyBandwidthLimit
iron-kr 100 GB
iron-jp 100 GB
iron-kr-waf 100 GB
iron-git 100 GB
i-gate 10 GB

DDoS 비용 폭탄 방지용. 정상 월 사용량 ~1.2GB 대비 약 40배 여유.

⚠️ MonthlyBandwidthLimit 초과 시 동작 (2026-04-17 지원팀 확인):

  • 503이 아니라 Pull Zone 자체가 비활성화 — 모든 요청에 응답 불가
  • DNS를 Bunny 밖으로 돌려야 복구됨
  • 실시간 적용 아님 — cron job이 수 시간 간격으로 실행되어 초과 후에도 트래픽이 한동안 흐를 수 있음
  • Rate Limiting 규칙: Shield 무료 플랜(planType: 0)은 풀존당 2개. 증가하려면 플랜 업그레이드 필요
  • 무료 플랜 제한: timeframe 최대 10초, blockTime 최대 60초. Advanced 플랜 필요 시 더 긴 윈도우/차단 가능

Rate Limiting 규칙 (2026-04-17 설정, 전 풀존 동일)

규칙 requestCount timeframe blockTime actionType 용도
IPBurst200per10s 200 10s 60s 1 (Block) 순간 폭주 (초당 20+) → 즉시 차단

무료 플랜 1/2 슬롯 사용. MidRate 규칙은 NAT/공유기 환경 오탐 + 비브라우저 클라이언트(git, API) 호환 문제로 삭제 (2026-04-17).

⚠️ iron-git (101013)은 rate limit 규칙 없음 — git smart HTTP는 push/pull 시 단기간에 대량 요청을 보내므로 rate limit에 걸림. git 클라이언트는 Challenge도 통과 불가. 2026-04-17 rate limit으로 인한 접속 장애 발생 후 규칙 전부 삭제. 또한 iron-git에 MiddlewareScriptId: 64811이 장착되어 있음 (Obsidian 기존 기록과 불일치 — 확인 필요).

관련 문서