SOC-in-a-Box: 하나의 LLM, 여덟 개의 역할, 단일 GPU에서 구동되는 프로덕션급 AI SOC
요약
단일 로컬 LLM을 활용하여 보안 운영 센터(SOC)의 8가지 핵심 역할을 수행하는 AI 에이전트 아키텍처를 소개합니다. Redis Streams 기반의 이벤트 중심 파이프라인과 인간 참여형(HITL) 승인 프로세스를 통해 실무 적용 가능한 보안 자동화 시스템을 구축하는 방법을 다룹니다.
핵심 포인트
- 단일 로컬 LLM으로 8개의 독립적 보안 역할 수행 가능
- Redis Streams를 활용한 이벤트 기반 비동기 아키텍처 설계
- 신뢰 확보를 위한 인간 참여형(HITL) 승인 게이트 도입
- 과거 데이터를 활용한 에이전트 성능 백테스트 체계 구축
요약 (TL;DR)
실제 SOC (Security Operations Center)는 경보 분류 (alert triage), 심층 조사 (deeper investigation), 사고 대응 (incident response), 위협 인텔리전스 (threat intel), 탐지 튜닝 (detection tuning), 헌팅 (hunting), 교대 근무 관리 (shift management), 그리고 모든 파괴적인 작업에 대한 인간 승인자 (human approver)라는 8~9개의 뚜렷한 역할과 함께 24시간 365일 운영됩니다. 우리는 Redis Streams 버스를 통해 조정되며, 단 하나의 로컬 LLM (Mac M1 기반의 GLM-4.7-Flash)이 모든 역할을 수행하는 조직도 전체의 AI 버전을 구축했습니다. v1 버전은 실제 시스템에 대해 읽기 전용 (read-only)으로 작동하며, 유일한 쓰기 (write) 작업은 XSOAR 노트와 Webex 카드 작성, 그리고 모든 제안된 격리 조치 (containment action)에 대한 인간 승인 게이트뿐입니다.
<table><tbody><tr><td width="33%"><h2>8개 역할</h2>Sentinel · Tier 2 · IR Lead · Threat Intel · SOC Manager · Detection Eng · Threat Hunter · HITL</td><td width="33%"><h2>1개 LLM</h2>vllm-mlx를 통한 m1 GLM-4.7-Flash, studio1 백업으로의 FailoverChatModel 포함</td><td width="33%"><h2>0개 쓰기</h2>CrowdStrike, Tanium, Zscaler에 대한 쓰기 — 에이전트가 *제안*하고, 인간이 *실행*함</td></tr></tbody></table>흥미로운 점은 에이전트 그 자체가 아닙니다. 도구를 사용하는 LLM 루프 (LLM-with-tools loop) 자체는 새로운 것이 아닙니다. 흥미로운 부분은 다음과 같습니다: (1) 단 하나의 로컬 LLM이 과부하 없이 전체 SOC 조직도를 서비스할 수 있게 하는 아키텍처 설계 (architectural choices), (2) "AI가 격리를 수행한다"는 개념을 보안 팀이 실제로 신뢰할 수 있게 만드는 인간 참여형 (human-in-the-loop) 게이트, (3) 데모를 경영진에게 전달하기 전에 실제 과거 티켓을 대상으로 에이전트의 품질을 수치화할 수 있는 백테스트 하네스 (backtest harness)입니다.
문제의 형태
SOC는 챗봇이 아닙니다. 그것은 24시간 365일 이벤트 기반 파이프라인 (event-driven pipeline)입니다:
- 경고(Alerts)는 요청에 따라 발생하는 것이 아니라 지속적으로 유입됩니다. 시스템은 아무도 질문을 던지지 않을 때조차 실행 중이어야 하며 이벤트를 소비해야 합니다.
- 역할(Roles)은 독립적입니다. Tier 2는 Tier 1에게 질문을 던지는 것이 아니라, Tier 1이 이미 게시한 판결(verdict)을 가져와 더 깊이 파고듭니다. 위협 인텔리전스(Threat Intel)는 사고 대응 리드(IR Lead)의 추론 과정의 일부가 아니라, IR Lead가 수행한 _이후_에 실행됩니다.
- 어떤 역할은 반응적(reactive)이고, 어떤 역할은 주기적(periodic)입니다. Sentinel은 각각의 새로운 티켓에 반응하며, SOC Manager는 8시간마다 교대 근무 요약(shift summary)을 생성합니다. Threat Hunter는 하루에 두 번 감사 로그(audit log)를 스캔하고, Detection Engineer는 평일 아침에 소음이 많은 규칙(noisy rules)을 검토합니다.
- 파괴적인 작업에는 인간의 승인 단계(human gate)가 필요합니다. 새벽 3시에 호스트를 자동으로 격리하는 AI는 한 달 안에 전원이 뽑힐 것입니다. 흥미로운 질문은 이것입니다: 핸드오프(handoff, 인수인계)는 어떤 모습이어야 하는가?
- 감사 가능성(Auditability)은 타협할 수 없는 요소입니다. 모든 결정은 사고 사후 검토(incident retros)와 튜닝(tuning)을 위해 재현(replayable) 가능해야 합니다.
이 아키텍처는 이러한 제약 조건의 결과물이지, 그 반대가 아닙니다.
아키텍처 개요
flowchart TB
XSOAR[XSOAR ticket feed] --> Sentinel
subgraph Bus["Redis Streams bus (lab-vm1)"]
...
세 가지 반응적 역할(Tier 2 / IR Lead / Threat Intel)은 버스(bus) 상의 장기 실행 소비자(long-running consumers)입니다. 세 가지 주기적 역할(SOC Manager / Detection Engineer / Threat Hunter)은 달력 일정에 따라 깨어나 감사 스트림(audit stream)을 재현하고 보고서를 발행하는 systemd 타이머 유닛(timer units)입니다. HITL(Human-in-the-loop) 접점은 기존 IR 웹 앱 옆에 위치한 Flask 블루프린트(blueprint)입니다.
고려 사항
프레임워크 선택은 매우 중요했습니다. 한 번 잘못 선택하면 이후의 모든 추상화(abstraction) 단계에서 어려움을 겪게 됩니다. 우리는 다섯 가지 경로를 평가했습니다:
1. CrewAI
CrewAI는 설계된 목적에 매우 탁월합니다: 즉, 하나의 작업을 위해 협업하는 역할 기반의 에이전트 "크루(crew)"를 구성하는 것입니다. 선언적인 Agent + Task + Process(순차적 또는 계층적) 구조를 가지며, 동일한 크루 내의 에이전트 간 위임(delegation)을 위한 강력한 기본 요소(primitives)를 제공합니다.
SOC와의 불일치: CrewAI는 단일 프로세스 내의 오케스트레이션(orchestration) 실행을 가정합니다. "크루를 생성하고, 태스크를 실행하고, 출력을 얻는다." 우리의 역할(roles)은 하나의 크루가 아닙니다. 각기 고유한 가동 시간(uptime), 감사(audit), 재시작 시맨틱(restart semantics), 그리고 인간 참여(HITL) 게이트를 가진 독립적인 프로세스입니다. CrewAI의 인간 참여(human-in-the-loop) 방식은 태스크(Task)에 human_input=True를 설정하는 것인데, 이는 크루 실행 중에 표준 입력(stdin)을 차단하는 프롬프트 방식입니다. 이는 "Webex 카드 → Flask 페이지 → SQLite 사이드카(sidecar) → 버스 이벤트(bus event)를 통한 캐스케이드(cascade) 재진입" 흐름에서 살아남을 수 없습니다. 만약 이 구조를 강제로 적용한다면, 감사 스트림 재생(audit-stream replay), 백테스트(backtest), 그리고 역할별 systemd 가동 시간을 모두 잃게 될 것입니다.
CrewAI가 활용될 수 있는 지점: 단일 역할의 내부 추론(reasoning)으로서입니다. 예를 들어, Tier 2의 handle() 내부에서 Tier2Analysis 이벤트를 방출하기 전에, 하나의 '도구 사용 LLM 루프(LLM-with-tool-loop)'를 작은 크루(조사관 + 비평가 + 결정자)로 교체하는 방식입니다. 동일한 버스 아키텍처를 유지하면서 역할별 사고 능력을 더 정교하게 만드는 것이죠. 하지만 아직은 그만한 가치가 없을 것 같습니다. 비평가 루프(critic-loop) 패턴을 사용하는 Tier 2만으로도 충분히 작동하기 때문입니다.
2. AutoGen
AutoGen은 대화 형태를 띱니다. 명시적인 GroupChat 관리자가 있는 에이전트 간(agent-to-agent) 채팅 방식입니다. "두 LLM이 논쟁하여 하나의 답으로 수렴하는" 상황, 즉 코드 작성자 vs 코드 검토자, 옹호자 vs 비평가 같은 시나리오에 매우 적합합니다.
불일치 지점: SOC는 대화가 아닙니다. Tier 2는 Tier 1과 대화하는 것이 아니라, Tier 1의 판결(verdict)을 소비합니다. 채팅 기록을 상태(state)로 사용하는 모델은 필요하지 않은 문제에 컨텍스트 윈도우(context-window) 비용을 부과하며, GroupChat 오케스트레이터는 독립적으로 재시작할 수 없는 핵심 의존 요소(load-bearing thing)가 되어버립니다.
3. 일반적인 LangChain (그래프 없음, 버스 없음)
가장 저항이 적은 경로: 각 역할에 대해 Python 함수를 작성하고, 이를 체인(chain)으로 연결하여 동기식(synchronously)으로 실행하는 것입니다. 실제로 저희도 여기서 시작했지만, 곧 문제가 있음을 감지했습니다. 동기식 체인은 모든 역할이 이전 역할이 끝날 때까지 기다리게 만들고, 역할별 재시작을 불가능하게 하며, 해킹 없이는 HITL을 구현할 수 없게 만들고, 별도로 구축하지 않는 한 감사 로그(audit log)를 제공하지 않습니다.
역할이 두 개뿐이라면 그냥 이렇게 하셔도 됩니다. 하지만 저희는 여덟 개였습니다.
4. n8n / Zapier / 시각적 워크플로우 도구
n8n 및 유사한 시각적 워크플로우 (visual workflow) 도구들이 목록에 포함된 데에는 한 가지 구체적인 이유가 있습니다. 바로 경영진이 상자와 화살표로 구성된 도식(boxes-and-arrows)을 보는 것을 좋아하기 때문입니다. 하지만 LLM 노드들은 일급 객체 (first-class)가 아닙니다. 모든 모델 호출을 HTTP로 감싸야 하며, 그래프가 코드가 아닌 데이터베이스에 저장되므로 PR(Pull Request)을 통해 검토 가능한 코드가 아닙니다. 감사 가능성 (Auditability)과 재현성 (reproducibility) 모두 LangGraph + 버스 (bus) 경로보다 떨어집니다. (n8n은 LLM이 아닌 SOAR 스타일의 자동화에는 매우 적합하지만, 이 용도로는 아닙니다.)
5. 밑바닥부터 구축하는 asyncio + Redis Streams
정직한 기준점 (baseline)입니다. Python asyncio 워커, Redis Streams 컨슈머 그룹 (consumer groups), 에이전트 프레임워크 없음. 프레임워크의 추상화 비용은 아낄 수 있지만, LangGraph와 그 친구들이 무료로 제공하는 프롬프트 + 도구 루프 (tool-loop) + 상태 관리 (state-management) 배관 작업을 직접 해야 합니다. 단일 역할의 POC (Proof of Concept)라면 괜찮습니다. 하지만 여덟 개의 역할이라면, LangChain을 나쁜 방식으로 재발명하게 될 것입니다.
우리가 선택한 것: 역할별 추론을 위한 LangGraph + 역할 간 조율을 위한 Redis Streams
LangGraph는 명시적인 상태 (state)를 가진 깔끔한 역할별 도구 루프 (tool-loop)를 제공하며, Redis Streams는 역할 간 조율 (inter-role coordination) — 내구성이 있는 이벤트 (durable events), 최소 한 번 전달 (at-least-once delivery)을 위한 컨슈머 그룹, 또 다른 컨슈머일 뿐인 감사 스트림 (audit stream), 그리고 기존 역할을 건드리지 않고도 새로운 역할을 쉽게 추가할 수 있는 기능 — 을 제공합니다.
이 분리가 핵심입니다: LangGraph는 에이전트 런타임 (agent runtime)이고, 버스 (bus)는 조직도 (org chart)입니다. 이 둘을 혼동하지 마세요.
하나의 LLM, 여러 개의 역할
저희는 단 하나의 로컬 LLM — Mac M1 (64 GB)에서 vllm-mlx를 통해 구동되는 GLM-4.7-Flash 8-bit — 을 실행하며, 모든 역할은 서로 다른 시스템 프롬프트 (system prompt)와 서로 다른 도구 화이트리스트 (tool whitelist)를 사용하여 이를 호출합니다. 회복 탄력성 (resilience)은 FailoverChatModel (이전 포스트에서 처음 설명됨)에서 나옵니다. 이 모델은 M1이 작동을 멈추면 studio1 박스의 Qwen3 백업으로 투명하게 전환(fallback)되며, 기본 모델이 복구되는 즉시 다시 전환됩니다.
📦 새로운 소식 — 오픈 소스로 공개합니다. 해당
FailoverChatModel은 이제 PyPI에서 독립적이고 의존성이 적은 패키지로 제공됩니다:langchain-failover.pip install langchain-failover를 실행하고 두 개의 채팅 모델(chat models)을 지정하기만 하면, GPU 박스가 작동을 멈췄을 때 이 SOC의 두뇌를 온라인 상태로 유지해 주는 것과 동일한 기본/보조 페일오버(failover) 기능을 사용할 수 있습니다. 이 패키지는 연결 상태를 인식하며(예외의 원인 체인(cause chain)을 추적함), 복구 상태를 인식하고(다시 전환될 때 로그를 남김), 스트림 중간에도 안전합니다(mid-stream-safe). 이 패키지가 제대로 처리하는, 눈에 잘 띄지 않는 핵심적인 부분은 바로 도구 호출(tool-calling)이 페일오버 중에도 유지된다는 점입니다. 도구를 양쪽 모델 모두에 바인딩(bind)하므로, 조사를 수행 중인 에이전트가 페일오버되는 즉시 도구를 잃어버리지 않습니다. 이것이 바로 새벽 3시에 SOC 역할(role)이 필요로 하는 기능입니다. 소스 코드, 테스트 및 문서는 다음에서 확인하세요: github.com/vinayvobbili/langchain-failover. 🚀
{: .prompt-tip }
역할마다 여러 모델 제공자(model providers)를 사용하지 않는 이유는 무엇일까요?
- 비용 (Cost) — 모델을 한 번만 로드하면 됩니다. Mac M1은 35B 파라미터를 8비트(8-bit)로 여유롭게 수용하며,
glm47파서를 통해 안정적으로 도구 호출(tool-calls)을 수행합니다. - 지연 시간 (Latency) — 제공자 간의 홉(hop)이 없으며, 조정해야 할 API 속도 제한(rate limits)도 없습니다.
- 운영의 단순성 (Operational simplicity) — 하나의 상태 확인(health check), 하나의 인증 헤더(auth header), 하나의 로그 파일만 관리하면 됩니다.
- 역할들은 실제로 서로 다른 지능이 아닙니다 — 이들은 서로 다른 프롬프트(prompts), 도구 예산(tool budgets), JSON 출력 스키마(JSON output schemas)를 가진 동일한 지능입니다. Tier 2는 30개의 도구 호출을 수행하고, IR Lead는 15개, Threat Intel은 12개를 수행합니다.
GPT-4나 Claude가 우리에게 제공할 수 있는 것은 특정 역할에서의 '더 나은' 추론 능력이 아니라, 24x7 배포 시의 '더 나쁜' 비용 경제성입니다. SEV-1(최고 심각도)의 가장 어려운 사례들을 위해 재검토할 수는 있겠지만, 기본 설정은 로컬(local)입니다.
역할 (The roles)
| 역할 (Role) | 드라이버 (Driver) | 트리거 (Trigger) | 버스 출력 (Bus output) | 실제 시스템 부작용 (Real-system side effect) |
|---|---|---|---|---|
| Sentinel (Tier 1) | XSOAR poller | 새로운 티켓 (New tickets) | AlertTriaged | XSOAR 분류 노트 (triage note) |
| ... |
각 역할의 판정(verdict)은 wall_time_ms, tool_calls_made, 그리고 (백테스트 모드에서의) ground_truth와 함께 verdicts.sqlite 사이드카(sidecar)에 저장됩니다. 이를 통해 첫날부터 OpenTelemetry를 구축하지 않고도 일치율(agreement rates)과 지연 시간 분포(latency distributions)를 계산할 수 있습니다.
HITL 게이트 (The HITL gate)
가장 어려운 설계 문제는 "AI가 격리 조치(containment action)를 수행해야 하는가?"가 아니었습니다. 진짜 문제는 "AI가 인간이 실제로 참여할 수 있는 방식으로 어떻게 업무를 인계(hand off)할 것인가?"였습니다.
우리는 몇 가지 패턴을 시도했습니다. 효과가 있었던 패턴은 다음과 같습니다:
sequenceDiagram
participant IR as IR Lead<br/>(LLM agent)
participant Bus as Redis Streams
...
세 가지 세부 사항이 중요합니다:
- 승인자를 명시합니다. 카드 배너에는 "승인하려면 클릭하세요"가 아니라 "조치 필요 대상: IR Lead On-Call"이라고 표시됩니다. 팀원들은 각 카드가 누구의 편지함에 있는지 알게 됩니다.
- Flask 확인 페이지가 클릭과 기록된 결정 사이에 위치합니다. Webex 카드에서 클릭 한 번으로 승인하는 방식은 유혹적이었지만 잘못된 방식이었습니다. 실수로 클릭했을 때 자동으로 실행될 수 있기 때문입니다. 2단계 방식(버튼 클릭 → 페이지 확인 → 제출 클릭)은 우리가 의도한 마찰(friction)입니다.
- v1은 실제로 실행하지 않습니다. 결정은 로그로 기록되고,
ActionDecision이벤트가 발행되며, 데모는 거기서 마무리됩니다.action.decision[approved]를 소비하여 CrowdStrike RTR / Zscaler / Tanium을 호출하는 실행 에이전트(executor agent)인 v2는 경영진이 이 루프를 신뢰하게 되면 간단히 추가할 수 있습니다. 신뢰는 게이트를 건너뜀으로써 주장하는 것이 아니라, v1을 통해 얻어내는 것입니다.
수치화하기: 백테스트 하네스 (the backtest harness)
SOC 디렉터에게 가장 설득하기 어려운 부분은 "우리가 이것을 만들었다"가 아닙니다. 바로 "실제 경보(alert)에 적용하기 전에 이것이 제대로 작동한다는 것을 어떻게 알 수 있는가?"입니다.
우리는 32,000개 이상의 과거 CrowdStrike 티켓이 포함된 XSOAR 타임라인 데이터베이스를 보유하고 있으며, 각 티켓에는 사람이 Tier 1 단계에서 종료했는지, 아니면 사람이 Tier 2/Tier 3 단계에서 인계받았는지를 알려주는 escalation_state 필드가 있습니다. 이것이 우리의 정답(ground truth)입니다. 분석가가 직접 큐레이션했으므로 별도의 라벨링(labelling)이 필요하지 않습니다.
백테스트 하네스(backtest harness)는 사람이 인계(human-escalated)한 경우와 사람이 종료(human-closed)한 경우를 50/50으로 층화 추출하여 N개의 종료된 티켓을 샘플링한 다음, 모든 부수 효과(side effects)를 무력화한 상태로 에이전트 캐스케이드(agent cascade)를 통해 각 티켓을 재현합니다. 즉, 버스(bus) 게시물은 메모리 내에 캡처되고, Webex 전송은 no-op(아무 작업도 하지 않음) 처리되며, XSOAR 쓰기 및 HITL(Human-in-the-loop) 저장소는 스텁(stub) 처리됩니다.
각 티켓에 대해 다음 사항을 기록합니다:
- Sentinel의 판결(verdict) + 우선순위(priority)
- Tier 2가 개입했는지 여부 및 결정 사항 (인계(escalate) / 종료(close) / 사람의 검토 필요(needs human review))
- IR 리드(IR Lead)가 개입했는지 여부 및 할당된 심각도(SEV)
- 위협 인텔리전스(Threat Intel)가 개입했는지 여부 및 할당된 공격자(actor)
- 각 단계별 실제 소요 시간(Wall time) 및 도구 호출(tool-call) 횟수
그 다음, 실제 상황에서의 사람에 의한 인계(human-escalated-in-real-life) 대 _캐스케이드에 의한 IR 리드 인계(cascade-escalated-to-IR-Lead)_에 대한 혼동 행렬(confusion matrix)을 계산합니다:
TP 사람이 인계함 AND Tier 2가 인계함 → IR 리드
FN 사람이 인계함 BUT Tier 2가 종료함
FP 사람이 종료함 BUT Tier 2가 인계함 → IR 리드
...
TP/FP/FN에 대한 정밀도(Precision)와 재현율(Recall)은 경영진이 원하는 수치를 제공합니다. 즉, _"사람이 실제로 인계할 때 AI가 얼마나 자주 인계하는가, 그리고 얼마나 자주 허위 경보(cry wolf)를 울리는가?"_에 대한 답입니다. 요약 결과는 대시보드 패널이 읽어들이는 JSON 파일에 저장되므로, 이 질문에 대한 답은 느낌(vibe)이 아닌 숫자로 나타납니다.
또한 하네스에는 LLM을 정해진 JSON 스텁(canned-JSON stub)으로 교체하는 --dry-run 모드가 있어, 토큰을 단 하나도 소모하지 않고 2초 이내에 전체 파이프라인(plumbing)을 엔드 투 엔드(end-to-end)로 검증할 수 있습니다. 그리고 이 동일한 하네스는 단순한 스모크 테스트(smoke test)가 아닌 실제 일치 수치를 확인하고 싶을 때 전체 층화 샘플(stratified sample)을 대상으로 실제 LLM 실행을 구동합니다.
우리를 놀라게 한 점
설계를 변화시킨 정도에 따라 순서대로 세 가지가 있습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기