EDR | OWLBY

소연·5일 전

위 화면은 아무 문제가 없어 보입니다.
Alert도 없고, EDR 상태도 정상입니다.

그러나 보안 관제에서 가장 위험한 화면은 경고가 많은 화면이 아니라,
아무 이유 없이 조용한 화면입니다.

Alert가 0건일 때 우리는 시스템이 안전하다고 생각하기 쉽습니다.

하지만 실제로는
Endpoint Agent가 오프라인이거나,
일부 센서가 제대로 동작하지 않거나,
수집 파이프라인의 처리가 지연된 상태일 수도 있습니다.

공격이 탐지된 이후에도 문제는 남습니다.
프로세스 실행, 외부 통신, 파일 변경과 DNS 기록이
서로 다른 화면과 저장소에 흩어져 있다면,

관제 담당자는 Alert가 어떤 Event에서 시작됐고 어떤 Rule을 거쳐 Incident가 됐는지 다시 조립해야 합니다.

OWLBY는 이 두 문제에서 출발했습니다.

경보의 수가 아니라 볼 수 있는 상태인지를 먼저 판단하고,
탐지 결과를 흩어두지 않고 원본 Event까지 되짚을 수 있게.

실제 Windows·macOS Endpoint의 보안 이벤트를 수집하고,
위협 수준(Threat Level)과 수집 건전성(Collection Health)을 판단하며,
탐지 결과를 원본 Event까지 추적하는 EDR 플랫폼.

그게 OWLBY입니다.

우리의 EDR은 여기서부터 시작합니다.

Github / 박소연, 이주호, 이혜령, 황건하

핵심 기능

각각의 Event들이 어떻게 조사 가능한 하나의 Incident가 되는지, 하나의 알럿을 예로 들어 보여드립니다.

어느 단말에서 인코딩된 PowerShell이 실행됐고,
OWLBY가 이를 알럿으로 잡았습니다.
이 알럿 하나를, 담당자가 조사하는 순서 그대로 따라가 보겠습니다.

— 수집 Agent

“방금 그 알럿은 어디서 왔을까?”

Windows(C++)와 macOS(Swift) 네이티브 Agent가 프로세스, 네트워크, 파일, DNS, L7 이벤트를 수집합니다.
그 PowerShell 실행도, 한 단말의 Agent가 프로세스 이벤트로 잡아 중앙으로 보낸 한 건이었습니다.

— Overview

“지금 우리 조직은 괜찮을까?”

담당자가 가장 먼저 보는 화면. 방금 그 알럿으로 위험도가 막 올라갔습니다.
조직 전체의 현재 상태를 Threat Level과 Collection Health 두 축으로 판단합니다.
KPI, Detection Activity, Alert Severity 분포, Fleet 분포를 한 화면에서 확인합니다.

Overview 사진 자료
Overview 사진 자료

— Alerts → Events

“이 경고, 대체 뭐지?”

그 알럿을 엽니다. 규칙이 무엇을 보고 반응했는지 확인합니다.
Alert 상세의 원본 이벤트까지 되짚고, Process Tree로 누가 이 PowerShell을 띄웠는지 부모 프로세스를 추적합니다.

Alerts 사진 자료
Alerts 사진 자료

Alerts-Events 사진 자료
Alerts-Events 사진 자료

Events 사진 자료
Events 사진 자료

— Incidents

“이게 다 연결된 하나의 사건인가?”

같은 단말에서 30분 안에 발생한 관련 Alert들이 하나의 Incident로 자동 연결됩니다.
이제 담당자는 흩어진 경고 여러 개가 아니라, 하나의 사건과 그 근거들을 봅니다.
Attack Timeline과 Investigation Graph로 Event → Alert → Incident의 관계를 조사합니다.

Incidents 사진 자료
Incidents 사진 자료

Incidents 사진 자료
Incidents 사진 자료

— Intelligence

“그래서 이 공격, 밖으로 뭘 전송했을까?”

MITRE ATT&CK tactic/technique 분포, top process/domain/IP, Endpoint egress Topology를 보여줍니다.
IP·Domain을 correlate하면 현재 DNS와 과거 관찰을 구분해 함께 보여줍니다.

Intelligence 사진 자료
Intelligence 사진 자료

Intelligence 사진 자료
Intelligence 사진 자료

— Operations

“우리 수집이 놓친 건 없을까?”

Ingest Health, Live Service/Pipeline Probe, Archive 상태로 수집 파이프라인 자체의 건강을 모니터링합니다.

Operations 사진 자료
Operations 사진 자료

다시, 처음의 질문으로 돌아갑시다.

“그래서 무슨 일이 일어난 거지?”

이제 담당자는 답할 수 있습니다. 누군가 인코딩된 PowerShell로 명령을 숨겨 실행했고, OWLBY는 그 한 줄에서 시작해 흩어진 신호들을 하나의 사건으로 엮어 보여줬습니다.

수십 개의 경고가 아니라, 하나의 근거. 탐지에서 끝나지 않는 조사. 그게 OWLBY입니다.

추가 기능

이외에도 사용자의 접근성과 편의를 위한 UI/UX적 기능을 도입했습니다.

— 라이트/다크 테마 전환

사용자 환경에 맞춰 화면 테마를 선택할 수 있습니다. 오래 화면을 들여다보는 관제 담당자의 눈 피로를 줄입니다.

— 한/영 언어 선택

한국어와 영어를 전환할 수 있습니다. 화면 언어와 함께 날짜 표기도 선택한 언어에 맞춰 바뀝니다.

— Alerts 상태 변경

담당자가 Alert를 검토하며 상태를 직접 바꿀 수 있습니다. Submit & Next로 미해결 Alert 큐를 끊김 없이 넘겨 처리합니다.

— 대시보드 커스텀

Overview 위젯을 드래그·리사이즈로 재배치해 자신에게 맞는 대시보드를 구성할 수 있습니다. 구성한 레이아웃은 사용자별로 저장됩니다.

System Architecture

ERD

데이터의 성격에 따라 저장소를 나눕니다.
Agent 로컬 버퍼는 SQLite,
원본 이벤트 검색·집계는 ClickHouse,
사용자·단말·경고·사건은 PostgreSQL,
실패 원문과 장기 아카이브는 S3/Glacier가 담당합니다.

Frontend

— React 19

React는 컴포넌트 기반 UI 라이브러리로, 복잡한 대시보드 화면을 재사용 가능한 단위로 구성할 수 있게 해줍니다.

— TypeScript

TypeScript는 정적 타입을 제공해 런타임 이전에 오류를 잡아줍니다.
EDR_C에서는 백엔드 OpenAPI 명세로부터 타입을 자동 생성하여, API 응답 계약과 프론트 코드가 항상 일치하도록 보장했습니다.

— Vite

Vite는 초고속 빌드 및 개발 서버 환경을 제공하는 툴입니다.
HMR덕분에 조사 화면을 개발하는 속도를 극대화할 수 있었습니다.

Backend

— FastAPI & Uvicorn

FastAPI는 Python 기반의 고성능 웹 프레임워크로, Pydantic 모델을 통해 API 응답 계약을 엄격하게 강제합니다.
EDR_C에서는 Agent용 Collector API와 Dashboard API를 FastAPI로 구축했습니다.

— Nginx

Nginx는 역방향 프록시로, HTTPS/mTLS 종료와 트래픽 관리를 담당합니다.
EDR_C에서는 Nginx가 Agent의 client 인증서를 검증하고, 외부 신원 헤더를 제거한 뒤 검증된 인증서만 백엔드로 전달하도록 했습니다.

— Apache Kafka

Kafka는 대용량 이벤트 스트리밍 플랫폼입니다.
EDR_C에서는 수집 경계와 탐지 경계를 분리하고, at-least-once 전달을 전제로 멱등성을 설계해 이벤트 중복과 Worker 장애를 견디도록 했습니다.

— PostgreSQL

PostgreSQL은 관계형 데이터베이스로, 정합성이 중요한 데이터를 저장합니다.
EDR_C에서는 사용자, 단말, 인증키, Alert, Incident, 감사 로그를 PostgreSQL에 저장합니다.

— ClickHouse

ClickHouse는 대용량 이벤트 검색·집계에 특화된 컬럼형 데이터베이스입니다.
EDR_C에서는 위험 여부와 관계없이 모든 유효한 원본 메타데이터 이벤트를 ClickHouse에 저장하여, 조사 시 언제든 원본을 빠르게 되짚을 수 있도록 했습니다.

— AWS S3 & Glacier

S3와 Glacier는 객체 저장소로, 장기 보관과 비용 효율을 담당합니다.
EDR_C에서는 최근 이벤트는 ClickHouse에서 조회하고, 오래된 이벤트는 Parquet로 묶어 S3 Glacier에 아카이브했습니다.

DevOps

— Docker & Docker Compose

EDR_C는 Docker와 Docker Compose로 전체 스택을 컨테이너화했습니다.
개발자는 한 번의 Compose로 전체 개발 환경을 번거로운 로컬 설정 없이 바로 실행할 수 있습니다.

— AWS EC2 & Portainer (GitOps)

Mac mini의 Portainer Server가 EC2 Agent를 원격 관리합니다.
main에 push하면 GitHub Actions가 커밋 SHA로 이미지를 빌드하고, production 브랜치에 이미지 SHA를 고정하면, Portainer GitOps polling이 감지해 5분마다 자동 재배포합니다.

— AWS S3

운영 환경에서 S3는 실패 이벤트 원문과 raw event 아카이브의 저장소입니다.
Glacier에 raw event를 아카이빙 하고, 실패 이벤트는 90일 만료를 관리하여 저장 비용을 자동으로 최적화했습니다.

Monitoring

— Grafana Cloud

Grafana Cloud는 Observability SaaS 플랫폼입니다.
EDR_C에서는 메트릭, 로그, 알림을 Grafana Cloud에 모아, 별도 모니터링 서버를 직접 운영하지 않고도 시스템 상태를 실시간으로 파악할 수 있도록 했습니다.

— Grafana Alloy

Alloy는 Grafana의 통합 수집 에이전트입니다.
EDR_C에서는 EC2에서 Alloy 하나가 호스트, 컨테이너, Kafka, 백엔드 지표와 Docker 로그를 30초 주기로 모아 Grafana Cloud로 전송합니다.

— Grafana Mimir

Mimir는 Grafana Cloud의 대규모 메트릭 저장 백엔드입니다.
EDR_C의 모든 Prometheus 메트릭은 Alloy를 통해 Mimir에 저장되어 장기 추세 분석과 대시보드에 활용됩니다.

— Grafana Loki

Loki는 라벨 기반 로그 집계 시스템입니다.
EDR_C에서는 Docker 소켓에서 컨테이너의 로그만 골라 Loki로 전송하여, 여러 서비스의 로그를 한 곳에서 검색·분석할 수 있도록 했습니다.

Troubleshooting

시드 데이터를 248,000건 규모로 늘리자, 로컬에서 잘 돌던 화면들이 하나씩 무너지기 시작했습니다. 그 과정에서 발견하고 개선한 세 가지 사례를 공유합니다.

— 이벤트 저장소를 PostgreSQL에서 ClickHouse로 옮긴 이유

문제

초기에는 Alert·incident 이외에도 원본 이벤트까지 전부 PostgreSQL에 저장했었습니다. 데이터 형식이 하나로 유지되어 개발이 빠르고, 조인도 편했습니다.

문제는 시드 데이터를 248,000건 규모로 늘리면서 드러났습니다. Dashboard Overview가 24시간 조회에 16.31초, 7일 조회는 20초(Timeout)를 넘겨 아예 응답하지 못했습니다.

원인

원본 이벤트 데이터는 쉴 새 없이 append 되어 양이 매우 방대합니다. 덧붙여 읽기는 GROUP BY(기간별 집계)로 이루어 지기에, PostgreSQL에서는 특정 컬럼 몇 개만 집계하고 싶어도 구조상 전체 행을 훑어야 했습니다.

PostgreSQL을 계속 원본 이벤트 저장소로 사용했다면, 데이터 양이 늘어날수록 연산 시간은 끝도 없이 늘어날 수 밖에 없었습니다.

결과

따라서 원본 이벤트 저장소 용도로 ClickHouse를 도입했습니다. ClickHouse는 대량 append와 GROUP BY 연산에 최적화 된 DBMS입니다. 단지 DBMS 유형을 바꾼 것 뿐이지만, 매우 개선된 결과를 얻을 수 있었습니다.

아예 실패했던 7일 조회 화면은 0.7초대로 들어왔습니다.

— 이벤트 아카이빙용 저장소로 RDS가 아닌 S3/Glacier을 선택한 이유

문제

이벤트는 끊임없이 쌓입니다. 이 모든 이벤트들을 RDS 또는 EC2 인스턴스 내에 저장하여, 계속 HOT Data로 처리하는 것은 비용 문제로 직결됩니다. 따라서 많은 데이터가 쌓이기 전에, 미리 오래된 이벤트는 다른 저장소에 아카이빙해 두자는 아이디어를 떠올려 봤습니다.

결과

실제 AWS 요금(us-east-1, 2026)으로 저장 단가를 비교하면 격차가 분명합니다.

예를 들어 아카이빙된 이벤트가 1TB까지 쌓였을 때, 각 저장소 마다의 1년 저장 비용을 계산해 봅시다.

  • RDS cost = 1,000GB × $0.115 × 12M ≈ $1,380 / Y
  • S3 Glacier cost = 1,000GB × $0.0036 × 12M ≈ $43 / Y

같은 데이터를 약 32배 저렴하게 보관할 수 있습니다. 이러한 계산을 통해 최근 데이터는 ClickHouse에 저장해 두되, 오래된 이벤트는 ZSTD로 묶어 보관이 저렴한 S3 Glacier에 저장하는 방식을 채택했습니다.

0개의 댓글