From 399b97243a11d8f5bd1d555c3f5f812eaeedbc68 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Thu, 14 Aug 2025 12:04:46 -0400 Subject: [PATCH 1/7] feat(integrations): add suricata integration --- agent/config/const.go | 4 +- filters/nids/nids-ids.conf | 355 --------------------------- filters/suricata/suricata.conf | 378 +++++++++++++++++++++++++++++ log-auth-proxy/config/constants.go | 1 + 4 files changed, 382 insertions(+), 356 deletions(-) delete mode 100644 filters/nids/nids-ids.conf create mode 100644 filters/suricata/suricata.conf diff --git a/agent/config/const.go b/agent/config/const.go index 550e41197..de0a06abf 100644 --- a/agent/config/const.go +++ b/agent/config/const.go @@ -85,6 +85,7 @@ var ( DataTypeAix DataType = "ibm_aix" DataTypePfsense DataType = "firewall_pfsense" DataTypeFortiweb DataType = "firewall_fortiweb" + DataTypeSuricata DataType = "suricata" ProtoPorts = map[DataType]ProtoPort{ DataTypeSyslog: {UDP: "7014", TCP: "7014"}, @@ -102,6 +103,7 @@ var ( DataTypeAix: {UDP: "7016", TCP: "7016"}, DataTypePfsense: {UDP: "7017", TCP: "7017"}, DataTypeFortiweb: {UDP: "7018", TCP: "7018"}, + DataTypeSuricata: {UDP: "7019", TCP: "7019"}, DataTypeNetflow: {UDP: "2055", TCP: ""}, } @@ -116,7 +118,7 @@ func ValidateModuleType(typ string) string { switch DataType(typ) { case DataTypeSyslog, DataTypeVmware, DataTypeEset, DataTypeKaspersky, DataTypeFortinet, DataTypePaloalto, DataTypeMikrotik, DataTypeSophosXG, DataTypeSonicwall, DataTypeSentinelOne, DataTypeCiscoGeneric, - DataTypeDeceptivebytes, DataTypeAix, DataTypePfsense, DataTypeFortiweb: + DataTypeDeceptivebytes, DataTypeAix, DataTypePfsense, DataTypeFortiweb, DataTypeSuricata: return "syslog" case DataTypeNetflow: return "netflow" diff --git a/filters/nids/nids-ids.conf b/filters/nids/nids-ids.conf deleted file mode 100644 index 5e4d55c8e..000000000 --- a/filters/nids/nids-ids.conf +++ /dev/null @@ -1,355 +0,0 @@ -filter { - -# NIDS filter version 1.3.2 -# Based on https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html (latest 7.0.0) (april 2022) -# and real events log provided -# Support json format - - if [path] and [path] == "/var/log/suricata/eve.json" { - if [message] { - json { source => "message" } - } - if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" - or [event_type] == "alert" or [event_type] == "dns" or [event_type] == "ssh" - or [event_type] == "http" or [event_type] == "ftp" or [event_type] == "ftp_data" - or [event_type] == "tftp" or [event_type] == "smb" or [event_type] == "initial_request" - or [event_type] == "initial_response" or [event_type] == "connect_request" or [event_type] == "connect_response" - or [event_type] == "tls_handshake" or [rdp] or [event_type] == "rfb" or [event_type] == "mqtt" - or [event_type] == "http2" or [event_type] == "pgsql" or [event_type] == "ike" - or [event_type] == "ikev1" or [event_type] == "ikev2" - or [event_type] == "modbus" or [event_type] == "quic" or [event_type] == "snmp" - or [event_type] == "fileinfo" or [event_type] == "sip" or [event_type] == "dhcp") { - mutate { - rename => ["dest_ip", "[logx][nids][dest_ip]"] - rename => ["dest_port", "[logx][nids][dest_port]"] - rename => ["flow_id", "[logx][nids][flow_id]"] - rename => ["host", "[logx][nids][host]"] - rename => ["in_iface", "[logx][nids][in_iface]"] - rename => ["proto", "[logx][nids][proto]"] - rename => ["src_ip", "[logx][nids][src_ip]"] - rename => ["src_port", "[logx][nids][src_port]"] - rename => ["tx_id", "[logx][nids][tx_id]"] - } - } - mutate { - add_field => { - "dataType" => "nids" - } - add_field => { - "dataSource" => "nids" - } - remove_field => [ "timestamp", "type", "path"] - rename => ["event_type", "[logx][nids][event_type]"] - } - if [tls] { - mutate { - rename => ["tls", "[logx][nids][tls]"] - } - } - if [ssh] { - mutate { - rename => ["ssh", "[logx][nids][ssh]"] - } - } - if [stats] { - mutate { - rename => ["stats", "[logx][nids][stats]"] - } - } - if [flow] { - mutate { - rename => ["flow", "[logx][nids][flow]"] - } - } - if [tcp] { - mutate { - rename => ["tcp", "[logx][nids][tcp]"] - } - } - if [dns] { - mutate { - rename => ["dns", "[logx][nids][dns]"] - } - } - if [app_proto] { - mutate { - rename => ["app_proto", "[logx][nids][app_proto]"] - } - } - if [anomaly] { - mutate { - rename => ["anomaly", "[logx][nids][anomaly]"] - } - } - if [alert] { - mutate { - rename => ["alert", "[logx][nids][alert]"] - } - if [logx][nids][alert][severity] { - if [logx][nids][alert][severity] >= 3 { - mutate { - add_field => { - "[logx][nids][severity_label]" => "Low" - "[logx][nids][severity]" => 1 - } - } - } - if [logx][nids][alert][severity] == 2 { - mutate { - add_field => { - "[logx][nids][severity_label]" => "Medium" - "[logx][nids][severity]" => 2 - } - } - } - if [logx][nids][alert][severity] == 1 { - mutate { - add_field => { - "[logx][nids][severity_label]" => "High" - "[logx][nids][severity]" => 3 - } - } - } - mutate { - remove_field => [ "[logx][nids][alert][severity]" ] - } - } - } -#....................................................................... -# Add new event_types to logx structure, detected in real logs, present in suricata 7.0.0 - - mutate { - rename => ["http", "[logx][nids][http]"] - rename => ["ftp", "[logx][nids][ftp]"] - rename => ["ftp_data", "[logx][nids][ftp_data]"] - rename => ["tftp", "[logx][nids][tftp]"] - rename => ["smb", "[logx][nids][smb]"] - - # RDP event_type - rename => ["rdp", "[logx][nids][rdp]"] - # End RDP event_type - - rename => ["rfb", "[logx][nids][rfb]"] - rename => ["mqtt", "[logx][nids][mqtt]"] - rename => ["http2", "[logx][nids][http2]"] - rename => ["pgsql", "[logx][nids][pgsql]"] - rename => ["ike", "[logx][nids][ike]"] - rename => ["ikev1", "[logx][nids][ike]"] - rename => ["ikev2", "[logx][nids][ike]"] - rename => ["modbus", "[logx][nids][modbus]"] - rename => ["quic", "[logx][nids][quic]"] - - # New fields from real logs, not present in suricata docs - rename => ["snmp", "[logx][nids][snmp]"] - rename => ["fileinfo", "[logx][nids][fileinfo]"] - rename => ["sip", "[logx][nids][sip]"] - rename => ["dhcp", "[logx][nids][dhcp]"] - - # This field isnt an event_type but appear in alert real logs - rename => ["files", "[logx][nids][alert][files]"] - } - -#....................................................................... -# Add fields to logx structure, detected outside th event_type, present in suricata 7.0.0 - if [logx][nids][event_type] == "mqtt" { - mutate { - rename => ["pcap_cnt", "[logx][nids][mqtt][pcap_cnt]"] - } - } else if [logx][nids][event_type] == "pgsql" { - mutate { - rename => ["pcap_cnt", "[logx][nids][pgsql][pcap_cnt]"] - } - } else if [logx][nids][event_type] == "fileinfo" { - if [logx][nids][http] { - mutate { - rename => ["fileinfo", "[logx][nids][http][fileinfo]"] - } - } else if [logx][nids][http2] { - mutate { - rename => ["fileinfo", "[logx][nids][http2][fileinfo]"] - } - } else { - mutate { - rename => ["fileinfo", "[logx][nids][fileinfo]"] - } - } - } else if [logx][nids][event_type] == "anomaly" { - mutate { - rename => ["pcap_cnt", "[logx][nids][anomaly][pcap_cnt]"] - rename => ["packet", "[logx][nids][anomaly][packet]"] - rename => ["packet_info", "[logx][nids][anomaly][packet_info]"] - } - } else if [logx][nids][event_type] == "flow" { - mutate { - rename => ["icmp_type", "[logx][nids][flow][icmp_type]"] - rename => ["icmp_code", "[logx][nids][flow][icmp_code]"] - rename => ["response_icmp_code", "[logx][nids][flow][response_icmp_code]"] - rename => ["response_icmp_type", "[logx][nids][flow][response_icmp_type]"] - } - } - -#....................................................................... -# Implementing logx.utm.action field used for established connections - if [logx][nids][event_type] == "tls" { - if ![logx][nids][tls][session_resumed] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "dns" { - if [logx][nids][dns][type] and [logx][nids][dns][type] == "answer" { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "flow" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][flow][bytes_toserver] and [logx][nids][flow][bytes_toserver] > 0) and - ([logx][nids][flow][bytes_toclient] and [logx][nids][flow][bytes_toclient] > 0) { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "ssh" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - [logx][nids][ssh][server] and [logx][nids][ssh][client] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "alert" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and [logx][nids][alert][action] == "allowed" and - ([logx][nids][flow][bytes_toserver] and [logx][nids][flow][bytes_toserver] > 0) and - ([logx][nids][flow][bytes_toclient] and [logx][nids][flow][bytes_toclient] > 0) { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "http" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and [logx][nids][http][status] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "ftp" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and [logx][nids][ftp][completion_code] { - ruby { - code => " - event.get('[logx][nids][ftp][completion_code]').each_with_index do |value,key| - if value =~ /(2\d\d)|(125)/ - event.set('[logx][utm][action]', 'Success') - end - end - " - } - } - } else if [logx][nids][event_type] == "tftp" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][tftp][packet] and [logx][nids][tftp][packet] != "error") { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "smb" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ( [logx][nids][smb][command] and "NEGOTIATE" in [logx][nids][smb][command] ) and - [logx][nids][smb][status] and ( "SUCCESS" in [logx][nids][smb][status] or - "GRANTED" in [logx][nids][smb][status] or "CONNECTED" in [logx][nids][smb][status]) { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "rdp" - or ([logx][nids][rdp][event_type] and ([logx][nids][rdp][event_type] == "initial_response" - or [logx][nids][rdp][event_type] == "initial_request" or [logx][nids][rdp][event_type] == "connect_request" - or [logx][nids][rdp][event_type] == "connect_response" or [logx][nids][rdp][event_type] == "tls_handshake") ) { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][rdp][event_type] == "connect_response" or [logx][nids][rdp][event_type] == "tls_handshake") { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "rfb" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][rfb][authentication][security-result] and [logx][nids][rfb][authentication][security-result] == "OK") { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "mqtt" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][mqtt][connack][return_code] and - ([logx][nids][mqtt][connack][return_code] == 0 or [logx][nids][mqtt][connack][return_code] == "0x00")) { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "http2" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][http2][response][headers] ) { - ruby { - code => " - event.get('[logx][nids][http2][response][headers]').each_with_index do |value,key| - if (value['name'] == 'status' or value['name'] == ':status') - event.set('[logx][utm][action]', 'Success') - end - end - " - } - } - } else if [logx][nids][event_type] == "pgsql" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][pgsql][request][simple_query] or [logx][nids][pgsql][response][command_completed] or - ([logx][nids][pgsql][response][ssl_accepted] and [logx][nids][pgsql][response][ssl_accepted] == "true") or - ([logx][nids][pgsql][response][accepted] and [logx][nids][pgsql][response][accepted] == "true") or - [logx][nids][pgsql][response][authentication_md5_password] ) { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "ike" or [logx][nids][event_type] == "ikev1" or [logx][nids][event_type] == "ikev2" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "modbus" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "sip" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "quic" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "fileinfo" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "snmp" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } else if [logx][nids][event_type] == "dhcp" { - if [logx][nids][src_ip] and [logx][nids][dest_ip] and - ([logx][nids][dhcp][assigned_ip] and [logx][nids][dhcp][assigned_ip] != "0") { - mutate { - add_field => { "[logx][utm][action]" => "Success" } - } - } - } - } -} diff --git a/filters/suricata/suricata.conf b/filters/suricata/suricata.conf new file mode 100644 index 000000000..7e0138004 --- /dev/null +++ b/filters/suricata/suricata.conf @@ -0,0 +1,378 @@ +filter { + +# Suricata filter version 1.0.0 +# Based on https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html (latest 8.0.0) (august 2025) +# and real events log provided +# Support json format + split { + field => "message" + terminator => "" + } + + #Looking for datasource generated by an agent and parse original message + if [message]=~/\[utm_stack_agent_ds=(.+)\]-(.+)/ { + grok { + match => { + "message" => [ "\[utm_stack_agent_ds=%{DATA:dataSource}\]-%{GREEDYDATA:original_log_message}" ] + } + } + } + if [original_log_message] { + mutate { + update => { "message" => "%{[original_log_message]}" } + } + } + + if ![dataType] { + if [path] and [path] == "/var/log/suricata/eve.json" { + if [message] { + json { source => "message" } + } + if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" + or [event_type] == "alert" or [event_type] == "dns" or [event_type] == "ssh" + or [event_type] == "http" or [event_type] == "ftp" or [event_type] == "ftp_data" + or [event_type] == "tftp" or [event_type] == "smb" or [event_type] == "initial_request" + or [event_type] == "initial_response" or [event_type] == "connect_request" or [event_type] == "connect_response" + or [event_type] == "tls_handshake" or [rdp] or [event_type] == "rfb" or [event_type] == "mqtt" + or [event_type] == "http2" or [event_type] == "pgsql" or [event_type] == "ike" + or [event_type] == "ikev1" or [event_type] == "ikev2" + or [event_type] == "modbus" or [event_type] == "quic" or [event_type] == "snmp" + or [event_type] == "fileinfo" or [event_type] == "sip" or [event_type] == "dhcp") { + mutate { + rename => ["dest_ip", "[logx][suricata][dest_ip]"] + rename => ["dest_port", "[logx][suricata][dest_port]"] + rename => ["flow_id", "[logx][suricata][flow_id]"] + rename => ["host", "[logx][suricata][host]"] + rename => ["in_iface", "[logx][suricata][in_iface]"] + rename => ["proto", "[logx][suricata][proto]"] + rename => ["src_ip", "[logx][suricata][src_ip]"] + rename => ["src_port", "[logx][suricata][src_port]"] + rename => ["tx_id", "[logx][suricata][tx_id]"] + } + } + if (![dataSource]){ + mutate { + add_field => { "dataSource" => "%{host}" } + } + } + + mutate { + add_field => { + "dataType" => "suricata" + } + remove_field => [ "timestamp", "type", "path"] + rename => ["event_type", "[logx][suricata][event_type]"] + } + if [tls] { + mutate { + rename => ["tls", "[logx][suricata][tls]"] + } + } + if [ssh] { + mutate { + rename => ["ssh", "[logx][suricata][ssh]"] + } + } + if [stats] { + mutate { + rename => ["stats", "[logx][suricata][stats]"] + } + } + if [flow] { + mutate { + rename => ["flow", "[logx][suricata][flow]"] + } + } + if [tcp] { + mutate { + rename => ["tcp", "[logx][suricata][tcp]"] + } + } + if [dns] { + mutate { + rename => ["dns", "[logx][suricata][dns]"] + } + } + if [app_proto] { + mutate { + rename => ["app_proto", "[logx][suricata][app_proto]"] + } + } + if [anomaly] { + mutate { + rename => ["anomaly", "[logx][suricata][anomaly]"] + } + } + if [alert] { + mutate { + rename => ["alert", "[logx][suricata][alert]"] + } + if [logx][suricata][alert][severity] { + if [logx][suricata][alert][severity] >= 3 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "Low" + "[logx][suricata][severity]" => 1 + } + } + } + if [logx][suricata][alert][severity] == 2 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "Medium" + "[logx][suricata][severity]" => 2 + } + } + } + if [logx][suricata][alert][severity] == 1 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "High" + "[logx][suricata][severity]" => 3 + } + } + } + mutate { + remove_field => [ "[logx][suricata][alert][severity]" ] + } + } + } + #....................................................................... + # Add new event_types to logx structure, detected in real logs, present in suricata 7.0.0 + + mutate { + rename => ["http", "[logx][suricata][http]"] + rename => ["ftp", "[logx][suricata][ftp]"] + rename => ["ftp_data", "[logx][suricata][ftp_data]"] + rename => ["tftp", "[logx][suricata][tftp]"] + rename => ["smb", "[logx][suricata][smb]"] + + # RDP event_type + rename => ["rdp", "[logx][suricata][rdp]"] + # End RDP event_type + + rename => ["rfb", "[logx][suricata][rfb]"] + rename => ["mqtt", "[logx][suricata][mqtt]"] + rename => ["http2", "[logx][suricata][http2]"] + rename => ["pgsql", "[logx][suricata][pgsql]"] + rename => ["ike", "[logx][suricata][ike]"] + rename => ["ikev1", "[logx][suricata][ike]"] + rename => ["ikev2", "[logx][suricata][ike]"] + rename => ["modbus", "[logx][suricata][modbus]"] + rename => ["quic", "[logx][suricata][quic]"] + + # New fields from real logs, not present in suricata docs + rename => ["snmp", "[logx][suricata][snmp]"] + rename => ["fileinfo", "[logx][suricata][fileinfo]"] + rename => ["sip", "[logx][suricata][sip]"] + rename => ["dhcp", "[logx][suricata][dhcp]"] + + # This field isnt an event_type but appear in alert real logs + rename => ["files", "[logx][suricata][alert][files]"] + } + + #....................................................................... + # Add fields to logx structure, detected outside th event_type, present in suricata 7.0.0 + if [logx][suricata][event_type] == "mqtt" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][mqtt][pcap_cnt]"] + } + } else if [logx][suricata][event_type] == "pgsql" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][pgsql][pcap_cnt]"] + } + } else if [logx][suricata][event_type] == "fileinfo" { + if [logx][suricata][http] { + mutate { + rename => ["fileinfo", "[logx][suricata][http][fileinfo]"] + } + } else if [logx][suricata][http2] { + mutate { + rename => ["fileinfo", "[logx][suricata][http2][fileinfo]"] + } + } else { + mutate { + rename => ["fileinfo", "[logx][suricata][fileinfo]"] + } + } + } else if [logx][suricata][event_type] == "anomaly" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][anomaly][pcap_cnt]"] + rename => ["packet", "[logx][suricata][anomaly][packet]"] + rename => ["packet_info", "[logx][suricata][anomaly][packet_info]"] + } + } else if [logx][suricata][event_type] == "flow" { + mutate { + rename => ["icmp_type", "[logx][suricata][flow][icmp_type]"] + rename => ["icmp_code", "[logx][suricata][flow][icmp_code]"] + rename => ["response_icmp_code", "[logx][suricata][flow][response_icmp_code]"] + rename => ["response_icmp_type", "[logx][suricata][flow][response_icmp_type]"] + } + } + + #....................................................................... + # Implementing logx.utm.action field used for established connections + if [logx][suricata][event_type] == "tls" { + if ![logx][suricata][tls][session_resumed] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "dns" { + if [logx][suricata][dns][type] and [logx][suricata][dns][type] == "answer" { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "flow" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][flow][bytes_toserver] and [logx][suricata][flow][bytes_toserver] > 0) and + ([logx][suricata][flow][bytes_toclient] and [logx][suricata][flow][bytes_toclient] > 0) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ssh" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + [logx][suricata][ssh][server] and [logx][suricata][ssh][client] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "alert" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][alert][action] == "allowed" and + ([logx][suricata][flow][bytes_toserver] and [logx][suricata][flow][bytes_toserver] > 0) and + ([logx][suricata][flow][bytes_toclient] and [logx][suricata][flow][bytes_toclient] > 0) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "http" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][http][status] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ftp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][ftp][completion_code] { + ruby { + code => " + event.get('[logx][suricata][ftp][completion_code]').each_with_index do |value,key| + if value =~ /(2\d\d)|(125)/ + event.set('[logx][utm][action]', 'Success') + end + end + " + } + } + } else if [logx][suricata][event_type] == "tftp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][tftp][packet] and [logx][suricata][tftp][packet] != "error") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "smb" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ( [logx][suricata][smb][command] and "NEGOTIATE" in [logx][suricata][smb][command] ) and + [logx][suricata][smb][status] and ( "SUCCESS" in [logx][suricata][smb][status] or + "GRANTED" in [logx][suricata][smb][status] or "CONNECTED" in [logx][suricata][smb][status]) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "rdp" + or ([logx][suricata][rdp][event_type] and ([logx][suricata][rdp][event_type] == "initial_response" + or [logx][suricata][rdp][event_type] == "initial_request" or [logx][suricata][rdp][event_type] == "connect_request" + or [logx][suricata][rdp][event_type] == "connect_response" or [logx][suricata][rdp][event_type] == "tls_handshake") ) { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][rdp][event_type] == "connect_response" or [logx][suricata][rdp][event_type] == "tls_handshake") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "rfb" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][rfb][authentication][security-result] and [logx][suricata][rfb][authentication][security-result] == "OK") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "mqtt" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][mqtt][connack][return_code] and + ([logx][suricata][mqtt][connack][return_code] == 0 or [logx][suricata][mqtt][connack][return_code] == "0x00")) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "http2" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][http2][response][headers] ) { + ruby { + code => " + event.get('[logx][suricata][http2][response][headers]').each_with_index do |value,key| + if (value['name'] == 'status' or value['name'] == ':status') + event.set('[logx][utm][action]', 'Success') + end + end + " + } + } + } else if [logx][suricata][event_type] == "pgsql" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][pgsql][request][simple_query] or [logx][suricata][pgsql][response][command_completed] or + ([logx][suricata][pgsql][response][ssl_accepted] and [logx][suricata][pgsql][response][ssl_accepted] == "true") or + ([logx][suricata][pgsql][response][accepted] and [logx][suricata][pgsql][response][accepted] == "true") or + [logx][suricata][pgsql][response][authentication_md5_password] ) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ike" or [logx][suricata][event_type] == "ikev1" or [logx][suricata][event_type] == "ikev2" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "modbus" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "sip" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "quic" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "fileinfo" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "snmp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "dhcp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][dhcp][assigned_ip] and [logx][suricata][dhcp][assigned_ip] != "0") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } + } + } +} diff --git a/log-auth-proxy/config/constants.go b/log-auth-proxy/config/constants.go index 9cfde63c8..e254b8987 100644 --- a/log-auth-proxy/config/constants.go +++ b/log-auth-proxy/config/constants.go @@ -72,6 +72,7 @@ const ( AS400 = "as_400" FirewallPfsense = "firewall_pfsense" FirewallFortiweb = "firewall_fortiweb" + Suricata = "suricata" ) type ServiceStatus string From 0f2a049c0850e6b67fd3f3720303a03d35db3ee3 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 14 Aug 2025 11:12:20 -0500 Subject: [PATCH 2/7] feat: add support for SURICATA module in syslog integration --- .../guide-syslog/guide-syslog.component.ts | 3 +++ .../components/log-collector.component.ts | 3 +++ .../module-integration.component.html | 5 +++++ .../app-module/shared/enum/utm-module.enum.ts | 3 ++- .../src/assets/img/guides/logos/suricata.png | Bin 0 -> 44770 bytes 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 frontend/src/assets/img/guides/logos/suricata.png diff --git a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts index 896a0e12c..5a00ae6d8 100644 --- a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts +++ b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts @@ -72,6 +72,9 @@ export class GuideSyslogComponent implements OnInit { {module: UtmModulesEnum.AIX, port: '7016 TCP'}, {module: UtmModulesEnum.AIX, port: '7016 UDP'}, + + {module: UtmModulesEnum.SURICATA, port: '7019 TCP'}, + {module: UtmModulesEnum.SURICATA, port: '7019 UDP'}, ]; steps: Step[] = SYSLOGSTEPS; diff --git a/frontend/src/app/app-module/guides/shared/components/log-collector.component.ts b/frontend/src/app/app-module/guides/shared/components/log-collector.component.ts index 2294c1a47..3a6777a8b 100644 --- a/frontend/src/app/app-module/guides/shared/components/log-collector.component.ts +++ b/frontend/src/app/app-module/guides/shared/components/log-collector.component.ts @@ -170,6 +170,9 @@ export class LogCollectorComponent { case UtmModulesEnum.NETFLOW: return 'netflow'; + case UtmModulesEnum.SURICATA: + return 'suricata'; + case UtmModulesEnum.FIRE_POWER: case UtmModulesEnum.CISCO: case UtmModulesEnum.CISCO_SWITCH: diff --git a/frontend/src/app/app-module/module-integration/module-integration.component.html b/frontend/src/app/app-module/module-integration/module-integration.component.html index 8843ac63d..2ea24ef66 100644 --- a/frontend/src/app/app-module/module-integration/module-integration.component.html +++ b/frontend/src/app/app-module/module-integration/module-integration.component.html @@ -179,6 +179,11 @@ [guideName]="module.prettyName" [integrationId]="module.id"> + + diff --git a/frontend/src/app/app-module/shared/enum/utm-module.enum.ts b/frontend/src/app/app-module/shared/enum/utm-module.enum.ts index 8cfd4c664..a9e067e42 100644 --- a/frontend/src/app/app-module/shared/enum/utm-module.enum.ts +++ b/frontend/src/app/app-module/shared/enum/utm-module.enum.ts @@ -66,5 +66,6 @@ export enum UtmModulesEnum { SALESFORCE = 'SALESFORCE', BITDEFENDER = 'BITDEFENDER', AS_400 = 'AS_400', - SOC_AI = 'SOC_AI' + SOC_AI = 'SOC_AI', + SURICATA = 'SURICATA', } diff --git a/frontend/src/assets/img/guides/logos/suricata.png b/frontend/src/assets/img/guides/logos/suricata.png new file mode 100644 index 0000000000000000000000000000000000000000..c20f9dd2508e2f3e25daec8f42e6537b224bbe98 GIT binary patch literal 44770 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 D&lYWS literal 0 HcmV?d00001 From b9ec9bf80557e1d98843b5f131493699d528f79f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 14 Aug 2025 11:12:50 -0500 Subject: [PATCH 3/7] feat: add Suricata module integration and database procedures --- .../application_modules/enums/ModuleName.java | 3 +- .../factory/ModuleFactory.java | 7 +- .../factory/impl/ModuleSuricata.java | 43 ++++ .../changelog/20250814001_adding_suricata.xml | 204 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 5 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleSuricata.java create mode 100644 backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/enums/ModuleName.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/enums/ModuleName.java index 14f3266e8..de4779274 100644 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/enums/ModuleName.java +++ b/backend/src/main/java/com/park/utmstack/domain/application_modules/enums/ModuleName.java @@ -62,5 +62,6 @@ public enum ModuleName { SALESFORCE, BITDEFENDER, SOC_AI, - PFSENSE + PFSENSE, + SURICATA, } diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/ModuleFactory.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/ModuleFactory.java index c6d9e2399..41e58fd17 100644 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/ModuleFactory.java +++ b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/ModuleFactory.java @@ -67,6 +67,7 @@ public class ModuleFactory { private final ModulePfsense modulePfsense; private final ModuleFortiWeb moduleFortiWeb; private final ModuleAix moduleAix; + private final ModuleSuricata moduleSuricata; public ModuleFactory(ModuleFileIntegrity moduleFileIntegrity, @@ -129,7 +130,8 @@ public ModuleFactory(ModuleFileIntegrity moduleFileIntegrity, ModuleSocAi moduleSocAi, ModulePfsense modulePfsense, ModuleFortiWeb moduleFortiWeb, - ModuleAix moduleAix) { + ModuleAix moduleAix, + ModuleSuricata moduleSuricata) { this.moduleFileIntegrity = moduleFileIntegrity; this.moduleO365 = moduleO365; this.moduleAzure = moduleAzure; @@ -191,6 +193,7 @@ public ModuleFactory(ModuleFileIntegrity moduleFileIntegrity, this.modulePfsense = modulePfsense; this.moduleFortiWeb = moduleFortiWeb; this.moduleAix = moduleAix; + this.moduleSuricata = moduleSuricata; } public IModule getInstance(ModuleName nameShort) { @@ -316,6 +319,8 @@ public IModule getInstance(ModuleName nameShort) { return moduleFortiWeb; if (nameShort.equals(ModuleName.AIX)) return moduleAix; + if (nameShort.equals(ModuleName.SURICATA)) + return moduleSuricata; throw new RuntimeException("Unrecognized module " + nameShort.name()); } } diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleSuricata.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleSuricata.java new file mode 100644 index 000000000..8878eea50 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleSuricata.java @@ -0,0 +1,43 @@ +package com.park.utmstack.domain.application_modules.factory.impl; + +import com.park.utmstack.domain.application_modules.UtmModule; +import com.park.utmstack.domain.application_modules.enums.ModuleName; +import com.park.utmstack.domain.application_modules.factory.IModule; +import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; +import com.park.utmstack.domain.application_modules.types.ModuleRequirement; +import com.park.utmstack.service.application_modules.UtmModuleService; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Component +public class ModuleSuricata implements IModule { + private static final String CLASSNAME = "ModuleSuricata"; + + private final UtmModuleService moduleService; + + public ModuleSuricata(UtmModuleService moduleService) { + this.moduleService = moduleService; + } + + @Override + public UtmModule getDetails(Long serverId) throws Exception { + final String ctx = CLASSNAME + ".getDetails"; + try { + return moduleService.findByServerIdAndModuleName(serverId, ModuleName.SURICATA); + } catch (Exception e) { + throw new Exception(ctx + ": " + e.getMessage()); + } + } + + @Override + public List checkRequirements(Long serverId) throws Exception { + return Collections.emptyList(); + } + + @Override + public List getConfigurationKeys(Long groupId) throws Exception { + return Collections.emptyList(); + } +} diff --git a/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml new file mode 100644 index 000000000..6dd1bc93f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + do + $$ + begin + perform public.execute_register_integration_function(); + end; + $$ + language plpgsql; + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index ec2ce533d..72ec78fe6 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -107,5 +107,7 @@ + + From 8e71e5e6c57d07f3c30fcc1d78c2f30b0e3eb3a7 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 19 Aug 2025 11:03:44 -0500 Subject: [PATCH 4/7] feat(suricata): enhance logstash filter for Suricata event types and actions --- .../changelog/20250814001_adding_suricata.xml | 379 +++++++++++++++++- 1 file changed, 378 insertions(+), 1 deletion(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml index 6dd1bc93f..64c460c93 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml @@ -164,7 +164,384 @@ "message" + terminator => "" + } + + #Looking for datasource generated by an agent and parse original message + if [message]=~/\[utm_stack_agent_ds=(.+)\]-(.+)/ { + grok { + match => { + "message" => [ "\[utm_stack_agent_ds=%{DATA:dataSource}\]-%{GREEDYDATA:original_log_message}" ] + } + } + } + if [original_log_message] { + mutate { + update => { "message" => "%{[original_log_message]}" } + } + } + + if ![dataType] { + if [path] and [path] == "/var/log/suricata/eve.json" { + if [message] { + json { source => "message" } + } + if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" + or [event_type] == "alert" or [event_type] == "dns" or [event_type] == "ssh" + or [event_type] == "http" or [event_type] == "ftp" or [event_type] == "ftp_data" + or [event_type] == "tftp" or [event_type] == "smb" or [event_type] == "initial_request" + or [event_type] == "initial_response" or [event_type] == "connect_request" or [event_type] == "connect_response" + or [event_type] == "tls_handshake" or [rdp] or [event_type] == "rfb" or [event_type] == "mqtt" + or [event_type] == "http2" or [event_type] == "pgsql" or [event_type] == "ike" + or [event_type] == "ikev1" or [event_type] == "ikev2" + or [event_type] == "modbus" or [event_type] == "quic" or [event_type] == "snmp" + or [event_type] == "fileinfo" or [event_type] == "sip" or [event_type] == "dhcp") { + mutate { + rename => ["dest_ip", "[logx][suricata][dest_ip]"] + rename => ["dest_port", "[logx][suricata][dest_port]"] + rename => ["flow_id", "[logx][suricata][flow_id]"] + rename => ["host", "[logx][suricata][host]"] + rename => ["in_iface", "[logx][suricata][in_iface]"] + rename => ["proto", "[logx][suricata][proto]"] + rename => ["src_ip", "[logx][suricata][src_ip]"] + rename => ["src_port", "[logx][suricata][src_port]"] + rename => ["tx_id", "[logx][suricata][tx_id]"] + } + } + if (![dataSource]){ + mutate { + add_field => { "dataSource" => "%{host}" } + } + } + + mutate { + add_field => { + "dataType" => "suricata" + } + remove_field => [ "timestamp", "type", "path"] + rename => ["event_type", "[logx][suricata][event_type]"] + } + if [tls] { + mutate { + rename => ["tls", "[logx][suricata][tls]"] + } + } + if [ssh] { + mutate { + rename => ["ssh", "[logx][suricata][ssh]"] + } + } + if [stats] { + mutate { + rename => ["stats", "[logx][suricata][stats]"] + } + } + if [flow] { + mutate { + rename => ["flow", "[logx][suricata][flow]"] + } + } + if [tcp] { + mutate { + rename => ["tcp", "[logx][suricata][tcp]"] + } + } + if [dns] { + mutate { + rename => ["dns", "[logx][suricata][dns]"] + } + } + if [app_proto] { + mutate { + rename => ["app_proto", "[logx][suricata][app_proto]"] + } + } + if [anomaly] { + mutate { + rename => ["anomaly", "[logx][suricata][anomaly]"] + } + } + if [alert] { + mutate { + rename => ["alert", "[logx][suricata][alert]"] + } + if [logx][suricata][alert][severity] { + if [logx][suricata][alert][severity] >= 3 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "Low" + "[logx][suricata][severity]" => 1 + } + } + } + if [logx][suricata][alert][severity] == 2 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "Medium" + "[logx][suricata][severity]" => 2 + } + } + } + if [logx][suricata][alert][severity] == 1 { + mutate { + add_field => { + "[logx][suricata][severity_label]" => "High" + "[logx][suricata][severity]" => 3 + } + } + } + mutate { + remove_field => [ "[logx][suricata][alert][severity]" ] + } + } + } + #....................................................................... + # Add new event_types to logx structure, detected in real logs, present in suricata 7.0.0 + + mutate { + rename => ["http", "[logx][suricata][http]"] + rename => ["ftp", "[logx][suricata][ftp]"] + rename => ["ftp_data", "[logx][suricata][ftp_data]"] + rename => ["tftp", "[logx][suricata][tftp]"] + rename => ["smb", "[logx][suricata][smb]"] + + # RDP event_type + rename => ["rdp", "[logx][suricata][rdp]"] + # End RDP event_type + + rename => ["rfb", "[logx][suricata][rfb]"] + rename => ["mqtt", "[logx][suricata][mqtt]"] + rename => ["http2", "[logx][suricata][http2]"] + rename => ["pgsql", "[logx][suricata][pgsql]"] + rename => ["ike", "[logx][suricata][ike]"] + rename => ["ikev1", "[logx][suricata][ike]"] + rename => ["ikev2", "[logx][suricata][ike]"] + rename => ["modbus", "[logx][suricata][modbus]"] + rename => ["quic", "[logx][suricata][quic]"] + + # New fields from real logs, not present in suricata docs + rename => ["snmp", "[logx][suricata][snmp]"] + rename => ["fileinfo", "[logx][suricata][fileinfo]"] + rename => ["sip", "[logx][suricata][sip]"] + rename => ["dhcp", "[logx][suricata][dhcp]"] + + # This field isnt an event_type but appear in alert real logs + rename => ["files", "[logx][suricata][alert][files]"] + } + + #....................................................................... + # Add fields to logx structure, detected outside th event_type, present in suricata 7.0.0 + if [logx][suricata][event_type] == "mqtt" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][mqtt][pcap_cnt]"] + } + } else if [logx][suricata][event_type] == "pgsql" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][pgsql][pcap_cnt]"] + } + } else if [logx][suricata][event_type] == "fileinfo" { + if [logx][suricata][http] { + mutate { + rename => ["fileinfo", "[logx][suricata][http][fileinfo]"] + } + } else if [logx][suricata][http2] { + mutate { + rename => ["fileinfo", "[logx][suricata][http2][fileinfo]"] + } + } else { + mutate { + rename => ["fileinfo", "[logx][suricata][fileinfo]"] + } + } + } else if [logx][suricata][event_type] == "anomaly" { + mutate { + rename => ["pcap_cnt", "[logx][suricata][anomaly][pcap_cnt]"] + rename => ["packet", "[logx][suricata][anomaly][packet]"] + rename => ["packet_info", "[logx][suricata][anomaly][packet_info]"] + } + } else if [logx][suricata][event_type] == "flow" { + mutate { + rename => ["icmp_type", "[logx][suricata][flow][icmp_type]"] + rename => ["icmp_code", "[logx][suricata][flow][icmp_code]"] + rename => ["response_icmp_code", "[logx][suricata][flow][response_icmp_code]"] + rename => ["response_icmp_type", "[logx][suricata][flow][response_icmp_type]"] + } + } + + #....................................................................... + # Implementing logx.utm.action field used for established connections + if [logx][suricata][event_type] == "tls" { + if ![logx][suricata][tls][session_resumed] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "dns" { + if [logx][suricata][dns][type] and [logx][suricata][dns][type] == "answer" { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "flow" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][flow][bytes_toserver] and [logx][suricata][flow][bytes_toserver] > 0) and + ([logx][suricata][flow][bytes_toclient] and [logx][suricata][flow][bytes_toclient] > 0) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ssh" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + [logx][suricata][ssh][server] and [logx][suricata][ssh][client] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "alert" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][alert][action] == "allowed" and + ([logx][suricata][flow][bytes_toserver] and [logx][suricata][flow][bytes_toserver] > 0) and + ([logx][suricata][flow][bytes_toclient] and [logx][suricata][flow][bytes_toclient] > 0) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "http" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][http][status] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ftp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][ftp][completion_code] { + ruby { + code => " + event.get(''[logx][suricata][ftp][completion_code]'').each_with_index do |value,key| + if value =~ /(2\d\d)|(125)/ + event.set(''[logx][utm][action]'', ''Success'') + end + end + " + } + } + } else if [logx][suricata][event_type] == "tftp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][tftp][packet] and [logx][suricata][tftp][packet] != "error") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "smb" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ( [logx][suricata][smb][command] and "NEGOTIATE" in [logx][suricata][smb][command] ) and + [logx][suricata][smb][status] and ( "SUCCESS" in [logx][suricata][smb][status] or + "GRANTED" in [logx][suricata][smb][status] or "CONNECTED" in [logx][suricata][smb][status]) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "rdp" + or ([logx][suricata][rdp][event_type] and ([logx][suricata][rdp][event_type] == "initial_response" + or [logx][suricata][rdp][event_type] == "initial_request" or [logx][suricata][rdp][event_type] == "connect_request" + or [logx][suricata][rdp][event_type] == "connect_response" or [logx][suricata][rdp][event_type] == "tls_handshake") ) { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][rdp][event_type] == "connect_response" or [logx][suricata][rdp][event_type] == "tls_handshake") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "rfb" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][rfb][authentication][security-result] and [logx][suricata][rfb][authentication][security-result] == "OK") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "mqtt" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][mqtt][connack][return_code] and + ([logx][suricata][mqtt][connack][return_code] == 0 or [logx][suricata][mqtt][connack][return_code] == "0x00")) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "http2" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][http2][response][headers] ) { + ruby { + code => " + event.get(''[logx][suricata][http2][response][headers]'').each_with_index do |value,key| + if (value[''name''] == ''status'' or value[''name''] == '':status'') + event.set(''[logx][utm][action]'', ''Success'') + end + end + " + } + } + } else if [logx][suricata][event_type] == "pgsql" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][pgsql][request][simple_query] or [logx][suricata][pgsql][response][command_completed] or + ([logx][suricata][pgsql][response][ssl_accepted] and [logx][suricata][pgsql][response][ssl_accepted] == "true") or + ([logx][suricata][pgsql][response][accepted] and [logx][suricata][pgsql][response][accepted] == "true") or + [logx][suricata][pgsql][response][authentication_md5_password] ) { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "ike" or [logx][suricata][event_type] == "ikev1" or [logx][suricata][event_type] == "ikev2" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "modbus" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "sip" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "quic" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "fileinfo" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "snmp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } else if [logx][suricata][event_type] == "dhcp" { + if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and + ([logx][suricata][dhcp][assigned_ip] and [logx][suricata][dhcp][assigned_ip] != "0") { + mutate { + add_field => { "[logx][utm][action]" => "Success" } + } + } + } + } + } +}', 'suricata', null, true, 'SURICATA', false, '2.0.0'); ]]> From 02a6ac3ef6f9631ab209a6578eb6dfcadd5874f6 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 19 Aug 2025 17:13:49 -0500 Subject: [PATCH 5/7] Update suricata.conf --- filters/suricata/suricata.conf | 68 ++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/filters/suricata/suricata.conf b/filters/suricata/suricata.conf index 7e0138004..12f6c8f4e 100644 --- a/filters/suricata/suricata.conf +++ b/filters/suricata/suricata.conf @@ -1,6 +1,6 @@ filter { -# Suricata filter version 1.0.0 +# Suricata filter version 1.0.4 # Based on https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html (latest 8.0.0) (august 2025) # and real events log provided # Support json format @@ -24,11 +24,27 @@ filter { } if ![dataType] { - if [path] and [path] == "/var/log/suricata/eve.json" { - if [message] { - json { source => "message" } + # Parse Suricata logs from syslog + if [message] =~ /suricata\[\d+\]:.*event_type/ { + grok { + match => { + "message" => "<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{HOSTNAME:syslog_host} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:suricata_json}" } - if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" + tag_on_failure => ["_grokparsefailure_suricata"] + } + + if [suricata_json] { + json { + source => "suricata_json" + } + mutate { + remove_field => ["suricata_json", "syslog_pri", "syslog_timestamp", "syslog_program", "syslog_pid"] + } + } + + + # Process all Suricata event types (from both syslog and file) + if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" or [event_type] == "alert" or [event_type] == "dns" or [event_type] == "ssh" or [event_type] == "http" or [event_type] == "ftp" or [event_type] == "ftp_data" or [event_type] == "tftp" or [event_type] == "smb" or [event_type] == "initial_request" @@ -49,20 +65,36 @@ filter { rename => ["src_port", "[logx][suricata][src_port]"] rename => ["tx_id", "[logx][suricata][tx_id]"] } + } + + # Set dataSource from syslog_host or host + if (![dataSource]){ + if [syslog_host] { + mutate { + add_field => { "dataSource" => "%{syslog_host}" } } - if (![dataSource]){ - mutate { - add_field => { "dataSource" => "%{host}" } - } - } - + } else if [host] { mutate { - add_field => { - "dataType" => "suricata" - } - remove_field => [ "timestamp", "type", "path"] - rename => ["event_type", "[logx][suricata][event_type]"] + add_field => { "dataSource" => "%{host}" } + } + } + } + + # Clean up syslog_host field after using it + if [syslog_host] { + mutate { + remove_field => ["syslog_host"] + } + } + + # Add dataType and clean up fields + mutate { + add_field => { + "dataType" => "suricata" } + remove_field => [ "timestamp", "type", "path"] + rename => ["event_type", "[logx][suricata][event_type]"] + } if [tls] { mutate { rename => ["tls", "[logx][suricata][tls]"] @@ -374,5 +406,9 @@ filter { } } } + # Remove fields that are not needed + mutate { + remove_field => ["original_log_message", "suricata_json"] + } } } From 543d0af63dafe52fa0cf60abfb1d5081acdb96f4 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 19 Aug 2025 17:45:49 -0500 Subject: [PATCH 6/7] feat(suricata): update Suricata module configuration and log parsing logic --- .../changelog/20250814001_adding_suricata.xml | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml index 64c460c93..46a147b1b 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20250814001_adding_suricata.xml @@ -21,7 +21,7 @@ VALUES ('Suricata', 'Suricata is an open-source based intrusion detection system (IDS) and intrusion prevention system (IPS).', FALSE, - 'suricata.svg', + 'suricata.png', 'SURICATA', srv_id, 'Device', @@ -29,7 +29,7 @@ TRUE, TRUE) ON CONFLICT (module_name, server_id) DO UPDATE SET pretty_name = 'Suricata', - module_icon = 'suricata.svg', + module_icon = 'suricata.png', module_name = 'SURICATA', module_category = 'Device', module_description = 'Suricata is an open-source based intrusion detection system (IDS) and intrusion prevention system (IPS).', @@ -164,9 +164,9 @@ "message" } + # Parse Suricata logs from syslog + if [message] =~ /suricata\[\d+\]:.*event_type/ { + grok { + match => { + "message" => "<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{HOSTNAME:syslog_host} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:suricata_json}" } - if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" + tag_on_failure => ["_grokparsefailure_suricata"] + } + + if [suricata_json] { + json { + source => "suricata_json" + } + mutate { + remove_field => ["suricata_json", "syslog_pri", "syslog_timestamp", "syslog_program", "syslog_pid"] + } + } + + + # Process all Suricata event types (from both syslog and file) + if [event_type] and ([event_type] == "anomaly" or [event_type] == "tls" or [event_type] == "flow" or [event_type] == "alert" or [event_type] == "dns" or [event_type] == "ssh" or [event_type] == "http" or [event_type] == "ftp" or [event_type] == "ftp_data" or [event_type] == "tftp" or [event_type] == "smb" or [event_type] == "initial_request" @@ -215,20 +231,36 @@ rename => ["src_port", "[logx][suricata][src_port]"] rename => ["tx_id", "[logx][suricata][tx_id]"] } + } + + # Set dataSource from syslog_host or host + if (![dataSource]){ + if [syslog_host] { + mutate { + add_field => { "dataSource" => "%{syslog_host}" } } - if (![dataSource]){ - mutate { - add_field => { "dataSource" => "%{host}" } - } + } else if [host] { + mutate { + add_field => { "dataSource" => "%{host}" } } + } + } - mutate { - add_field => { - "dataType" => "suricata" - } - remove_field => [ "timestamp", "type", "path"] - rename => ["event_type", "[logx][suricata][event_type]"] + # Clean up syslog_host field after using it + if [syslog_host] { + mutate { + remove_field => ["syslog_host"] + } + } + + # Add dataType and clean up fields + mutate { + add_field => { + "dataType" => "suricata" } + remove_field => [ "timestamp", "type", "path"] + rename => ["event_type", "[logx][suricata][event_type]"] + } if [tls] { mutate { rename => ["tls", "[logx][suricata][tls]"] @@ -423,9 +455,9 @@ if [logx][suricata][src_ip] and [logx][suricata][dest_ip] and [logx][suricata][ftp][completion_code] { ruby { code => " - event.get(''[logx][suricata][ftp][completion_code]'').each_with_index do |value,key| + event.get('[logx][suricata][ftp][completion_code]').each_with_index do |value,key| if value =~ /(2\d\d)|(125)/ - event.set(''[logx][utm][action]'', ''Success'') + event.set('[logx][utm][action]', 'Success') end end " @@ -477,9 +509,9 @@ ([logx][suricata][http2][response][headers] ) { ruby { code => " - event.get(''[logx][suricata][http2][response][headers]'').each_with_index do |value,key| - if (value[''name''] == ''status'' or value[''name''] == '':status'') - event.set(''[logx][utm][action]'', ''Success'') + event.get('[logx][suricata][http2][response][headers]').each_with_index do |value,key| + if (value['name'] == 'status' or value['name'] == ':status') + event.set('[logx][utm][action]', 'Success') end end " @@ -540,8 +572,12 @@ } } } + # Remove fields that are not needed + mutate { + remove_field => ["original_log_message", "suricata_json"] + } } -}', 'suricata', null, true, 'SURICATA', false, '2.0.0'); +}$$, 'suricata', null, true, 'SURICATA', false, '2.0.0'); ]]> From d0b5e801983de0ac8af9baf58e8c27be952fc65b Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 20 Aug 2025 17:38:08 -0400 Subject: [PATCH 7/7] Update changelog and version --- CHANGELOG.md | 5 ++--- version.yml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f20ac5b..661a42373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ -# UTMStack 10.8.6 Release Notes +# UTMStack 10.9.0 Release Notes -- Expanded the exclusion dictionary for malicious IP connection logs to reduce false positives. -- Added support for older Linux versions (RedHat 7, RedHat 8, Ubuntu 20.04). \ No newline at end of file +- Added New Suricata Integration. \ No newline at end of file diff --git a/version.yml b/version.yml index 5dd9cf489..fdbb687c1 100644 --- a/version.yml +++ b/version.yml @@ -1 +1 @@ -version: 10.8.6 \ No newline at end of file +version: 10.9.0 \ No newline at end of file