1+ <?xml version =" 1.0" encoding =" utf-8" ?>
2+ <databaseChangeLog
3+ xmlns =" http://www.liquibase.org/xml/ns/dbchangelog"
4+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
5+ xsi : schemaLocation =" http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd" >
6+
7+ <changeSet id =" 20251107001" author =" JocLRojas" >
8+
9+ <sql dbms =" postgresql" splitStatements =" true" stripComments =" true" >
10+ <![CDATA[
11+
12+ UPDATE public.utm_logstash_filter
13+ SET filter_version='2.1.0',
14+ logstash_filter='filter {
15+
16+ # pfSense filter version 2.1.0
17+ # Based on https://docs.netgate.com/pfsense/en/latest/monitoring/logs/raw-filter-format.html
18+
19+ split {
20+ field => "message"
21+ terminator => "<utm-log-separator>"
22+ }
23+
24+ #Looking for datasource generated by an agent and parse original message
25+ if [message] =~ /\[utm_stack_agent_ds=(.+)\]-(.+)/ {
26+ grok {
27+ match => {
28+ "message" => "\[utm_stack_agent_ds=%{DATA:dataSource}\]-%{GREEDYDATA:original_log_message}"
29+ }
30+ }
31+ }
32+ if [original_log_message] {
33+ mutate {
34+ update => { "message" => "%{[original_log_message]}"
35+ }
36+ }
37+ }
38+
39+ if ![dataType] {
40+ # The log destination is already identified by the agent so, don''t need an entry point
41+ #......................................................................#
42+ #Generating dataType field required by CurrelationRulesEngine
43+ mutate {
44+ add_field => { "dataType" => "firewall-pfsense" }
45+ }
46+
47+ #......................................................................#
48+ #Using grok to parse header of the message
49+ grok {
50+ match => {
51+ "message" => [
52+ # Format with syslog header: <priority>version timestamp host program[pid]: data
53+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{DATA:program}\[%{NUMBER:pid}\]: %{GREEDYDATA:msg_all}",
54+ # Format with syslog header: <priority>version timestamp host program pid - - data
55+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{DATA:program} %{NUMBER:pid} - - %{GREEDYDATA:msg_all}",
56+ # Format with syslog header: <priority>version timestamp host program pid: data
57+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{DATA:program} %{NUMBER:pid}: %{GREEDYDATA:msg_all}",
58+ # Format with syslog header: <priority>version timestamp host program: data (no pid)
59+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{DATA:program}: %{GREEDYDATA:msg_all}",
60+ # Original pattern (legacy support): optional number at start + timestamp
61+ "(%{INT:not_defined})?(\s)?(<%{NUMBER:priority}>)?(%{INT:syslog_version})? %{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{GREEDYDATA:msg_all}",
62+ # Fallback: just timestamp and host for logs without syslog headers
63+ "%{TIMESTAMP_ISO8601:timestamp} %{IPORHOST:syslog_host} %{GREEDYDATA:msg_all}",
64+ # Direct CSV payload without any header
65+ "%{IPORHOST:syslog_host} %{DATA:program}.*? %{GREEDYDATA:msg_all}"
66+ ]
67+ }
68+ tag_on_failure => ["_grok_header_fail"]
69+ }
70+
71+ #......................................................................#
72+ #Checking that the msg_all field exists
73+ if [msg_all] {
74+ if [message] =~ / filterlog(.+),(match|\w+),(block|pass),(in|out),(4|6)/ {
75+ grok {
76+ match => {
77+ "msg_all" => "%{WORD:event_type}(\s)?(\[)?(%{NUMBER:pid})?(\])?( - -|:) %{GREEDYDATA:csv_msg}"
78+ }
79+ }
80+ #.....................................................................#
81+ #Check if csv_msg exists and parsing it
82+ if [csv_msg] {
83+ #Changing the empty fields by X0X and then eliminating them
84+ mutate {
85+ gsub => [
86+ "csv_msg", ",,", ",X0X,"
87+ ]
88+ }
89+ #The gsub is repeated, because the first time it leaves some intermediate fields empty
90+ mutate {
91+ gsub => [
92+ "csv_msg", ",,", ",X0X,"
93+ ]
94+ }
95+ }
96+ } else if [message] =~ / [a-z-_\.]+( \d+)? - - / {
97+ grok {
98+ match => {
99+ "msg_all" => "%{DATA:event_type}( %{NUMBER:pid})? - - (- )?%{GREEDYDATA:msg}"
100+ }
101+ }
102+ } else if [message] =~ /\/(.+?)( \d+)? - - \((.+?)\) [A-Z]+ \((.+?)\)/ {
103+ grok {
104+ match => {
105+ "msg_all" => "%{PATH:process}( %{NUMBER:pid})? - - \(%{DATA:user}\) %{WORD:command_action} \(%{PATH:process_path}\)"
106+ }
107+ }
108+ }
109+ }
110+
111+ #......................................................................#
112+ # Rename other fields
113+ mutate {
114+ rename => {
115+ "event_type" => "[logx][pfsense][event_type]"
116+ "msg" => "[logx][pfsense][msg]"
117+ "pid" => "[logx][pfsense][pid]"
118+ "priority" => "[logx][pfsense][priority]"
119+ "message" => "[logx][pfsense][message]"
120+ }
121+ }
122+
123+ #.....................................................................#
124+ #Generating dataSource field required by Correlation Engine
125+ if [syslog_host] {
126+ mutate {
127+ rename => { "syslog_host" => "[logx][pfsense][syslog_host]" }
128+ update => { "dataSource" => "%{[logx][pfsense][syslog_host]}" }
129+ }
130+ }
131+ if (![dataSource]){
132+ mutate {
133+ add_field => { "dataSource" => "%{host}" }
134+ }
135+ }
136+
137+ if [logx][pfsense][message] and [logx][pfsense][message] =~ /^</ {
138+ grok {
139+ match => {
140+ "[logx][pfsense][message]" => [
141+ "<%{NUMBER:priority}>%{INT:syslog_version} %{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program}(?: %{NUMBER:pid})? - - %{GREEDYDATA:payload}",
142+
143+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program}\[%{NUMBER:pid}\]: %{GREEDYDATA:payload}",
144+
145+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program} %{NUMBER:pid} - - %{GREEDYDATA:payload}",
146+
147+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program} %{NUMBER:pid}: %{GREEDYDATA:payload}",
148+
149+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program}: %{GREEDYDATA:payload}",
150+
151+ "<%{NUMBER:priority}>(?:%{INT:syslog_version} )?%{TIMESTAMP_ISO8601:deviceTime} %{DATA:syslog_host} %{DATA:program}.*? %{GREEDYDATA:payload}"
152+ ]
153+ }
154+ tag_on_failure => ["_grok2_fail"]
155+ }
156+ if !("_grok2_fail" in [tags]) and [payload] {
157+ mutate {
158+ replace => { "[logx][pfsense][message]" => "%{[payload]}" }
159+ }
160+ }
161+ }
162+ #......................................................................#
163+ #Remove unnecessary fields
164+ mutate {
165+ remove_field => [
166+ "@version","timestamp","payload","deviceTime","path","type","syslog_version","syslog_host","not_defined",
167+ "pid","priority","program","event","csv_msg","csv_field","msg_all","original_log_message","tags","headers"
168+ ]
169+ }
170+ }
171+
172+ if [logx][pfsense][message] {
173+
174+ #......................................................................#
175+ #IPv4 branches
176+ if [logx][pfsense][message] =~ /,4,/ {
177+
178+ #......................................................................#
179+ #IPv4 ICMP: map ICMP-specific tail
180+ if [logx][pfsense][message] =~ /,icmp,/ {
181+ csv {
182+ source => "[logx][pfsense][message]"
183+ target => "[logx][pfsense]"
184+ separator => ","
185+ skip_empty_columns => true
186+ columns => [
187+ "rule_number","sub_rule_number","anchor","tracker","interface","reason",
188+ "action","direction","ip_version","ipv4_tos","ipv4_ecn","ipv4_ttl",
189+ "ipv4_id","ipv4_offset","ipv4_flags","ipv4_protocol_id","proto","ip_length",
190+ "src_ip","dest_ip",
191+ "icmp_type","icmp_data1","icmp_data2","icmp_data3","icmp_data4","icmp_data5"
192+ ]
193+ }
194+
195+ #......................................................................#
196+ #IPv4 TCP/UDP: share list; TCP fills extra fields, UDP leaves them nil
197+ } else {
198+ csv {
199+ source => "[logx][pfsense][message]"
200+ target => "[logx][pfsense]"
201+ separator => ","
202+ skip_empty_columns => true
203+ columns => [
204+ "rule_number","sub_rule_number","anchor","tracker","interface","reason",
205+ "action","direction","ip_version","ipv4_tos","ipv4_ecn","ipv4_ttl",
206+ "ipv4_id","ipv4_offset","ipv4_flags","ipv4_protocol_id","proto","ip_length",
207+ "src_ip","dest_ip",
208+ "src_port","dest_port","data_length",
209+ "tcp_flags","sequence_number","ack_number","tcp_window","urg","tcp_options"
210+ ]
211+ }
212+ }
213+ }
214+
215+ #......................................................................#
216+ #IPv6 branches
217+ else if [logx][pfsense][message] =~ /,6,/ {
218+
219+ #......................................................................#
220+ #IPv6 ICMP
221+ if [logx][pfsense][message] =~ /,icmp,/ {
222+ csv {
223+ source => "[logx][pfsense][message]"
224+ target => "[logx][pfsense]"
225+ separator => ","
226+ skip_empty_columns => true
227+ columns => [
228+ "rule_number","sub_rule_number","anchor","tracker","interface","reason",
229+ "action","direction","ip_version","ipv6_class","ipv6_flow_label","ipv6_hop_limit",
230+ "proto","ipv6_protocol_id","ip_length","src_ip","dest_ip",
231+ "icmp_type","icmp_data1","icmp_data2","icmp_data3","icmp_data4","icmp_data5"
232+ ]
233+ }
234+
235+ #......................................................................#
236+ # IPv6 TCP/UDP
237+ } else {
238+ csv {
239+ source => "[logx][pfsense][message]"
240+ target => "[logx][pfsense]"
241+ separator => ","
242+ skip_empty_columns => true
243+ columns => [
244+ "rule_number","sub_rule_number","anchor","tracker","interface","reason",
245+ "action","direction","ip_version","ipv6_class","ipv6_flow_label","ipv6_hop_limit",
246+ "proto","ipv6_protocol_id","ip_length","src_ip","dest_ip",
247+ "src_port","dest_port","data_length",
248+ "tcp_flags","sequence_number","ack_number","tcp_window","urg","tcp_options"
249+ ]
250+ }
251+ }
252+ }
253+
254+ #......................................................................#
255+ #Generating action field for established connections
256+ if [logx][pfsense][action] == "pass" {
257+ mutate { add_field => { "[logx][utm][action]" => "Success" } }
258+ }
259+ }
260+ mutate {
261+ remove_field => "[logx][pfsense][message]"
262+ }
263+ }'
264+ WHERE id=1522;
265+ ]]>
266+ </sql >
267+ </changeSet >
268+ </databaseChangeLog >
0 commit comments