Files
obsidian/infra/crowdsec-safeline.md
kappa fb5a34ebcc edge gateway: "APISIX = SafeLine WAF 전용" 표현 정정
kappa 피드백 + kubectl 실측 기반 정정. 이전 기록의 "SafeLine WAF 전용 리버스
프록시" 또는 "SafeLine WAF 전용" 표현은 오해의 소지가 있음.

실측 사실 (2026-04-10 kubectl get svc + ApisixRoute + HTTPRoute):
- APISIX (svc apisix-gateway): MetalLB VIP 192.168.9.50, gateway 80/443 +
  admin 9180. 독립 LoadBalancer. Deployment replica 2.
  ApisixRoute 1건: juiceshop/juiceshop → juiceshop.keepanker.cv → juiceshop
  svc (chaitin-waf plugin 으로 SafeLine 통합 테스트)
- Traefik (svc traefik): MetalLB VIP 192.168.9.53, 80/443.
  HTTPRoute 14건 (argocd/gitea/grafana/n8n/nocodb/sftpgo/openmemory/searxng/
  kroki/safeline-mgt/vault-ui + api/namecheap + api/vultr + bunnycdn-mcp)
  + 5 legacy IngressRoute (vlogs/outline/vault-mcp/vault-mcp-http/bunnycdn-mcp)

→ **두 gateway 는 동등한 병렬 독립 LoadBalancer**. APISIX 는 Traefik 뒤의
리버스 프록시가 아니라 자체 MetalLB VIP 를 가진 별개 외부 인입 채널.
"SafeLine WAF 전용" 이 아니라, 2026-03-25 메인 라우팅이 Traefik 으로 이전된
이후 현재 APISIX route 가 SafeLine 테스트용 1건만 남은 상태일 뿐 — 범용
gateway 로 언제든 새 route 추가 가능.

정정 대상:
- infra/apisix.md — 서울 섹션 헤더/용도/축소 문구
- infra/infra-hosts.md — 게이트웨이 한 줄 요약
- infra/k3s-migration.md — 게이트웨이 열 + Phase 0 + Namespace 표
- infra/gateway-api.md — 전환 이력 2026-03-25 줄
- infra/crowdsec-safeline.md — waf-kr BunnyCDN Pull Zone 섹션 머리말

부수적으로 2026-04-10 에 발견된 heimdall kubectl 부재 문제 해결
(kubectl + helm + kubeconfig 복원) + tofu cloud-init 자동화 추가 —
commit bd5e4cb (ops-agents-tofu).
2026-04-10 00:36:29 +09:00

16 KiB

title, updated
title updated
CrowdSec 및 SafeLine WAF 2026-04-04

CrowdSec LAPI

항목
위치 jp1 Incus crowdsec 컨테이너
LAPI http://10.253.100.240:8080
관리 ssh incus-jp1 "incus exec crowdsec -- cscli ..."

로그 수집 (Acquisition)

Traefik → CrowdSec (Vector)

Traefik DaemonSet (stdout JSON accessLog)
  → Vector Agent DaemonSet (K3s logging ns, kubernetes_logs source)
  → VRL transform (access log만 필터, non-JSON abort)
  → HTTP sink (배치 50건, 5초)
  → CrowdSec HTTP acquisition (:8086/traefik-logs)
  → crowdsecurity/traefik-logs 파서
항목
Vector Helm vector/vector 0.51.0, Agent DaemonSet (3노드)
Values ~/k8s/vector/values.yaml
CrowdSec 포트 8086
인증 Authorization: traefik-crowdsec-log-2024
파서 crowdsecurity/traefik-logs (Hub, JSON 모드)

APISIX → CrowdSec (http-logger)

APISIX K3s 서울 (apisix ns, global_rules http-logger 플러그인)
  → 배치 50건, 5초 buffer
  → CrowdSec HTTP acquisition (:8085/apisix-logs)
  → custom/apisix-json-logs 파서
항목
송신처 K3s 서울 APISIX 단독 (osaka는 송신 안 함, zlambda는 라우트 비어있음)
설정 K3s APISIX Admin API global_rules/http-logger (uri: http://10.253.100.240:8085/apisix-logs)
CrowdSec 포트 8085
인증 Authorization: apisix-crowdsec-log-2024
파서 custom/apisix-json-logs (로컬)
osaka의 처리 송신은 안 하고 crowdsec-bouncer 글로벌 플러그인으로 결정 소비만. real-ip 플러그인도 글로벌.

APISIX → log-collector → CrowdSec (sandbox-tokyo)

sandbox-tokyo APISIX http-logger
  → log-collector (jp1 crowdsec 컨테이너, :8087)
  → SQLite (/var/lib/log-collector/requests.db)
  → CrowdSec HTTP acquisition (:8085/apisix-logs) 포워딩
항목
log-collector Go HTTP 서버, /usr/local/bin/log-collector (jp1 crowdsec 컨테이너)
소스 jp1 ~/log-collector/main.go
수신 포트 8087
인증 Authorization: apisix-crowdsec-log-2024
SQLite /var/lib/log-collector/requests.db
기능 APISIX 로그 수신 → SQLite 저장 → CrowdSec 포워딩, 타임스탬프 밀리초 보정

SafeLine → CrowdSec (실시간, PG LISTEN/NOTIFY)

SafeLine WAF 차단 → mgt_detect_log_basic INSERT
  → PG 트리거 (notify_detect_log) → pg_notify('safeline_detect', JSON)
  → safeline-listener (kr2, Go) → detail 테이블 enrichment (x-real-ip, user-agent)
  → CrowdSec HTTP acquisition (:8088/safeline-logs)
  → custom/safeline-http-logs 파서
  → custom/safeline-waf-blocked 시나리오 (trigger, 즉시 밴)
항목
safeline-listener Go, /usr/local/bin/safeline-listener (kr2)
소스 kr2 ~/safeline-listener/main.go
systemd safeline-listener.service (kr2)
PG DSN SafeLine CE DB (10.43.8.243:5432, safeline-ce)
CrowdSec 포트 8088
인증 Authorization: safeline-crowdsec-2026
acquis 설정 /etc/crowdsec/acquis.d/safeline-http.yaml
파서 custom/safeline-http-logs (/etc/crowdsec/parsers/s01-parse/safeline-http-logs.yaml)
시나리오 custom/safeline-waf-blocked (trigger 타입, 즉시 밴)
enrichment req_header에서 x-real-ip, user-agent 추출, detail 테이블에서 method/payload 조회

PG 트리거 (SafeLine CE DB에 설치):

CREATE OR REPLACE FUNCTION notify_detect_log() RETURNS trigger AS $$
BEGIN
  PERFORM pg_notify('safeline_detect', json_build_object(
    'id', NEW.id, 'ts', NEW.created_at,
    'src_ip', NEW.src_ip, 'host', NEW.host,
    'url_path', NEW.url_path, 'attack_type', NEW.attack_type
  )::text);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_detect_log
  AFTER INSERT ON mgt_detect_log_basic
  FOR EACH ROW EXECUTE FUNCTION notify_detect_log();

ddos-detect (AI 행위 분석) — 폐기 (2026-04-08)

[!warning] 폐기됨 2026-04-08 분석기 서비스 정지 + 삭제. "이 방식으로는 안 될 것 같다"는 판단 (60초 폴링 + Claude CLI 동기 호출 구조의 한계). 새로운 AI 분석 아키텍처는 victorialogs 기반으로 재설계 예정.

제거된 항목: ddos-detect.service (systemd), /var/lib/log-collector/ddos-detect/ Go 바이너리 + 소스, ddos-detect.sh, extract_behavior.py, ddos-logs/ (분석 결과 markdown). requests.db는 별개의 log-collector 데몬이 사용 중이라 보존. Gitea repo kaffa/ddos-detect는 보존 (코드 reference).

이전 동작 (참고):

항목
위치 jp1 crowdsec 컨테이너 /var/lib/log-collector/ddos-detect/
Gitea gitea.inouter.com/kaffa/ddos-detect
기능 SQLite 폴링 → IP별 행위 패턴 추출 → Claude AI 분석 → cscli 자동 밴
설정 config.yaml (poll 60s, Claude sonnet, ban 4h, max_workers 3)

리얼 IP 처리

경로 설정 source IP 추출
BunnyCDN → HAProxy → Traefik forwardedHeaders.trustedIPs: 127/8, 10/8, 172.16/12, 192.168/16 + externalTrafficPolicy: Local ClientHost (X-Forwarded-For에서 추출)
BunnyCDN → HAProxy → APISIX real_ip_header: X-Forwarded-For + real_ip_from: 127/8, 10.42/16, 10.43/16, 192.168.9/24, 100.64/10 client_ip (nginx real_ip 모듈)

Traefik values: ~/k8s/traefik/values.yaml

시나리오

유형 시나리오 출처
HTTP http-probing, http-crawl-non_statics, http-sensitive-files, http-backdoors-attempts, http-sqli-probing, http-xss-probing 등 Hub
SafeLine custom/safeline-waf-blocked (trigger), custom/safeline-waf-repeated (3+/5min) 로컬
APISIX (2026-04-08 신규) custom/apisix-high-rate-per-ip, custom/apisix-499-burst, custom/apisix-single-path-flood, custom/apisix-5xx-burst 로컬
CAPI http:dos, http:scan, ssh:bruteforce (커뮤니티) 자동

APISIX 시나리오 (4종, 2026-04-08 작성)

ddos-detect AI 분석기 폐기 후 deterministic 패턴 매칭으로 대체. 모두 leaky bucket, ban 4h. K3s 서울 APISIX 트래픽만 보고 판단 (osaka/zlambda는 http-logger 송신 안 함).

시나리오 매개변수 의도 filter
apisix-high-rate-per-ip capacity 1000, leakspeed 100ms (≈ sustained 10 req/s, burst 1000) 일반 HTTP flood. APISIX 자체 limit-req(20 req/s)에 안 걸리는 sustained 패턴 log_type=http_access-log, groupby source_ip
apisix-499-burst capacity 30, leakspeed 2s connection exhaustion / Slowloris (클라이언트 강제 끊김) http_status == "499"
apisix-single-path-flood capacity 50, leakspeed 600ms 동일 path 반복 (CAPTCHA 우회, login bf, 동일 자원 폭격) groupby source_ip + ":" + http_path
apisix-5xx-burst capacity 20, leakspeed 3s 백엔드 부하 유발성 공격, 취약점 스캔 http_status startsWith "5"

⚠️ 운영 주의:

  • 5xx-burst: 백엔드 장애 시 false positive 가능. 운영 알람과 분리해서 검토.
  • 499-burst: 모바일 클라이언트 끊김으로 정상 발생할 수 있어 30 임계값을 며칠 관찰 후 조정 권장.
  • high-rate-per-ip: APISIX limit-req 글로벌 룰(20 req/s burst 10)을 통과한 트래픽만 도달 → 1차 통과 후 sustained pattern을 잡는 2차 layer.
  • 현재 모두 remediation: true (즉시 ban). dry-run으로 시작 안 함 — false positive 발생 시 임계값 또는 ban duration 조정.
  • 2026-04-09 조정: high-rate-per-ip 초기값(capacity 200, leakspeed 300ms) 사용 중 kappa 본인 트래픽이 지속 ≈3.3 req/s 한도를 넘어 오탐 ban 발생 → BunnyCDN 엣지 미들웨어가 캡챠 페이지 반환 → POST /__captcha/verify 가 BunnyCDN 미들웨어에서 미처리 상태로 origin까지 흘러 404 반환하는 dead-end 버그와 결합되어 outline/vault/n8n/jarvis/telegram-webhook/actions 전 호스트 접근 불가. 조치: (1) cscli로 ban decision 삭제 + BunnyCDN zone 캐시 퍼지, (2) 시나리오 완화 capacity 200→1000, leakspeed 300ms→100ms (sustained 10 req/s 허용), (3) BunnyCDN 엣지 스크립트 64811 middleware.ts 수정/__captcha/verify 라우트를 isBlocked 블록 밖으로 끌어올려 bloom filter 업데이트 race와 무관하게 항상 인터셉트하도록 변경. POST는 handleCaptchaVerify, 그 외(method/ip 없음)는 302 to /. 소스: gitea 7da39b2ac.
  • 2026-04-09 별건: cs-cf-worker-bouncer.yaml line 68-78 (anvil.it.com 블록) 들여쓰기 12칸 오타로 5일간 42,593회 systemd crash loop, Cloudflare Worker KV 동기화 중단. sed로 인덴트 4칸으로 정정, 5일 만에 정상 기동. 별건: K8s default/cfb-ssh-key 시크릿의 id_rsa 데이터가 libcrypto parse 실패 상태라 cfb-managercs-cf-worker-bouncer SSH 인증 불가, /domains /status API가 500 반환. 새 ed25519 키페어 생성·시크릿 교체·authorized_keys 등록·파드 재시작으로 복구.
  • 2026-04-09 Turnstile sitekey 교체: BunnyCDN 엣지 스크립트 64811의 TURNSTILE_SITE_KEY env 변수가 0x4AAAAAACbmaudAjITah7y7 (name inouter, allowed domain ['anvil.it.com'])로 설정되어 있어 outline.inouter.com에서 Turnstile 위젯이 "웹 사이트에 연결할 수 없음" 에러. cs-cf-worker-bouncer가 managed 모드로 자동 생성한 inouter.com zone 위젯(0x4AAAAAAC2cntUlRC3KKMKG, secret 0x4AAAAAAC2cnp9fkaIBt3rixDBalNKfLZQ)으로 교체. Turnstile managed 위젯은 zone apex 도메인으로 등록해도 서브도메인을 암묵 허용함이 확인됨 (outline.inouter.com 정상 동작). 단, iron-kr 풀존에는 actions.it.com·iron-kr.b-cdn.net도 포함되는데 이들은 inouter.com zone 외부라 캡챠 위젯 로드 불가. iron-jp 풀존(anvil.it.com 등)도 동일 제약. TODO: 스크립트를 수정해 request.hostname에 따라 zone별 sitekey/secret을 dispatch하거나, CF 대시보드에서 multi-domain 위젯 하나로 통합.

발견 사항: K3s APISIX 글로벌 limit-req

{
  "key_type": "var",
  "key": "remote_addr",
  "rate": 20,
  "burst": 10,
  "rejected_code": 429
}

/apisix/global_rules/limit-req (etcd). 모든 라우트에 적용. CrowdSec 시나리오는 이 1차 차단을 통과한 트래픽만 본다는 점을 고려해서 임계값 설계.

Bouncer

cs-cf-worker-bouncer (Cloudflare Worker)

항목
위치 jp1 Incus cs-cf-worker-bouncer 컨테이너
설정 /etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml
정본 gitea.inouter.com/kaffa/k8sconfigs/crowdsec/crowdsec-cloudflare-worker-bouncer.yaml
동기화 10초 (decision stream polling)
방식 LAPI → bouncer → Cloudflare Worker KV (bloom filter) → Worker에서 차단/captcha
보호 zone keepanker.cv, actions.it.com, ironclad.it.com, inouter.com, servidor.it.com
Turnstile 5개 zone managed 모드, 168시간 secret key 로테이션

Decision 수동 관리

bouncer의 lapi_key읽기 전용. decision 추가/삭제는 crowdsec 컨테이너에서 cscli로:

# ban 추가
ssh incus-jp1 "incus exec crowdsec -- cscli decisions add --ip 1.2.3.4 --duration 2m --reason 'manual ban' --type ban"

# 확인
ssh incus-jp1 "incus exec crowdsec -- cscli decisions list --ip 1.2.3.4"

# 삭제
ssh incus-jp1 "incus exec crowdsec -- cscli decisions delete --ip 1.2.3.4"

bouncer 컨테이너에서 curl POST /v1/decisions로 직접 넣으면 등록 안 됨 — API 키 권한 제한.

설정 변경 시 주의

sed -i 사용 금지 — Incus 컨테이너 내에서 sed -i로 수정 시 이전 내용이 파일 끝에 남아 YAML 깨짐. 반드시 전체 파일 덮어쓰기:

cat updated-config.yaml | ssh incus-jp1 "incus file push - cs-cf-worker-bouncer/etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml"
ssh incus-jp1 "incus exec cs-cf-worker-bouncer -- systemctl restart crowdsec-cloudflare-worker-bouncer"

netbis-cf (Cloudflare Worker — Netbis 계정)

Kappa 계정용 cs-cf-worker-bouncer와 별도 컨테이너로 분리 운영. 상세: netbis

항목
위치 jp1 Incus netbis-cf-bouncer 컨테이너 (10.253.103.33)
바운서 이름 netbis-cf
설정 /etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml
동기화 10초
CF 계정 Netbis (netbis@netbis.io, Account ID 8fcf3c7876332aba33e974cbbfdad951)
보호 zone fall-vip.com, fall-mvp.com, fall-vip7.com, psd777.com, rss-555.com, rss-7790.com
Turnstile 6개 zone managed 모드, 168시간 secret key 로테이션
로그 소스 sandbox-tokyo APISIX → CrowdSec http-logger (8085/apisix-logs)

bunny-cdn-bouncer (BunnyCDN Edge Script)

항목
동기화 jp1 infra-tool 컨테이너, /opt/crowdsec-bouncer/bouncer.py
크론 3분 delta sync, 매시 full sync
방식 LAPI → Bloom filter → BunnyCDN Edge Script 임베딩 → 퍼블리시
Edge Script ID 64811 (crowdsec-bouncer-middleware), FNV-1a bloom filter
차단 시 Cloudflare Turnstile CAPTCHA + LibSQL verified IP 캐싱(4h)
적용 풀존 iron-jp (5555247), iron-kr (5555227) — 두 풀존 모두 MiddlewareScriptId: 64811 (2026-04-09 실측)
API 키 Vault secret/infra/crowdsec-bunny-bouncer
소스 ~/crowdsec-bunny-bouncer/

보안 구조

클라이언트 → BunnyCDN Edge Script (CrowdSec bouncer, 0차)
          → BunnyCDN WAF (OWASP CRS, 1차)
          → Traefik / APISIX + SafeLine WAF (2차)
          → CrowdSec 로그 분석 (3차) → bouncer 피드백 루프

0차: BunnyCDN Edge Script (CrowdSec bouncer)

  • Bloom filter로 악성 IP 즉시 차단
  • false positive 시 Turnstile CAPTCHA로 구제

1차: BunnyCDN WAF (OWASP CRS)

  • CDN 에지에서 오리진 도달 전 차단
  • 차단: SQLi, XSS, CMDi, SSRF, Shellshock, Log4j
  • 비활성화: DATA LEAKAGES SQL (id=911) — NocoDB 오탐 방지
  • 통과: Request Smuggling, NoSQLi, 경로 스캔

2차: SafeLine WAF (chaitin-waf)

  • APISIX plugin_metadata → detector 10.43.253.244:8000
  • SafeLine v9.3.2, K3s safeline ns
  • 관리: safeline.inouter.com, API 헤더 X-SLCE-API-TOKEN
  • 토큰: Vault secret/infra/safeline
  • tengine 미사용, APISIX 직접 연동

3차: CrowdSec 로그 분석

  • Traefik 로그: Vector DaemonSet → :8086crowdsecurity/traefik-logs
  • APISIX 로그: http-logger → :8085custom/apisix-json-logs
  • HTTP 시나리오 매칭 → decision → bouncer 피드백

iron-kr-waf BunnyCDN Pull Zone (구 waf-kr)

APISIX 외부 인입 경로 (juiceshop SafeLine 통합 테스트 용도). APISIX 자체는 독립 LoadBalancer gateway (MetalLB 192.168.9.50) 이지 "SafeLine 전용" 이 아님 — 현재 1 route 가 SafeLine 용일 뿐.

항목
Zone ID 5555224
Name iron-kr-waf
Origin https://220.120.65.245:9443
경로 인터넷 → BunnyCDN(iron-kr-waf) → OpenWrt HAProxy(:9443) → MetalLB 192.168.9.50:443 → APISIX(K3s 서울) → chaitin-waf → SafeLine detector → K3s svc

등록 호스트: iron-kr-waf.b-cdn.net, juiceshop.keepanker.cv (WAF 테스트용). ApisixRoute juiceshop (chaitin-waf block) → juiceshop:3000.

옛 메모의 waf-kr (5554681) 는 더 이상 존재하지 않음 — iron-kr-waf (5555224) 로 통합·재명명됨.

참고

  • BunnyCDN WAF 차단 시 오리진에 로그 안 옴 → CrowdSec에 미수신
  • OpenWrt CrowdSec firewall bouncer는 DNAT 구조라 리얼 IP 매칭 불가
  • chaitin-waf 플러그인은 plugin_attr이 아닌 **plugin_metadata(etcd)**에서 detector 노드를 읽음
  • Incus 컨테이너에서 sed -i로 설정 수정 시 파일 손상 주의 (전체 파일 push 사용)