Zion Boggan zionboggan.com ↗

stand up wazuh single-node stack (indexer/manager/dashboard) + thehive

7a0131f   Zion Boggan committed on Mar 10, 2026 (3 months ago)
.env.example +14 -0
@@ -0,0 +1,14 @@
+INDEXER_USERNAME=admin
+INDEXER_PASSWORD=changeme-indexer
+DASHBOARD_USERNAME=kibanaserver
+DASHBOARD_PASSWORD=changeme-dashboard
+WAZUH_API_USER=wazuh-wui
+WAZUH_API_PASSWORD=changeme-api
+
+THEHIVE_SECRET=replace-with-64-char-random-string
+THEHIVE_PORT=9000
+
+CORTEX_PORT=9001
+
+SHUFFLE_WEBHOOK_URL=http://shuffle-backend:5001/api/v1/hooks/webhook_REPLACE
+THEHIVE_API_KEY=replace-with-thehive-api-key
.gitignore +10 -0
@@ -0,0 +1,10 @@
+.env
+config/wazuh/certs/
+*.pem
+*.key
+*.crt
+data/
+*.log
+.DS_Store
+__pycache__/
+*.pyc
config/wazuh/certs.yml +10 -0
@@ -0,0 +1,10 @@
+nodes:
+ indexer:
+ - name: wazuh-indexer
+ ip: wazuh-indexer
+ server:
+ - name: wazuh-manager
+ ip: wazuh-manager
+ dashboard:
+ - name: wazuh-dashboard
+ ip: wazuh-dashboard
config/wazuh/indexer/wazuh.indexer.yml +31 -0
@@ -0,0 +1,31 @@
+network.host: "0.0.0.0"
+node.name: "wazuh-indexer"
+cluster.initial_master_nodes:
+ - "wazuh-indexer"
+cluster.name: "wazuh-cluster"
+discovery.seed_hosts:
+ - "wazuh-indexer"
+node.max_local_storage_nodes: "3"
+path.data: /var/lib/wazuh-indexer
+path.logs: /var/log/wazuh-indexer
+
+plugins.security.ssl.http.pemcert_filepath: /usr/share/wazuh-indexer/certs/wazuh-indexer.pem
+plugins.security.ssl.http.pemkey_filepath: /usr/share/wazuh-indexer/certs/wazuh-indexer-key.pem
+plugins.security.ssl.http.pemtrustedcas_filepath: /usr/share/wazuh-indexer/certs/root-ca.pem
+plugins.security.ssl.transport.pemcert_filepath: /usr/share/wazuh-indexer/certs/wazuh-indexer.pem
+plugins.security.ssl.transport.pemkey_filepath: /usr/share/wazuh-indexer/certs/wazuh-indexer-key.pem
+plugins.security.ssl.transport.pemtrustedcas_filepath: /usr/share/wazuh-indexer/certs/root-ca.pem
+plugins.security.ssl.http.enabled: true
+plugins.security.ssl.transport.enforce_hostname_verification: false
+plugins.security.ssl.transport.resolve_hostname: false
+
+plugins.security.authcz.admin_dn:
+ - "CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US"
+plugins.security.nodes_dn:
+ - "CN=wazuh-indexer,OU=Wazuh,O=Wazuh,L=California,C=US"
+plugins.security.restapi.roles_enabled:
+ - "all_access"
+ - "security_rest_api_access"
+plugins.security.allow_default_init_securityindex: true
+plugins.security.check_snapshot_restore_write_privileges: true
+plugins.security.enable_snapshot_restore_privilege: true
config/wazuh/manager/ossec.conf +67 -0
@@ -0,0 +1,67 @@
+<ossec_config>
+ <global>
+ <jsonout_output>yes</jsonout_output>
+ <alerts_log>yes</alerts_log>
+ <logall>no</logall>
+ <logall_json>no</logall_json>
+ <email_notification>no</email_notification>
+ </global>
+
+ <alerts>
+ <log_alert_level>3</log_alert_level>
+ </alerts>
+
+ <remote>
+ <connection>secure</connection>
+ <port>1514</port>
+ <protocol>tcp</protocol>
+ <queue_size>131072</queue_size>
+ </remote>
+
+ <auth>
+ <disabled>no</disabled>
+ <port>1515</port>
+ <use_source_ip>no</use_source_ip>
+ <force>
+ <enabled>yes</enabled>
+ <after_registration_time>1h</after_registration_time>
+ </force>
+ <purge>yes</purge>
+ <use_password>no</use_password>
+ </auth>
+
+ <ruleset>
+ <decoder_dir>ruleset/decoders</decoder_dir>
+ <rule_dir>ruleset/rules</rule_dir>
+ <decoder_dir>etc/decoders</decoder_dir>
+ <rule_dir>etc/rules</rule_dir>
+ <list>etc/lists/cti-malicious-ip</list>
+ <list>etc/lists/cti-malicious-domain</list>
+ <list>etc/lists/cti-malware-hash</list>
+ </ruleset>
+
+ <integration>
+ <name>custom-thehive</name>
+ <hook_url>SET_FROM_ENV_SHUFFLE_WEBHOOK_URL</hook_url>
+ <level>10</level>
+ <alert_format>json</alert_format>
+ </integration>
+
+ <command>
+ <name>firewall-drop</name>
+ <executable>firewall-drop</executable>
+ <timeout_allowed>yes</timeout_allowed>
+ </command>
+
+ <active-response>
+ <command>firewall-drop</command>
+ <location>local</location>
+ <rules_id>100210</rules_id>
+ <timeout>600</timeout>
+ </active-response>
+
+ <vulnerability-detection>
+ <enabled>yes</enabled>
+ <index-status>yes</index-status>
+ </vulnerability-detection>
+</ossec_config>
docker-compose.yml +148 -0
@@ -0,0 +1,148 @@
+name: soc-automation-lab
+
+services:
+ wazuh-indexer:
+ image: wazuh/wazuh-indexer:4.9.0
+ hostname: wazuh-indexer
+ restart: always
+ ports:
+ - "9200:9200"
+ environment:
+ - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
+ - bootstrap.memory_lock=true
+ ulimits:
+ memlock:
+ soft: -1
+ hard: -1
+ nofile:
+ soft: 65536
+ hard: 65536
+ volumes:
+ - indexer-data:/var/lib/wazuh-indexer
+ - ./config/wazuh/certs/wazuh-indexer.pem:/usr/share/wazuh-indexer/certs/wazuh-indexer.pem
+ - ./config/wazuh/certs/wazuh-indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh-indexer-key.pem
+ - ./config/wazuh/certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
+ - ./config/wazuh/certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem
+ - ./config/wazuh/certs/admin-key.pem:/usr/share/wazuh-indexer/certs/admin-key.pem
+ - ./config/wazuh/indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
+
+ wazuh-manager:
+ image: wazuh/wazuh-manager:4.9.0
+ hostname: wazuh-manager
+ restart: always
+ depends_on:
+ - wazuh-indexer
+ ports:
+ - "1514:1514"
+ - "1515:1515"
+ - "514:514/udp"
+ - "55000:55000"
+ environment:
+ - INDEXER_URL=https://wazuh-indexer:9200
+ - INDEXER_USERNAME=${INDEXER_USERNAME}
+ - INDEXER_PASSWORD=${INDEXER_PASSWORD}
+ - API_USERNAME=${WAZUH_API_USER}
+ - API_PASSWORD=${WAZUH_API_PASSWORD}
+ volumes:
+ - manager-config:/var/ossec/etc
+ - manager-logs:/var/ossec/logs
+ - ./config/wazuh/manager/ossec.conf:/wazuh-config-mount/etc/ossec.conf
+ - ./config/wazuh/rules/local_rules.xml:/var/ossec/etc/rules/local_rules.xml
+ - ./config/wazuh/decoders/local_decoder.xml:/var/ossec/etc/decoders/local_decoder.xml
+ - ./integrations/custom-thehive.py:/var/ossec/integrations/custom-thehive.py
+ - ./integrations/custom-thehive:/var/ossec/integrations/custom-thehive
+ - ./config/wazuh/lists/cti-malicious-ip:/var/ossec/etc/lists/cti-malicious-ip
+ - ./config/wazuh/lists/cti-malicious-domain:/var/ossec/etc/lists/cti-malicious-domain
+ - ./config/wazuh/lists/cti-malware-hash:/var/ossec/etc/lists/cti-malware-hash
+ - ./config/wazuh/certs/root-ca.pem:/usr/share/wazuh/certs/root-ca.pem
+
+ wazuh-dashboard:
+ image: wazuh/wazuh-dashboard:4.9.0
+ hostname: wazuh-dashboard
+ restart: always
+ depends_on:
+ - wazuh-indexer
+ ports:
+ - "5601:5601"
+ environment:
+ - INDEXER_USERNAME=${INDEXER_USERNAME}
+ - INDEXER_PASSWORD=${INDEXER_PASSWORD}
+ - DASHBOARD_USERNAME=${DASHBOARD_USERNAME}
+ - DASHBOARD_PASSWORD=${DASHBOARD_PASSWORD}
+ volumes:
+ - ./config/wazuh/certs/wazuh-dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
+ - ./config/wazuh/certs/wazuh-dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
+ - ./config/wazuh/certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
+
+ cassandra:
+ image: cassandra:4.1
+ hostname: cassandra
+ restart: always
+ environment:
+ - MAX_HEAP_SIZE=1024M
+ - HEAP_NEWSIZE=1024M
+ - CASSANDRA_CLUSTER_NAME=thehive
+ volumes:
+ - cassandra-data:/var/lib/cassandra
+
+ elasticsearch:
+ image: docker.elastic.co/elasticsearch/elasticsearch:7.17.20
+ hostname: elasticsearch
+ restart: always
+ environment:
+ - discovery.type=single-node
+ - xpack.security.enabled=false
+ - "ES_JAVA_OPTS=-Xms1g -Xmx1g"
+ ulimits:
+ memlock:
+ soft: -1
+ hard: -1
+ volumes:
+ - es-data:/usr/share/elasticsearch/data
+
+ cortex:
+ image: thehiveproject/cortex:3.1.8
+ hostname: cortex
+ restart: always
+ depends_on:
+ - elasticsearch
+ ports:
+ - "${CORTEX_PORT}:9001"
+ environment:
+ - job_directory=/tmp/cortex-jobs
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - cortex-jobs:/tmp/cortex-jobs
+
+ thehive:
+ image: strangebee/thehive:5.4.0-1
+ hostname: thehive
+ restart: always
+ depends_on:
+ - cassandra
+ - elasticsearch
+ - cortex
+ ports:
+ - "${THEHIVE_PORT}:9000"
+ command:
+ - --secret
+ - ${THEHIVE_SECRET}
+ - "--cql-hostnames"
+ - cassandra
+ - "--index-backend"
+ - elasticsearch
+ - "--es-hostnames"
+ - elasticsearch
+ - "--cortex-hostnames"
+ - cortex
+ volumes:
+ - thehive-data:/opt/thp/thehive/data
+
+volumes:
+ indexer-data:
+ manager-config:
+ manager-logs:
+ cassandra-data:
+ es-data:
+ cortex-jobs:
+ thehive-data:
scripts/deploy.sh +60 -0
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+CERT_DIR="${ROOT}/config/wazuh/certs"
+
+cd "${ROOT}"
+
+if [[ ! -f .env ]]; then
+ echo "no .env found - copy .env.example to .env and fill it in first" >&2
+ exit 1
+fi
+
+generate_certs() {
+ if [[ -f "${CERT_DIR}/root-ca.pem" ]]; then
+ echo "certs already present, skipping generation"
+ return
+ fi
+ echo "generating indexer certificates"
+ mkdir -p "${CERT_DIR}"
+ docker run --rm \
+ -v "${CERT_DIR}:/certs" \
+ -v "${ROOT}/config/wazuh/certs.yml:/config/certs.yml:ro" \
+ wazuh/wazuh-certs-generator:0.0.2
+ chmod 640 "${CERT_DIR}"/*.pem
+}
+
+wait_for_indexer() {
+ echo "waiting for the indexer to come up"
+ for _ in $(seq 1 40); do
+ if curl -sk -u "${INDEXER_USERNAME}:${INDEXER_PASSWORD}" \
+ https://localhost:9200/_cluster/health | grep -q '"status"'; then
+ echo "indexer is responding"
+ return 0
+ fi
+ sleep 10
+ done
+ echo "indexer did not become ready in time" >&2
+ return 1
+}
+
+set -a
+source .env
+set +a
+
+generate_certs
+docker compose up -d
+wait_for_indexer
+
+cat <<EOF
+
+stack is up.
+
+ Wazuh dashboard https://localhost:5601 (${DASHBOARD_USERNAME})
+ TheHive http://localhost:${THEHIVE_PORT}
+ Cortex http://localhost:${CORTEX_PORT}
+
+Next: bring up Shuffle (cd shuffle && docker compose up -d), import the
+workflow, and paste the webhook URL into config/wazuh/manager/ossec.conf.
+EOF