| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- #!/usr/bin/env bash
- set -euo pipefail
- PROFILE="${1:-mixed}"
- MODELS="${2:-all}"
- BASE_DELAY="${3:-0.8}"
- WAZUH_SYSLOG_HOST="${WAZUH_SYSLOG_HOST:-127.0.0.1}"
- WAZUH_SYSLOG_PORT="${WAZUH_SYSLOG_PORT:-514}"
- SIM_MAX_EVENTS="${SIM_MAX_EVENTS:-0}" # 0 = run forever
- SIM_SRC_PREFIX="${SIM_SRC_PREFIX:-10.10.20}"
- SIM_VPN_USER="${SIM_VPN_USER:-remote.user}"
- SIM_ADMIN_USER="${SIM_ADMIN_USER:-admin}"
- EVENT_COUNT=0
- if ! [[ "${BASE_DELAY}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
- echo "error: base delay must be numeric (e.g. 0.8)"
- exit 1
- fi
- if ! [[ "${SIM_MAX_EVENTS}" =~ ^[0-9]+$ ]]; then
- echo "error: SIM_MAX_EVENTS must be an integer >= 0"
- exit 1
- fi
- emit_syslog() {
- local msg="$1"
- local sent="false"
- if command -v nc >/dev/null 2>&1; then
- if printf "%s\n" "${msg}" | nc -u -w1 "${WAZUH_SYSLOG_HOST}" "${WAZUH_SYSLOG_PORT}"; then
- sent="true"
- fi
- fi
- if [[ "${sent}" != "true" ]]; then
- if printf "%s\n" "${msg}" >"/dev/udp/${WAZUH_SYSLOG_HOST}/${WAZUH_SYSLOG_PORT}" 2>/dev/null; then
- sent="true"
- fi
- fi
- if [[ "${sent}" != "true" ]]; then
- echo "error: failed to send syslog event to ${WAZUH_SYSLOG_HOST}:${WAZUH_SYSLOG_PORT}/udp"
- return 1
- fi
- EVENT_COUNT=$((EVENT_COUNT + 1))
- echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] sent(${EVENT_COUNT}): ${msg}"
- }
- pick_model() {
- case "${MODELS}" in
- 501E|501e) echo "501E" ;;
- 80F|80f) echo "80F" ;;
- 60F|60f) echo "60F" ;;
- 40F|40f) echo "40F" ;;
- all)
- case $((RANDOM % 4)) in
- 0) echo "501E" ;;
- 1) echo "80F" ;;
- 2) echo "60F" ;;
- *) echo "40F" ;;
- esac
- ;;
- *)
- echo "error: unknown models '${MODELS}'"
- echo "valid: 501E | 80F | 60F | 40F | all"
- exit 1
- ;;
- esac
- }
- devname_for_model() {
- case "$1" in
- 501E) echo "FGT501E-HQ" ;;
- 80F) echo "FGT80F-Branch01" ;;
- 60F) echo "FGT60F-Store12" ;;
- 40F) echo "FGT40F-SMB01" ;;
- *) echo "FGT-UNKNOWN" ;;
- esac
- }
- devid_for_model() {
- case "$1" in
- 501E) echo "FGT501E12345678" ;;
- 80F) echo "FGT80FTK20000001" ;;
- 60F) echo "FGT60FTK21001234" ;;
- 40F) echo "FGT40FTK22004567" ;;
- *) echo "FGT00000000000000" ;;
- esac
- }
- src_ip() {
- echo "${SIM_SRC_PREFIX}.$((RANDOM % 220 + 10))"
- }
- event_ts() {
- echo "$(date +%s)"
- }
- syslog_prefix() {
- local pri="$1"
- local model="$2"
- local devname
- local devid
- devname="$(devname_for_model "${model}")"
- devid="$(devid_for_model "${model}")"
- printf "<%s>date=%s time=%s devname=\"%s\" devid=\"%s\" eventtime=%s vd=\"root\"" \
- "${pri}" "$(date '+%Y-%m-%d')" "$(date '+%H:%M:%S')" "${devname}" "${devid}" "$(event_ts)"
- }
- send_traffic_allow() {
- local model="$1"
- local sip
- sip="$(src_ip)"
- emit_syslog "$(syslog_prefix 189 "${model}") logid=\"0000000013\" type=\"traffic\" subtype=\"forward\" level=\"notice\" srcip=${sip} srcport=$((RANDOM % 40000 + 1024)) srcintf=\"lan\" dstip=172.217.14.$((RANDOM % 200 + 20)) dstport=443 dstintf=\"wan1\" proto=6 service=\"HTTPS\" policyid=12 action=\"accept\" sentbyte=$((RANDOM % 4000 + 500)) rcvdbyte=$((RANDOM % 20000 + 1000)) duration=$((RANDOM % 60 + 2)) soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_traffic_allow severity=low"
- }
- send_webfilter_block() {
- local model="$1"
- local sip
- sip="$(src_ip)"
- emit_syslog "$(syslog_prefix 190 "${model}") logid=\"0419016384\" type=\"utm\" subtype=\"webfilter\" level=\"warning\" srcip=${sip} srcintf=\"internal\" dstip=104.18.$((RANDOM % 200 + 1)).$((RANDOM % 240 + 10)) dstport=80 hostname=\"suspicious-$((RANDOM % 9000 + 1000)).example\" url=\"/dropper\" action=\"blocked\" cat=52 profile=\"WF-Strict\" policyid=3 soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_webfilter_block severity=medium"
- }
- send_ips_block() {
- local model="$1"
- local sip
- sip="$(src_ip)"
- emit_syslog "$(syslog_prefix 190 "${model}") logid=\"0720018432\" type=\"utm\" subtype=\"ips\" level=\"alert\" srcip=${sip} dstip=45.155.$((RANDOM % 200 + 1)).$((RANDOM % 240 + 10)) srcport=$((RANDOM % 40000 + 1024)) dstport=445 sessionid=$((RANDOM % 9000000 + 1000000)) action=\"blocked\" attack=\"MS.SMB.Server.Service.Remote.Code.Execution\" severity=\"critical\" policyid=8 soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_ips_block severity=high"
- }
- send_vpn_down() {
- local model="$1"
- emit_syslog "$(syslog_prefix 191 "${model}") logid=\"0104044546\" type=\"event\" subtype=\"vpn\" level=\"warning\" user=\"${SIM_VPN_USER}\" tunneltype=\"ipsec\" remip=203.0.113.$((RANDOM % 200 + 10)) locip=198.51.100.$((RANDOM % 200 + 10)) action=\"tunnel-down\" reason=\"peer-not-responding\" msg=\"IPsec tunnel Branch-HQ is down\" soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_vpn_down severity=high"
- }
- send_admin_login() {
- local model="$1"
- emit_syslog "$(syslog_prefix 191 "${model}") logid=\"0100032002\" type=\"event\" subtype=\"system\" level=\"information\" logdesc=\"Admin login successful\" user=\"${SIM_ADMIN_USER}\" ui=\"https(10.20.1.$((RANDOM % 200 + 10)))\" msg=\"Administrator ${SIM_ADMIN_USER} logged in successfully\" soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_admin_login severity=low"
- }
- send_event_by_profile() {
- local model="$1"
- local roll
- roll=$((RANDOM % 100))
- case "${PROFILE}" in
- normal)
- if [[ "${roll}" -lt 80 ]]; then
- send_traffic_allow "${model}"
- elif [[ "${roll}" -lt 92 ]]; then
- send_admin_login "${model}"
- elif [[ "${roll}" -lt 98 ]]; then
- send_webfilter_block "${model}"
- else
- send_vpn_down "${model}"
- fi
- ;;
- incident)
- if [[ "${roll}" -lt 45 ]]; then
- send_traffic_allow "${model}"
- elif [[ "${roll}" -lt 70 ]]; then
- send_webfilter_block "${model}"
- elif [[ "${roll}" -lt 92 ]]; then
- send_ips_block "${model}"
- else
- send_vpn_down "${model}"
- fi
- ;;
- mixed)
- if [[ "${roll}" -lt 65 ]]; then
- send_traffic_allow "${model}"
- elif [[ "${roll}" -lt 82 ]]; then
- send_webfilter_block "${model}"
- elif [[ "${roll}" -lt 94 ]]; then
- send_admin_login "${model}"
- elif [[ "${roll}" -lt 98 ]]; then
- send_vpn_down "${model}"
- else
- send_ips_block "${model}"
- fi
- ;;
- *)
- echo "error: unknown profile '${PROFILE}'"
- echo "valid: normal | incident | mixed"
- exit 1
- ;;
- esac
- }
- echo "starting FortiGate continuous simulator"
- echo "profile=${PROFILE} models=${MODELS} base_delay=${BASE_DELAY}s target=${WAZUH_SYSLOG_HOST}:${WAZUH_SYSLOG_PORT}/udp max_events=${SIM_MAX_EVENTS}"
- echo "press Ctrl+C to stop"
- trap 'echo; echo "stopped after ${EVENT_COUNT} events"; exit 0' INT TERM
- while true; do
- send_event_by_profile "$(pick_model)"
- if [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]]; then
- echo "done: reached SIM_MAX_EVENTS=${SIM_MAX_EVENTS}"
- exit 0
- fi
- # occasional burst to mimic short real-world spikes
- if [[ $((RANDOM % 100)) -lt 12 ]]; then
- send_event_by_profile "$(pick_model)"
- [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]] && exit 0
- send_event_by_profile "$(pick_model)"
- [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]] && exit 0
- fi
- sleep "${BASE_DELAY}"
- done
|