SIEM/Log Management Triển khai kiến trúc Wazuh phân tán: Cấu hình Cluster, HAProxy Load Balancer, Vector Log Pipeline và OpenSearch

1 . Phần lý thuyết tổng quan​

Trước khi đi vào các bước cấu hình kỹ thuật, ta cần nắm vững một số khái niệm cốt lõi trong mô hình triển khai thực tế này:

1784023552218.png


Giải thích luồng hoạt động của hệ thống theo sơ đồ kiến trúc:

  1. Tự động hóa triển khai (Ansible + AD): Thay vì cài đặt thủ công, mình sử dụng các công cụ tự động hóa như Ansible hoặc Group Policy Object (GPO) của Active Directory để đẩy gói cài đặt và cấu hình Wazuh Agent xuống hàng loạt máy trạm (Windows/Linux) cùng một lúc.
  2. Thu thập dữ liệu (Agents): Các máy trạm sau khi được cài Agent sẽ liên tục theo dõi hệ thống. Điểm đặc biệt là thay vì gửi log trực tiếp đến một máy chủ Wazuh cụ thể, chúng được cấu hình để báo cáo (report) về một địa chỉ trung gian duy nhất: IP của HAProxy.
  3. Cân bằng tải (HAProxy VIP): HAProxy đóng vai trò như một "trạm phân luồng giao thông". Nó tiếp nhận toàn bộ kết nối từ Agent và chia đều tải (load balancing) xuống các node Worker bên dưới. Nếu một Worker chết, HAProxy sẽ tự động nắn luồng dữ liệu sang Worker còn lại để đảm bảo không rớt mạng (Failover).
  4. Xử lý sự kiện (Wazuh Cluster - Master/Worker):
    • Worker-01 & Worker-02: Đây là lực lượng "công nhân" chuyên cày cuốc. Chúng nhận log từ HAProxy, thực hiện giải mã (decode) và đối chiếu với các luật an ninh (rules) để phát hiện bất thường.
    • Master: Là "tổng tư lệnh". Nó không trực tiếp nhận log từ Agent mà chuyên làm nhiệm vụ cấp phép cho Agent mới (enrollment) và quản lý, đồng bộ cấu hình (sync cluster) xuống cho các Worker.
  5. Định tuyến & Chuẩn hóa (Vector): Sau khi Wazuh xử lý xong, các cảnh báo được ghi ra file alerts.json. Công cụ Vector sẽ đọc file này, trích xuất dữ liệu, chuẩn hóa về định dạng quốc tế ECS, và chia luồng (route) - ví dụ log Windows đi đường Windows, log Linux đi đường Linux.
  6. Lưu trữ & Truy vấn (OpenSearch Cluster): Điểm đến cuối cùng. Dữ liệu từ Vector được tống vào OpenSearch để lập chỉ mục (indexing). Tại đây, mình có thể truy cập OpenSearch Dashboards để tìm kiếm log siêu tốc và vẽ các biểu đồ giám sát trực quan.

1. Lý thuyết Wazuh Cluster​

Khi triển khai quy mô lớn với hàng ngàn Agent, một máy chủ Wazuh Manager đơn lẻ sẽ bị quá tải, gây ra hiện tượng rớt log hoặc tắc nghẽn. Để giải quyết, mình sử dụng kiến trúc Wazuh Cluster theo mô hình Master - Worker:

  • Node Master: Đóng vai trò là máy điều phối trung tâm. Nó chịu trách nhiệm chính trong việc cấp phép (enrollment) cho các Agent mới, quản lý các cấu hình tập trung, tập luật (rules), bộ giải mã (decoders) và đồng bộ tất cả những dữ liệu này xuống các node Worker. Thường chỉ có 1 node Master trong Cluster.
  • Node Worker: Là những "công nhân" thực thụ. Nhiệm vụ chính của chúng là nhận log từ các Agent, giải mã, phân tích và xử lý luồng dữ liệu khổng lồ. Hệ thống có thể mở rộng (scale-out) bằng cách thêm nhiều node Worker tùy theo lượng Agent. Việc chạy Cluster mang lại lợi ích to lớn: Tính sẵn sàng cao (High Availability)Cân bằng tải (Load Balancing). Nếu một Worker chết, hệ thống vẫn hoạt động bình thường qua các Worker khác.

2. Lý thuyết về Vector​

Theo truyền thống, Wazuh thường dùng Filebeat để đọc file log cảnh báo (alerts.json) và đẩy thẳng lên OpenSearch. Tuy nhiên, kiến trúc của mình thay thế Filebeat bằng Vector (do DataDog phát triển). Vector là một công cụ định tuyến log (observability pipeline) cực kỳ nhẹ, viết bằng Rust, sở hữu tốc độ xử lý và khả năng tiết kiệm tài nguyên vượt trội. Sau khi cài đặt Vector, mình sử dụng nó vì những khả năng tùy biến mạnh mẽ sau:

  • Chuẩn hóa dữ liệu (Normalization): Vector có thể can thiệp vào từng dòng log của Wazuh, map các trường dữ liệu rời rạc về chung một chuẩn quốc tế gọi là ECS (Elastic Common Schema), giúp việc truy vấn đồng nhất dù đó là log của Windows hay Linux.
  • Định tuyến thông minh (Routing): Thay vì tống tất cả mọi thứ vào chung một Index wazuh-alerts-* lộn xộn, Vector có thể phân loại log ngay từ đầu nguồn. Log hệ thống Windows sẽ được chia vào index riêng, log đăng nhập Linux vào index riêng, v.v., giúp tiết kiệm dung lượng lưu trữ và tối ưu tốc độ tìm kiếm trên OpenSearch.
Để hiểu rõ hơn Vector có thể đọc :https://github.com/nhuthangl24/PopTech/blob/main/Vector.md

2 . Cấu hình Wazuh Cluster (Master - Worker)​

Để đảm bảo tính sẵn sàng cao (High Availability) và khả năng cân bằng tải cho hệ thống Wazuh, mình sẽ thiết lập Wazuh Cluster bao gồm 1 node Master và nhiều node Worker. Đầu tiên, mình cần tạo một chuỗi khóa (Cluster Key) ngẫu nhiên để các node trong Cluster có thể xác thực với nhau. Trên node Master, chạy lệnh sau:

openssl rand -hex 16

Kết quả trả về sẽ là một chuỗi ngẫu nhiên (ví dụ: 69d9ead4db70d100b4949c882b398c2b), hãy lưu lại chuỗi này để cấu hình cho các node.

1784023588673.png


Tiếp theo, chỉnh sửa file cấu hình của Wazuh trên node Master bằng lệnh nano:

nano /var/ossec/etc/ossec.conf
Tìm đến phần <cluster> và chỉnh sửa lại cấu hình với node_type là master, và nhập chuỗi key vừa tạo ở bước trên:

1784023607105.png


Ý nghĩa từng trường dữ liệu :
  • <name>: Tên được gán cho cụm (cluster). Đây là tên định danh cho toàn bộ cluster.
  • <node_name>: Tên của node hiện tại. Mỗi node trong cluster cần có tên riêng.
  • <key>: Một khóa duy nhất dài 32 ký tự, phải giống nhau trên tất cả các node trong cluster. Khóa bạn vừa tạo
  • <node_type>: Loại node, có thể là master (quản lý đồng bộ) hoặc worker (xử lý log từ agent).
  • <port>: Cổng mà node sử dụng để giao tiếp trong cluster.
  • <bind_addr>: Địa chỉ IP mà node lắng nghe kết nối. Có thể dùng 0.0.0.0 để lắng nghe tất cả các IP.
  • <node>: Địa chỉ của master node bên trong khối <nodes>;. Phải được khai báo trên tất cả các node, kể cả master node. Địa chỉ có thể là IP hoặc DNS.
  • <hidden>: Bật/tắt việc hiển thị thông tin về cluster khi có alert được tạo ra.
  • <disabled>: Chỉ định node có được kích hoạt trong cluster hay không

Đối với các node Worker, mình cũng cấu hình tương tự trong file /var/ossec/etc/ossec.conf. Điểm khác biệt là node_name phải đặt khác nhau cho mỗi worker, node_type là worker, và cần trỏ địa chỉ IP của node master (192.168.75.150) trong phần <nodes>.

Lưu ý: Cấu hình trên cả 2 máy worker (worker-01, worker-02) đều giống hệt nhau, chỉ đổi tên node.

1784023865248.png


Sau khi khởi động lại dịch vụ Wazuh Manager trên tất cả các node, mình có thể kiểm tra trạng thái của Cluster bằng lệnh:

sudo /var/ossec/bin/cluster_control -l
Kết quả sẽ hiển thị danh sách các node đang tham gia vào Cluster, bao gồm node master và các node worker.

1784023858575.png


3. Thiết lập cân bằng tải (Load Balancer) với HAProxy​

Để các Agent có thể gửi log về hệ thống một cách hiệu quả và có khả năng chịu lỗi (failover), mình sẽ sử dụng HAProxy làm Load Balancer.

Cài đặt HAProxy trên máy chủ Load Balancer (trong bài lab này giả sử IP là 192.168.75.160):

sudo apt update
sudo apt install -y haproxy

Tiếp theo, mở file cấu hình của HAProxy:

sudo nano /etc/haproxy/haproxy.cfg
Cấu hình HAProxy để cân bằng tải cho 2 cổng chính của Wazuh:
  • Cổng 1515 (Registration): Trỏ thẳng về node Master (192.168.75.150) vì chỉ có Master mới có quyền cấp phép agent mới.
  • Cổng 1514 (Cluster - Agent connection): Cân bằng tải (thuật toán leastconn) đến các node Worker (worker-01 và worker-02) để xử lý log gửi về.
1784026961487.png


File cấu hình HaProxy : https://github.com/nhuthangl24/PopTech/blob/main/haproxy.cfg

Sau khi cấu hình xong, tiến hành kiểm tra lỗi cú pháp của file cấu hình HAProxy:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg

1784026996093.png


Configuation file is Valid tức là không có lỗi nào

Khởi động và kích hoạt dịch vụ HAProxy để nó chạy cùng hệ thống:

sudo systemctl restart haproxy
sudo systemctl enable haproxy
sudo systemctl status haproxy

Giám sát trạng thái qua HAProxy Statistics: Sau khi cấu hình, mình có thể truy cập vào giao diện thống kê của HAProxy (thường qua port 5555 như đã cấu hình) để theo dõi trực quan hệ thống. Giao diện này cung cấp bức tranh toàn cảnh về các luồng dữ liệu:
  • wazuh_registration_backend: Trỏ về node master, trạng thái màu xanh (UP) với check L4OK cho thấy kết nối (Layer 4) ổn định, đảm bảo việc cấp phép cho agent mới không bị gián đoạn.
  • wazuh_cluster_backend: Hiển thị trạng thái của các node Worker (worker-01, worker-02). Cả hai đều đang màu xanh (UP) và sẵn sàng chia sẻ tải để nhận log từ Agent gửi về.
1784023951228.png



Cấu hình HAProxy Helper trên Wazuh Cluster: Để Wazuh Cluster có thể chủ động điều phối lại kết nối các Agent đang bị "dính" vào một node quá tải hoặc bị lỗi, buộc chúng phải phân bổ lại thông qua HAProxy, mình cần bổ sung block cấu hình <haproxy_helper>; vào file /var/ossec/etc/ossec.conf (nằm bên trong block<cluster>) trên các node Wazuh:

1784023963922.png

Ý nghĩa từng trường dữ liệu :

<haproxy_helper>: Khối cấu hình các tham số tích hợp với HAProxy.
<haproxy_disabled>: Bật hoặc tắt tính năng HAProxy Helper (Giá trị 'no' là đang bật).
<haproxy_address>: Địa chỉ IP của máy chủ HAProxy đóng vai trò Load Balancer.
<haproxy_user>: Tên tài khoản để đăng nhập vào giao diện API/thống kê của HAProxy.
<haproxy_password>: Mật khẩu xác thực tương ứng với tài khoản HAProxy.
<haproxy_port>: Cổng dịch vụ mà API/thống kê của HAProxy đang lắng nghe (VD: 5555).
<haproxy_protocol>: Giao thức dùng để giao tiếp với HAProxy (http hoặc https).
<haproxy_backend>: Tên của backend pool chứa các Wazuh node được cấu hình trên HAProxy.
<frequency>: Chu kỳ (giây) mà Wazuh báo cáo trạng thái các agent lên HAProxy.
<agent_chunk_size>: Số lượng agent tối đa được gom vào một gói tin khi báo cáo trạng thái để giảm tải đường truyền.
<agent_reconnection_time>: Khoảng thời gian (giây) theo dõi khi agent đang cố gắng kết nối lại.
<agent_reconnection_stability_time>: Thời gian (giây) bắt buộc kết nối phải duy trì để được công nhận là đã kết nối ổn định hoàn toàn.
<imbalance_tolerance>: Ngưỡng dung sai cho phép mức độ chênh lệch tải (số agent) giữa các node trong cụm (0.1 = 10%).
<remove_disconnected_node_after>: Thời gian (giây) chờ trước khi tự động gạt bỏ một node mất kết nối ra khỏi thuật toán tính tải.
</haproxy_helper>: Thẻ đóng khối cấu hình HAProxy.

Giải thích: Cấu hình này cho phép Wazuh Manager chủ động "nói chuyện" với trang thống kê HAProxy qua port 5555 bằng tài khoản haproxy_admin. Nó sẽ liên tục đọc trạng thái của backend wazuh_cluster_backend. Khi phát hiện mức độ mất cân bằng giữa các node vượt mức cho phép (imbalance_tolerance &gt; 0.1), Wazuh sẽ ép một lượng Agent ngắt kết nối (mỗi đợt 100 agents - agent_chunk_size) và kết nối lại, qua đó giúp HAProxy tái phân bổ lại luồng log đồng đều giữa các node Worker.

Bây giờ, khi triển khai Agent lên các máy trạm (ví dụ máy Kali Linux), mình chỉ cần trỏ địa chỉ Server về IP của Load Balancer (HAProxy - 192.168.75.160) thay vì trỏ trực tiếp vào một node Wazuh cụ thể:

1784024011849.png


Kiểm tra tính năng Failover (Chịu lỗi) và Phục hồi: Tính năng cân bằng tải của HAProxy thể hiện sức mạnh rõ nhất khi có sự cố xảy ra.

  • Kịch bản sự cố (Failover): Giả sử node worker-01 gặp trục trặc (bị tắt dịch vụ hoặc đứt mạng), HAProxy sẽ ngay lập tức phát hiện thông qua cơ chế health check. Trên trang thống kê, dòng worker-01 sẽ chuyển sang màu đỏ (DOWN, L4CON). Mặc dù vậy, toàn bộ log từ các Agent sẽ được HAProxy tự động nắn luồng (route) dồn sang node worker-02 (vẫn đang màu xanh UP). Hệ thống SIEM của mình sẽ không bị rớt bất kỳ một log nào (Zero downtime).
1784024020121.png


  • Kịch bản phục hồi (Recovery): Ngay khi sự cố trên worker-01 được khắc phục (dịch vụ Wazuh chạy lại bình thường), HAProxy qua health check sẽ thấy cổng mở lại và tự động chuyển trạng thái của worker-01 về màu xanh (UP, L4OK). Lúc này, HAProxy lại tiếp tục thuật toán cân bằng tải để chia đều kết nối cho cả hai worker như trạng thái ban đầu.
1784024024530.png


4. Cấu hình Vector để xử lý và phân loại Log​

Trong kiến trúc của mình, Wazuh Manager đẩy cảnh báo (alerts) ra file JSON. Mình sử dụng Vector để đọc file này, phân tích cú pháp (parse), chuẩn hóa (normalize) theo chuẩn ECS và sau đó định tuyến (route) log đẩy sang các Index riêng biệt trên OpenSearch tùy theo hệ điều hành hoặc loại log.

Bước 1: Cài đặt Vector trên máy chủ Việc cài đặt Vector cực kỳ đơn giản nhờ script cài đặt tự động mà Datadog cung cấp. Chạy lệnh sau để tải và cài đặt Vector:

bash -c "$(curl -L https://setup.vector.dev)"
sudo apt install vector

1784024244641.png


Sau khi quá trình cài đặt hoàn tất, mình có thể kiểm tra xem Vector đã được cài đặt đúng phiên bản hay chưa:

vector --version

1784024260435.png


Bước 2: Cấu hình Vector pipeline (vector.yaml) Vector sử dụng file cấu hình vector.yaml để định nghĩa luồng xử lý dữ liệu (pipeline) bao gồm 3 thành phần chính: Sources (đầu vào), Transforms (xử lý trung gian) và Sinks (đầu ra).

Để tải file cấu hình chuẩn đã được viết sẵn cho bài lab này, hãy chạy lệnh sau:


Hoặc dùng trình soạn thảo nano để tự chỉnh sửa:

nano /etc/vector/vector.yaml
Dưới đây là hình ảnh minh họa một phần bên trong file cấu hình vector.yaml mà mình vừa tải về:

1784024310580.png


File cấu hình Vector: https://github.com/nhuthangl24/PopTech/blob/main/vector.yml

Chi tiết các bước xử lý trong Vector:
  1. Source: wazuh_alerts đọc liên tục từ cuối file /var/ossec/logs/alerts/alerts.json.
  2. Transform (Parse & Normalize):
    • Vector đọc chuỗi JSON.
    • Chuẩn hóa các trường dùng chung (host, ip, event) theo định dạng ECS (Elastic Common Schema).
    • Tách log của Wazuh sinh ra ra một namespace riêng .wazuh.* để không bị trùng lặp.
    • Quan trọng: Vector lưu lại thuộc tính id gốc của Wazuh thành .wazuh.alert_id. Giá trị này sẽ được tái sử dụng làm Document ID (_id) trên OpenSearch để chống trùng lặp dữ liệu (deduplication) khi xảy ra lỗi mạng phải đẩy lại log.
  3. Route by OS: Dựa vào trường .wazuh.decoder.name, Vector chia nhánh luồng dữ liệu. Nếu decoder là windows_eventchannel, luồng sẽ được đẩy sang xử lý chuẩn hóa Windows (normalize_windows), nếu không thì đẩy sang luồng Linux (normalize_linux).
  4. Normalize (Windows/Linux):
    • Trên nhánh Windows: Parse chi tiết các trường EventID, Channel, Provider... Nếu channel là Security thì đánh nhãn Index là logs-windows-security, v.v...
    • Trên nhánh Linux: Parse iptables, FIM (File Integrity Monitoring), Auth (sshd, sudo). Ví dụ log sshd được gán nhãn Index là logs-linux-auth.
  5. Sink (Đẩy lên OpenSearch): Cuối cùng, Vector đẩy dữ liệu lên OpenSearch, nhóm theo index đã được đánh nhãn (ví dụ: logs-windows-security-2026.07.14).
Kiểm tra tính hợp lệ của cấu hình Vector: Trước khi chạy dịch vụ, mình cần kiểm tra xem file cấu hình vector.yaml có bị lỗi cú pháp hay không bằng lệnh vector validate. Lưu ý, vì trong cấu hình có sử dụng biến môi trường ${OPENSEARCH_PASSWORD} nên mình phải export biến này trước khi validate:

export OPENSEARCH_PASSWORD='<Mật_khẩu_của_bạn>'
vector validate vector.yaml
Nếu trả về kết quả màu xanh Validated như trong hình dưới là cấu hình đã chuẩn.

1784024342784.png


Phân quyền để Vector đọc được log của Wazuh: Sau khi cài đặt Vector, nó hoạt động dưới user vector. Tuy nhiên file log của Wazuh (/var/ossec/logs/alerts/alerts.json) lại thuộc quyền của user wazuh. Do đó, mình cần thêm user vector vào group wazuh để nó có quyền đọc file log:

sudo usermod -aG wazuh vector
Kiểm tra xem Vector đã có thể đọc log thành công chưa bằng lệnh sau:

sudo -u vector tail -n 1 /var/ossec/logs/alerts/alerts.json >/dev/null && echo READ_OK
Nếu kết quả trả về "READ_OK" thì việc phân quyền đã hoàn tất.

Để Vector có thể tự động tạo ra các Index trên OpenSearch, mình cần có sẵn dữ liệu log (nếu không có log, OpenSearch sẽ không tự sinh ra index). Mình có thể tạo một log giả mạo để kích hoạt việc này:

1784024372099.png


Kiểm tra các Index đã được tạo trên OpenSearch: Thay vì lên giao diện Web ngay, mình có thể gọi API trực tiếp vào OpenSearch (port 9200) bằng lệnh curl để kiểm tra nhanh xem dữ liệu đã được ghi vào hay chưa:

Như trong ảnh dưới, mình có thể thấy các Index được Vector tự động định tuyến và tạo ra theo đúng tên hệ điều hành và loại log (được đóng khung đỏ) như: logs-linux-auth-2026.07.14, logs-windows-security-2026.07.14, logs-linux-journal-2026.07.14... với trạng thái (health) là "yellow" hoặc "green", chứng tỏ dữ liệu đã được ghi thành công.

1784024388387.png


5. Quản lý Index và Index Pattern trên OpenSearch Dashboards​

Sau khi Vector đẩy thành công dữ liệu phân loại lên OpenSearch, mình cần khai báo Index Pattern để giao diện Discover có thể hiển thị và truy vấn log. Các bước thực hiện như sau:

Bước 1: Truy cập menu Dashboards Management Từ giao diện chính của OpenSearch, mình click vào biểu tượng menu (hamburger menu) ở góc trên cùng bên trái. Kéo xuống phần Management, mình chọn Dashboards Management:

1784025808592.png


Tiếp theo, ở menu điều hướng bên trái của trang quản lý Dashboards, mình click chọn mục Index patterns:

1784025816932.png


Bước 2: Bắt đầu tạo Pattern Tại màn hình Index patterns (lúc này có thể đang trống), mình bấm vào nút + Create index pattern màu xanh ở góc trên bên phải:

1784025821228.png


Bước 3: Khai báo tên Pattern Ở bước này, mình nhập tiền tố của các index muốn gộp vào ô cấu hình tên pattern, ví dụ: security-auditlog-*. Giải thích: Dấu * ở cuối có tác dụng gộp chung tất cả các index sinh ra theo từng ngày thành một dải dữ liệu thống nhất để thuận tiện cho việc tìm kiếm.

1784025828273.png


Sau khi nhập xong tên pattern, mình bấm Next step. Ở màn hình tiếp theo, mình cần chọn trường thời gian (thường là @timestamp hoặc timestamp) từ menu thả xuống rồi bấm Create index pattern để hoàn tất:

1784025858305.png


Bước 4: Kiểm tra kết quả trên Discover Sau khi hoàn tất việc tạo Index Pattern, mình chuyển sang tab Discover để xem thành quả. Nhìn vào bức ảnh dưới đây, mình có thể thấy rõ các khu vực quan trọng chứng minh hệ thống đang hoạt động hoàn hảo:
  1. Menu thả xuống (Góc trên bên trái): Hiển thị danh sách các Index Pattern đã tạo. Tại đây, mình có thể dễ dàng click chọn bất kỳ pattern nào (ví dụ trong ảnh đang chọn logs-linux-auth-*) để theo dõi riêng luồng log tương ứng.
  2. Danh sách các trường dữ liệu (Cột bên trái): OpenSearch đã đọc và nhận diện thành công toàn bộ các trường dữ liệu (fields). Việc các trường như agent.id, data.command... được bóc tách rõ ràng type chứng tỏ Vector đã làm cực kỳ tốt nhiệm vụ parse và normalize dữ liệu trước khi ném vào OpenSearch.
  3. Khung log chi tiết (Bên phải): Hiển thị chính xác các sự kiện log thực tế đang đổ về. Mình có thể thấy rõ các trường dữ liệu đều đã được Vector chuẩn hóa và tách lọc cực kỳ chi tiết, vuông vức. Điều này giúp cho việc truy vấn, lọc dữ liệu (Filter) hay vẽ biểu đồ (Dashboard) sau này trở nên cực kỳ dễ dàng và chuyên nghiệp.
1784026593802.png
 

Đính kèm

  • 1784023845043.png
    1784023845043.png
    89 KB · Lượt xem: 0
  • 1784024396003.png
    1784024396003.png
    335.9 KB · Lượt xem: 0
  • 1784024403407.png
    1784024403407.png
    71.7 KB · Lượt xem: 0
  • 1784024710161.png
    1784024710161.png
    335.9 KB · Lượt xem: 0
  • 1784024770021.png
    1784024770021.png
    335.9 KB · Lượt xem: 0
Back
Top