서론
출발은 단순했다. Velociraptor를 EDR로 삼아 Docker로 올리고 EC2의 Wazuh에 붙이면 끝이라고 여겼다. 그런데 연동 문서를 뒤지는 동안 두 도구가 각각 무엇을 하는 물건인지부터 다시 물어야 했고, 그 물음은 Velociraptor가 정말 EDR인지, 격리는 어느 쪽 몫인지, 오픈소스 EDR은 왜 자동 차단을 못 하는지, 서비스가 ECS나 EKS로 옮겨 가면 에이전트를 어디에 둘 수 있는지로 번져 갔다. 모르는 단어가 나올 때마다 한 갈래씩 파고들며 며칠을 보냈고, 그 끝에서 처음의 계획은 전제부터 다시 세워야 했다.
배경 및 관련 개념
SIEM과 DFIR 도구의 기능 정의
Wazuh는 SIEM이고 Velociraptor는 아니다. 처음에는 둘 다 로그를 모아서 보여 주는 도구라고 생각했다. Velociraptor의 웹 GUI에 대시보드와 표가 있고 Sigma 룰까지 돌아가길래 SIEM에 가까운 물건으로 알고 있었는데, 실제로 붙여 놓고 보니 그 대시보드가 보여 주는 것은 내가 돌린 수집 작업의 결과 목록이지 로그 스트림이 아니었다. Wazuh는 중앙에서 판단하는 쪽이고, Velociraptor는 엔드포인트에 내려가 직접 조사하는 쪽이었다.
Wazuh는 manager가 로그를 룰에 대조해 alert를 만들고, indexer(OpenSearch)가 이를 저장하며, dashboard가 indexer를 읽어 보여 주는 구조다. all-in-one으로 깔면 하나처럼 보이지만 판정하는 쪽과 저장하는 쪽이 따로 있다. 이에 비해 Velociraptor는 VQL로 아티팩트를 만들어 엔드포인트에서 증거를 수집하고, 개별 호스트부터 네트워크 전체까지 조사하는 프레임워크로 볼 수 있다. SIEM의 요건에 대보자.
요건 | Wazuh | Velociraptor |
다중 소스 로그 수집(방화벽, 클라우드, 앱) | O | X (자기 클라이언트만) |
정규화(공통 스키마) | O | X (아티팩트마다 컬럼 다름) |
상관분석(룰 엔진, frequency/timeframe) | O | X |
장기 보관과 전체 검색 | O | X (흐름 단위 저장) |
Velociraptor가 Elastic/OpenSearch 업로드 아티팩트를 따로 마련해 둔 것도 같은 행간에서 읽을 수 있다. 스스로 SIEM이라면 밖으로 내보낼 까닭이 없으니, 장기 저장과 검색은 처음부터 외부 SIEM의 몫으로 남겨 두고 설계된 셈이다.
EDR의 구성 요소와 오픈소스의 한계
EDR이라는 이름표도 절반만 맞는다. 네 글자에 하나씩 대보니 Endpoint와 Response(격리, 지속성 제거, 원격 셸)는 있고, Detection은 Sigma 모니터링이 엔드포인트에서 평가되는 반쪽이며, 자동 차단·AV·탬퍼 프로텍션은 없었다.
그렇다면 다른 오픈소스 EDR로 갈아타면 되는가 싶었지만, 내가 검토한 오픈소스 도구들에서는 자동 차단이 상용 EDR만큼 통합되어 있지 않았고, 도구를 바꿔서 풀릴 문제로 보이지 않았다. 왜 그 층만은 오픈소스에서 보기 어려운지 좀처럼 납득이 되지 않아 한참을 더 붙들어야 했다. 프로세스는 이미 실행된 뒤에 죽이면 늦고, 실행 요청 단계에서 거부하려면 커널 모드에서 가로채는 수밖에 없다. 유저 모드 프로그램인 Velociraptor는 ETW로 이벤트를 구독할 수는 있어도 개입까지는 하지 못한다. 그런데 커널 드라이버의 버그는 부팅 불능으로 이어지고, 2024년 CrowdStrike 사건이 보여 주듯 전담 QA 조직이 있어도 수백만 대가 멈출 수 있다. 오픈소스에는 그 책임을 질 주체가 없다. 더구나 Windows는 커널 모드 코드 서명 정책에 맞는 서명 없이 커널 드라이버를 로드하지 않고, macOS의 Endpoint Security Framework는 Apple의 별도 승인이 필요하다. 여기에 차단의 정확도까지 담보하려면 대규모 텔레메트리와 화이트리스트 데이터가 뒤를 받쳐야 한다고 생각하게 되었다. 차단 층은 OS 내장 기능인 Windows Defender에 맡기고, Velociraptor는 조사 도구로 두는 선에서 매듭지을 수 있었다.
쿠버네티스의 구조와 AWS 관리형 서비스
쿠버네티스는 클러스터, 노드, 파드, 컨테이너의 네 계층으로 나누어 볼 수 있다. 이 네 층을 머릿속에 바로 세우기까지 나는 두 번 헛짚었다. 처음에는 쿠버네티스가 컨테이너뿐 아니라 EC2까지 껐다 켰다 하는 줄 알았고, 다음에는 노드가 파드들을 묶은 단위인 줄 알았다.
flowchart TB
C[클러스터: 노드들의 묶음]
C --> N1[노드: 머신 1대<br/>AWS에서는 EC2]
C --> N2[노드]
N1 --> P1[파드: 배치 단위]
N1 --> P2[파드]
P1 --> K1[컨테이너: 프로세스]
P1 --> K2[컨테이너]파드는 떼어놓을 수 없는 컨테이너들의 묶음으로, 같은 노드에 놓이고 IP를 공유해 localhost로 통신하며 볼륨을 나누고 같이 죽는다. 보조 역할을 하는 컨테이너가 사이드카다. 반면 노드는 파드를 묶은 것이 아니라 파드가 얹히는 머신이다. 파드보다 먼저 있고, 파드가 0개여도 남아 있으며, 같은 노드 위의 다른 파드끼리는 localhost도 볼륨도 공유하지 않는다. "묶는다"가 맞는 것은 컨테이너→파드와 노드→클러스터까지고, 파드→노드는 "얹힌다"에 가깝다.
쿠버네티스는 EC2를 만들거나 지우지 못한다. 노드가 EC2인지 온프렘 서버인지 가리지 않으며, 파드를 놓을 자리가 없으면 Pending으로 두고 끝낸다. 노드를 늘리는 일은 Cluster Autoscaler나 Karpenter처럼 IAM 권한을 따로 받아 AWS API를 부르는 별도 컴포넌트의 몫이다. 그렇다면 Autoscaler만 있으면 EKS는 필요 없지 않나 싶었는데, 그 발상 자체가 틀린 것은 아니다. 다만 컨트롤 플레인 운영이 전부 내 일이 되고, 온프렘 컨트롤 플레인에 AWS 노드를 붙이는 하이브리드는 파드 간 지연과 전송 비용 탓에 실무에서는 드물게만 쓰인다.
EKS가 대신해 주는 것은 컨트롤 플레인뿐이고, 워커 노드는 노드그룹과 Fargate 가운데 내가 고를 수 있다. "EKS 노드그룹"이라는 말을 처음 봤을 때는 EKS가 곧 쿠버네티스인데 왜 노드를 따로 말하는지 몰라 한참을 헤맸다. 컨트롤 플레인이라는 말도 정확히 몰랐는데, 실체는 kube-apiserver(
kubectl 창구), etcd(상태 저장소), scheduler(파드를 어느 노드에 놓을지 결정), controller-manager(원하는 상태와 실제 상태를 맞춤) 네 컴포넌트의 묶음이었다. EKS를 쓰면 이 넷이 AWS 계정 안에서 돌고 나에게는 API 엔드포인트 주소만 주어지므로, "EC2 위에 EKS를 설치한다"는 내 표현은 반만 맞은 셈이다.구분 | 컨트롤 플레인 | EC2 노드그룹 | Fargate |
운영 주체 | AWS | 나 (AWS는 실행 자동화만) | AWS |
소유 | AWS 계정 안 | 내 계정의 EC2 | AWS 소유 micro-VM |
접근 | API 엔드포인트만 | SSH 가능 | 불가 |
노드 단위 | 해당 없음 | EC2 1대 = 노드 1개 | 파드 1개 = 노드 1개 |
노드그룹은 워커용 EC2의 묶음(Auto Scaling Group)으로, AMI 선택과 kubelet 설치·등록을 AWS가 자동화하지만 인스턴스는 내 계정에 있고 SSH도 할 수 있다. 이것을 두고 "EKS가 노드를 관리해 준다"고 여겼었는데, 결정과 책임은 그대로 내 것이고 실행 단계만 자동화된 것이었다. 반면 Fargate는 파드 하나마다 전용 micro-VM이 생겼다 사라지며, 커널은 AWS 소유라 그 안에는 손을 댈 수 없다.
컨트롤 플레인 운영의 어려움은 etcd 한 곳에 모인다. 이날 처음 본 이름이었는데, 운영이 왜 어렵다는 것인지 찾아 들어가면 어느 글이든 결국 여기서 멈췄다. 클러스터의 모든 상태를 담는 키-값 저장소로, 파드 목록, 노드 상태, Deployment, Secret, RBAC 설정이 전부 여기 있고, 컨트롤러는 etcd에 적힌 원하는 상태에 현실을 맞춘다. 사라지면 클러스터를 다시 세우는 수밖에 없으며, 운영에는 주기적 스냅샷, 홀수 노드 구성, SSD, 인증서 갱신, 용량 제한 관리가 따른다. EKS 비용의 상당 부분은 이 운영 대행의 값이라고 볼 수 있다. 경량 배포판인 k3s는 etcd 대신 SQLite 파일을 기본으로 쓰지만, 노드 개념이 없는 것이 아니라 노드가 1개일 뿐이다.
여기까지 오고 나서야 정작 중요한 것이 계층 자체가 아니었음을 알았다. 에이전트가 어느 층을 관찰해야 하는지를 정하려면, 그 층 가운데 내가 실제로 손댈 수 있는 머신이 어디까지인지부터 알아야 했다.
Wazuh와 Velociraptor의 역할 경계
탐지 로직의 평가 위치와 변조 위협
Velociraptor의 Sigma 탐지는 침해될 수 있는 호스트 위에서 평가된다. 원시 로그 대신 매칭된 알림만 SIEM으로 보내므로 저장 부담이 줄고 룰 갱신이 Sigma 팩 교체로 끝나는 이점이 있지만, Wazuh 룰이 manager에서 평가되는 것과 달리 엔드포인트가 장악되면 판정 자체가 무력해질 수 있다. 공격자가 Sigma 룰을 고쳐 버리면 어쩌나 싶어 아티팩트 보안 문서를 다시 읽었더니, 개발진 스스로 클라이언트는 잠재적으로 침해된 플랫폼에서 돌기에 그 말을 결코 온전히 믿을 수 없다고 서두에 못 박아 두고 있었다.
그런데 엔드포인트가 장악됐을 때 방어의 초점은 룰 무결성보다 침묵 탐지에 두는 편이 맞아 보인다. 공격자에게는 룰을 고치는 것보다 클라이언트 서비스를 끄거나 서버 주소를 바꾸는 편이 쉽기 때문이다. Wazuh는 agent 연결 끊김을 기본 룰로 잡지만 "Sigma 이벤트 유입이 한동안 0"인 상황에는 약하므로, Velociraptor 쪽에서 주기적 heartbeat 이벤트를 내고 그 부재를 보는 식으로 보완할 수 있다.
서버 장악은 전 엔드포인트 원격 코드 실행과 같으므로 룰 무결성보다 ACL과 패치를 먼저 챙길 수밖에 없다.
ARTIFACT_WRITER 권한을 가진 사용자는 아티팩트 정의를 고쳐 required_permissions 모델을 우회할 수 있어 admin 동등으로 간주되고, 클라이언트는 사용자나 ACL 개념 없이 VQL을 실행하므로 아티팩트가 스케줄되면 클라이언트 쪽에서 무엇이든 할 수 있다.이 두 층을 함께 받치는 대응은 감사 로그를 공격자가 손댈 수 없는 곳으로 즉시 내보내는 것이다. Velociraptor의
VelociraptorAudit 컴포넌트는 로그인, 아티팩트 생성·수정·삭제, 헌트 생성·수정·삭제, 접근 제어 변경을 기록하고, syslog 포워딩을 켜면 감사 이벤트가 기본으로 전달되며 tls를 지정하면 암호화할 수도 있다.# server.config.yaml Logging: separate_logs_per_component: true remote_syslog_server: <Wazuh-IP>:514 remote_syslog_components: - VelociraptorAudit - VelociraptorGUI - VelociraptorAPI
이렇게 두면 공격자가 Velociraptor의 Sigma 룰을 고치는 순간 그 행위가 Wazuh manager의 룰(
operation이 artifact_modify인 감사 이벤트를 높은 level로)에 걸리고, 공격자는 Wazuh 쪽 룰까지는 손대지 못한다. 필드명은 실제 출력 JSON에 맞춰 wazuh-logtest로 확인해 보는 편이 안전하다. 아울러 같은 엔드포인트에서 Wazuh agent가 Sysmon·auth.log 원시 로그를 계속 보내게 두면 Velociraptor 계층이 죽어도 manager 쪽 탐지가 남고, 두 계층의 결과가 어긋나는 것 자체를 단서로 삼을 수 있다.격리 기능 비교
탐지와 달리 격리는 Velociraptor 쪽이 강하다. 격리라면 Wazuh 에이전트로 충분하지 않을까 여겼는데, Wazuh active response
firewall-drop은 소스 IP 하나를 에이전트 방화벽에 더하는 것이고 Velociraptor Quarantine은 호스트 전체를 망에서 떼어 내는 것이라 하는 일이 달랐다. 둘을 나란히 놓아 보자.항목 | Velociraptor Quarantine | Wazuh active response |
차단 대상 | 호스트의 모든 통신 | 특정 소스 IP |
조사 채널 | Velociraptor 서버 연결 유지 | 해당 없음 |
지속성 | 라벨 있는 한 재부팅 후 유지 | 타임아웃 후 해제 |
발동 | 수동 또는 API | 룰 매칭 시 자동 |
적합 상황 | 랜섬웨어·C2·내부자 | SSH 브루트포스·스캐닝 |
Velociraptor 쪽 동작을 조금 더 들여다보자. Windows·Linux 클라이언트의 네트워크 스택을 재구성해 Velociraptor 서버하고만 통신하게 만들어 원격 조사를 이어 갈 수 있게 한다. Windows는 로컬 IPsec 정책으로 DNS·DHCP·서버 접근 외 트래픽을 막고 적용 후 프론트엔드 연결이 안 되면 정책을 되돌리며, Linux는 nftables로 전용 테이블에 체인을 만들어 DNS와 서버 외 트래픽을 끊는다. Windows는
Quarantine 라벨이 있는 한 QuarantineMonitor가 격리를 주기적으로 다시 적용해 재부팅 후에도 유지할 수 있다. 다만 격리는 OS 기능에 기대므로 nftables 미설치나 시스템 손상 시 동작하지 않을 수 있고 어떤 보장도 없으며, 평문 HTTP는 의도적으로 지원하지 않는다.그렇더라도 Wazuh alert에서 API로 자동 격리까지 거는 것은 위험해 보인다. 오탐 한 번에 프로덕션 서버가 망에서 떨어지면 곧 서비스 중단이라, 자동화는 트리아지 수집까지 두고 격리는 사람이 GUI에서 결정하는 편이 맞겠다는 생각이 들었다.
컨테이너 환경에서의 에이전트 배치
컨테이너의 실체와 격리의 방향성
컨테이너는 격리 장치를 씌운 호스트 프로세스다. 그 격리는 컨테이너가 호스트를 건드리지 못하게 하는 방향으로 설계되어 있고, 반대로 호스트에서 컨테이너를 들여다보는 길은 열려 있다. 별도 커널 없이 호스트 커널을 나눠 쓰는 프로세스에 namespace(가시성), cgroup(자원), capabilities(루트 권한 분할), seccomp(syscall 차단)를 씌운 것이라, 호스트에서
ps aux를 치면 모든 컨테이너의 프로세스가 한 목록에 나온다.docker inspect -f '{{.State.Pid}}' <container> # 호스트 PID sudo ls /proc/<PID>/root/ # 컨테이너 루트 파일시스템 sudo cat /proc/<PID>/environ | tr '\0' '\n' # 환경변수(시크릿) 평문 sudo ls -l /proc/<PID>/ns/ # 호스트와 namespace 비교
이것이 containerd 얘기인지 Docker에서도 되는지 한동안 헷갈렸는데,
/proc/<PID>/root는 그 프로세스의 mount namespace 기준 루트를 가리키는 커널의 심볼릭 링크라 런타임과 무관하게 동작한다. 호스트 root는 docker exec 없이도, 컨테이너에 셸이 없어도 내부를 읽을 수 있다.non-root 실행이 줄여 주는 것은 컨테이너에서 호스트로 향하는 피해뿐이다. 컨테이너에 굳이 별도 유저를 만드는 까닭을 나는 여기서야 알아차렸다. 컨테이너 안의 root는 기본적으로 호스트와 같은 UID 0이라 탈출하면 호스트 root지만, UID 1000이면 탈출해도 UID 1000이다. privileged 컨테이너나 잘못된 mount·capability 설정처럼 컨테이너가 호스트에 닿는 길이 아주 없는 것은 아니지만, 그것은 격리를 스스로 허문 경우다. 반대 방향, 곧 호스트에서 컨테이너를 들여다보는 것은 non-root로 막히지 않으므로 호스트 노드 감시는 따로 두어야 하는 셈이다.
securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false # setuid로 root 복귀 차단 readOnlyRootFilesystem: true capabilities: drop: ["ALL"]
실행 기반별 에이전트 배포 가능성
에이전트를 어디에 둘 수 있느냐는 오케스트레이터가 아니라 실행 기반이 정한다. 관리형 서비스는 운영 부담을 덜어 가는 만큼 호스트 수준의 가시성도 함께 걷어 가기 때문이다. 나는 Fargate에서 도는 컨테이너에 Falco를 올릴 생각이었는데, Fargate는 DaemonSet을 지원하지 않으며, VM 경계로 다른 파드의 호스트 자원 접근을 막기 때문에 파드가 자기 kubelet에도 닿지 못한다.
이 문장을 읽어 내기까지 모르는 말이 여럿이었다. 먼저 DaemonSet은 "모든 노드에 파드를 하나씩 깔아라"라는 배포 방식이다. 노드 하나에 하나만 있으면 그 노드 위의 모든 파드를 볼 수 있으니 보안·모니터링 도구가 즐겨 쓴다. 사이드카는 앱과 같은 파드에 보조 컨테이너를 하나 더 넣는 것이라, 파드마다 따로 붙여야 하고 제 파드만 본다. 이미지 임베드는 아예 앱 이미지 안에 에이전트를 넣는 것이라 앱을 빌드하는 팀의 협조가 있어야 하고 distroless 이미지에는 쓸 수 없다. 노드 하나를 맡기는 방식과 파드 하나씩 붙이는 방식의 차이가 여기서 갈린다.
혼합 클러스터는 한 EKS 클러스터 안에 EC2 노드그룹과 Fargate 프로필이 같이 있는 구성이다. 파드마다 어느 쪽에 얹힐지가 프로필로 정해지므로, 같은 클러스터라도 노드에 손댈 수 있는 파드와 없는 파드가 섞이게 된다. CNAPP은 이미지 스캔, 설정 점검, 런타임 탐지를 한 회사가 묶어 파는 상용 제품군을 부르는 이름으로, 오픈소스 조합으로 하려던 일을 돈 주고 한 번에 사는 셈이다.
이 말들을 다 풀고 나니 결론은 하나로 모였다. 혼합 클러스터라면 EC2 노드에는 DaemonSet으로, Fargate에는 이미지 임베드나 사이드카로 나누어 배포하는 수밖에 없다. 오픈소스만의 궁색함인가 싶어 상용 CNAPP 쪽 문서를 찾아보았는데, 거기서도 안내는 같았다. Fargate에서는 DaemonSet이 안 되니 이미지에 넣거나 사이드카로 붙이고, EC2 노드에만 DaemonSet을 쓰라는 것이다. 돈 주고 사는 프로그램도 실행 기반이 닫아 둔 문을 열지는 못했다.
Fargate에서 DaemonSet이 안 되는 까닭도 앞서 본 구조에서 나온다. 파드당 노드가 1개라 "모든 노드에 하나씩"이라는 전제가 서지 않고, 커널이 AWS 소유라 eBPF 프로브도 올릴 수 없다. 조합별로 정리해 보자.
조합 | 노드 접근 | DaemonSet | Falco | GuardDuty Runtime |
EKS + EC2 노드그룹 | O | O | O | O |
EKS + Fargate | X | X | X | X |
ECS + EC2 | O | 해당 없음(호스트 설치) | O | O |
ECS + Fargate | X | X | X | O |
Wazuh 공식 디스커션에서도 ECS Fargate 컨테이너에 에이전트를 넣던 사용자가 이미지 비대와 배포 지연을 호소했고, Wazuh 측은 awslogs 드라이버로 CloudWatch에 보낸 뒤 Wazuh가 CloudWatch를 모니터링하는 방식을 권했다. AWS 자체 솔루션인 GuardDuty 런타임 모니터링도 ECS Fargate까지는 덮지만 EKS Fargate는 덮지 못한다. 커널 레벨 eBPF 에이전트를 DaemonSet 애드온으로 배포해 컨테이너 탈출, 리버스 셸, 악성 프로세스 실행을 탐지하며 EKS(EC2)·ECS(Fargate 포함)·EC2를 덮지만, EKS on Fargate와 EKS 하이브리드 노드는 지원하지 않는다.
결국 관리형 서비스가 걷어 가는 것은 운영 관리 표면만이 아니다. OS 패치, kubelet 관리, 호스트 침해 위험은 사라지지만 컨테이너 내부 침해 위험은 그대로 남되 볼 방법이 없어지므로, 포렌식 증적이 요건인 환경에서는 Fargate를 고르기 어렵다. 관리 부담을 줄이는 선택이 곧 가시성을 줄이는 선택이었고, 그 값을 치를 수 있는지가 기준이 되었다.
Falco의 필요성
컨테이너 내부의 syscall 가시성은 Falco에 맡길 수 있다. Falco는 Wazuh와 겹치니 굳이 쓸 필요가 없다고 지레짐작하고 있었는데, 오히려 빈칸을 정확히 메우는 도구였다. GitLab의 비교 평가에 따르면 Falco는 컨테이너 OS의 syscall을 볼 수 있어 내부 프로세스에 프로브가 동작하는 반면, Wazuh의 프로브 대부분은 에이전트를 깔 수 없는 컨테이너 내부 OS에 적용되지 않으며, Wazuh의 컨테이너 런타임 지원은 Python 클라이언트를 통한 Docker뿐이다. EKS는 containerd를 쓰므로 Wazuh의 Docker listener가 동작하지 않는다. auditd도 syscall을 보지만 "PID 1235가
/etc/shadow를 열었다"까지이고, Falco는 같은 이벤트에 컨테이너·이미지·네임스페이스·파드 이름을 붙여 준다.Falco 역시 SIEM이 아니므로 알림의 저장과 상관분석은 Wazuh에 맡길 수 있다. JSON 파일로 출력하게 하고 노드의 Wazuh agent가
<localfile>로 읽게 하는 구성이 가장 단순하다. 이때 Wazuh agent는 파드보다 노드 OS에 두는 편이 맞아 보인다. 파드로 띄우면 재생성마다 새 에이전트로 등록되기 때문에 Wazuh 블로그는 StatefulSet과 퍼시스턴트 볼륨으로 등록 키와 호스트명을 유지하라고 권하는데, 애초에 쿠버네티스 바깥 레이어를 보는 도구이기도 하다.etcd와 시크릿
etcd 읽기 권한은 전체 시크릿 탈취와 같다. 쿠버네티스 Secret은 base64 인코딩만 되어 저장되고, 쓰기 권한이 Pod 생성 권한과 결합되면 다른 Secret이나 권한 높은 ServiceAccount에 접근할 수 있다. 방어는 etcd 포트(2379/2380) 비노출, 클라이언트 인증서, encryption at rest, 데이터 파일 FIM으로 할 수 있다. 컨테이너 탈출이 치명적인 까닭이 여기서 이어진다. 노드를 잡으면 그 위 모든 파드의
/proc/*/environ을 훑을 수 있고, 컨트롤 플레인 노드라면 etcd 데이터까지 손에 들어온다.결론
처음의 물음은 "Velociraptor를 컨테이너에 어떻게 넣지"였다. 끝에 남은 물음은 "내가 관찰해야 할 실행 경계는 어디인가"였다.
컨테이너는 격리 장치를 씌운 호스트 프로세스이고, 호스트에서는 그 안을 내려다볼 수 있다. 그래서 컨테이너마다 에이전트를 넣는 것보다 노드에 에이전트를 두고, 노드에서 그 위의 컨테이너를 관찰하는 쪽이 자연스러웠다. Wazuh는 노드의 로그와 이벤트를 모아 중앙에서 판단하고, Falco는 노드에서 컨테이너의 syscall을 바라보며, Velociraptor는 필요한 엔드포인트를 직접 조사하는 식으로 역할을 나눌 수 있었다.
그러고 나니 에이전트를 어디에 넣을지는 Kubernetes의 문제가 아니라 실행 기반의 문제가 되었다. EC2 노드그룹이라면 호스트에 손댈 수 있으므로 DaemonSet과 호스트 에이전트를 사용할 수 있지만, Fargate에서는 그 경계가 AWS가 관리하는 영역으로 넘어간다. 관리 부담을 덜어 주는 대신 내가 관찰할 수 있는 범위도 줄어드는 셈이다.
결국 처음 계획에서 바뀐 것은 에이전트의 설치 위치만이 아니었다. "어느 컨테이너에 넣을까"라는 질문 자체를 버리고, "어느 실행 경계를 내가 관찰하고 통제할 수 있는가"를 먼저 묻는 쪽으로 생각이 바뀌었다. 이번 여정에서 얻은 답은 특정 도구 하나가 아니라, 보안 에이전트의 위치를 정할 때는 컨테이너가 아니라 내가 접근할 수 있는 실행 경계부터 봐야 한다는 것이었다.
