OPNsense 모니터링 구축
개요
OPNsense 방화벽의 주요 서비스를 Grafana 대시보드로 모니터링하는 시스템 구축.
아키텍처
OPNsense (192.168.1.1)
├── node_exporter (:9100) → Prometheus → Grafana (시스템 메트릭)
├── os-telegraf (:9273) → Prometheus → Grafana (Unbound DNS 메트릭)
├── CrowdSec LAPI (:6060) → Prometheus → Grafana (보안 메트릭)
└── syslog (UDP→loki.lan:1514) → Alloy → Loki → Grafana (방화벽 로그)
컴포넌트 구성
1. Unbound DNS (Telegraf)
- 플러그인:
os-telegraf (OPNsense GUI에서 설치)
- 설정:
Services > Telegraf > Input > Unbound 활성화
- 출력: Prometheus 형식 (
:9273/metrics)
- 주요 설정: “루트로 실행” 활성화 (unbound-control 권한 해결)
- Prometheus job:
telegraf
- 수집 메트릭: 쿼리 수, 캐시 히트율, 응답코드, 메모리, 재귀 시간 등
2. CrowdSec
- 엔드포인트:
192.168.1.1:6060/metrics (내장 Prometheus)
- Prometheus job:
crowdsec
- 수집 메트릭: 활성 차단 IP, 알림, 버킷, 파서 처리량, Bouncer 요청
3. 방화벽 로그 (Alloy → Loki)
- OPNsense:
System > Settings > Logging > Remote → loki.lan:1514 (UDP, RFC5424)
- Grafana Alloy (
loki.lan):
- syslog 수신 (UDP:1514)
- filterlog 파싱 (action, direction, src_ip 추출)
- GeoIP 변환 (GeoLite2-City.mmdb)
- Loki로 전송
- 설정 파일:
/etc/alloy/config.alloy
- GeoIP DB:
/etc/alloy/geoip/GeoLite2-City.mmdb
Grafana 대시보드 패널
Unbound DNS 섹션
| 패널 | 타입 | 쿼리 |
|---|
| 총 DNS 쿼리 | Stat | unbound_total_num_queries{job="telegraf"} |
| 캐시 히트 | Stat | unbound_total_num_cachehits{job="telegraf"} |
| 캐시 히트율 | Stat | 히트 / (히트 + 미스) * 100 |
| 캐시 미스 | Stat | unbound_total_num_cachemiss{job="telegraf"} |
| 평균 재귀 시간 | Stat | unbound_total_recursion_time_avg{job="telegraf"} |
| Uptime | Stat | unbound_time_up{job="telegraf"} |
| DNS 쿼리/초 | Timeseries | rate(총 쿼리, 캐시 히트, 캐시 미스) |
| DNS 응답 코드/초 | Timeseries | NOERROR, NXDOMAIN, SERVFAIL 등 |
| DNS 쿼리 타입/초 | Timeseries | A, AAAA, PTR, SRV, HTTPS |
| Unbound 메모리 | Timeseries | Message Cache, RRSet, Iterator, Validator |
CrowdSec 섹션
| 패널 | 타입 | 쿼리 |
|---|
| 활성 차단 IP | Stat | sum(cs_active_decisions{job="crowdsec"}) |
| 로컬 알림 | Stat | sum(cs_alerts{job="crowdsec"}) |
| 활성 버킷 | Stat | cs_buckets{job="crowdsec"} |
| CrowdSec 버전 | Stat | cs_info{job="crowdsec"} |
| 활성 차단 (유형별) | Timeseries | HTTP/SSH/TCP 스캔별 차단 추이 |
| CrowdSec 처리량/초 | Timeseries | 파서, Node, Bouncer 요청 |
방화벽 로그 섹션
| 패널 | 타입 | 쿼리 |
|---|
| Block vs Pass | Timeseries | rate({job="opnsense_syslog", action="block/pass"}) |
| 차단 방향 | Timeseries | In vs Out |
| 차단된 외부 요청 로그 | Logs | {job="opnsense_syslog", action="block", direction="in"} |
Prometheus scrape 설정
- job_name: opnsense
static_configs:
- targets: ['192.168.1.1:9100']
- job_name: telegraf
static_configs:
- targets: ['192.168.1.1:9273']
- job_name: crowdsec
static_configs:
- targets: ['192.168.1.1:6060']
GeoIP 지도 섹션
| 패널 | 타입 | 쿼리 |
|---|
| 차단된 요청 지도 (GeoIP) | Geomap | 국가/위도/경도별 차단 횟수 |
| 국가별 차단 횟수 | Table | 국가명, 국가코드, 차단 횟수 |
Alloy GeoIP 파이프라인 상세
syslog(UDP:1514) → stage.regex(IP 추출) → stage.geoip(GeoLite2-City) → stage.labels + stage.structured_metadata → Loki
GeoIP 출력 필드
- labels:
geoip_country_name, geoip_country_code (낮은 카디널리티)
- structured_metadata:
geoip_city_name, geoip_location_latitude, geoip_location_longitude (높은 카디널리티)
트러블슈팅 이력
- Alloy
loki.source.syslog는 RFC5424만 지원 → OPNsense에서 RFC5424 활성화
- RFC5424 활성화 전 잔여 RFC3164 메시지로 파싱 에러 → Alloy 재시작으로 해결
labels 속성이 loki.source.syslog에서 미지원 → loki.process + stage.static_labels 사용
- 고카디널리티 GeoIP 필드 →
stage.structured_metadata로 분리
stage.geoip 후 stage.labels로 명시적 승격 필요 (shared map → labels)
경고 규칙 (Telegram 알림)
| 경고 | 조건 | 지속 | 심각도 |
|---|
| 외부 공격 급증 | 차단 5분간 200건 초과 | 5분 | Critical |
| DNS 서비스 다운 | Unbound uptime < 1초 | 1분 | Critical |
| DNS 캐시 히트율 저하 | 히트율 < 50% | 10분 | Warning |
| CrowdSec 차단 IP 급증 | 활성 차단 > 20,000개 | 5분 | Warning |
| DNS SERVFAIL 급증 | SERVFAIL rate > 5/초 | 5분 | Warning |
남은 작업