From b7e1989d45754d3e0e1f9aa1dc2cb4df27513d28 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Thu, 6 Nov 2025 09:49:46 -0500 Subject: [PATCH 01/15] resolve block-size not large enough for max fragmented IP packet size warning --- salt/suricata/defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/suricata/defaults.yaml b/salt/suricata/defaults.yaml index d819d1cf9..88435a70a 100644 --- a/salt/suricata/defaults.yaml +++ b/salt/suricata/defaults.yaml @@ -34,7 +34,7 @@ suricata: threads: 1 tpacket-v3: "yes" ring-size: 5000 - block-size: 32768 + block-size: 69632 block-timeout: 10 use-emergency-flush: "yes" buffer-size: 32768 From a84df141376c12bd2426a9b43b1c7e82edfd6322 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:23:55 -0600 Subject: [PATCH 02/15] rename forward node -> sensor node --- salt/common/tools/sbin/so-common | 2 +- salt/manager/tools/sbin/soup | 2 +- salt/pcap/soc_pcap.yaml | 2 +- setup/so-whiptail | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/salt/common/tools/sbin/so-common b/salt/common/tools/sbin/so-common index ba2cb5ae7..51d7bb038 100755 --- a/salt/common/tools/sbin/so-common +++ b/salt/common/tools/sbin/so-common @@ -395,7 +395,7 @@ is_manager_node() { } is_sensor_node() { - # Check to see if this is a sensor (forward) node + # Check to see if this is a sensor node is_single_node_grid && return 0 grep "role: so-" /etc/salt/grains | grep -E "sensor|heavynode" &> /dev/null } diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index f32b6edf8..276bbc94c 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -1679,7 +1679,7 @@ This appears to be a distributed deployment. Other nodes should update themselve Each minion is on a random 15 minute check-in period and things like network bandwidth can be a factor in how long the actual upgrade takes. If you have a heavy node on a slow link, it is going to take a while to get the containers to it. Depending on what changes happened between the versions, Elasticsearch might not be able to talk to said heavy node until the update is complete. -If it looks like you’re missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Forward nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC. +If it looks like you’re missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Sensor nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC. For more information, please see $DOC_BASE_URL/soup.html#distributed-deployments. diff --git a/salt/pcap/soc_pcap.yaml b/salt/pcap/soc_pcap.yaml index c9136512f..6b0c5a196 100644 --- a/salt/pcap/soc_pcap.yaml +++ b/salt/pcap/soc_pcap.yaml @@ -7,7 +7,7 @@ pcap: description: By default, Stenographer limits the number of files in the pcap directory to 30000 to avoid limitations with the ext3 filesystem. However, if you're using the ext4 or xfs filesystems, then it is safe to increase this value. So if you have a large amount of storage and find that you only have 3 weeks worth of PCAP on disk while still having plenty of free space, then you may want to increase this default setting. helpLink: stenographer.html diskfreepercentage: - description: Stenographer will purge old PCAP on a regular basis to keep the disk free percentage at this level. If you have a distributed deployment with dedicated forward nodes, then the default value of 10 should be reasonable since Stenographer should be the main consumer of disk space in the /nsm partition. However, if you have systems that run both Stenographer and Elasticsearch at the same time (like eval and standalone installations), then you’ll want to make sure that this value is no lower than 21 so that you avoid Elasticsearch hitting its watermark setting at 80% disk usage. If you have an older standalone installation, then you may need to manually change this value to 21. + description: Stenographer will purge old PCAP on a regular basis to keep the disk free percentage at this level. If you have a distributed deployment with dedicated Sensor nodes, then the default value of 10 should be reasonable since Stenographer should be the main consumer of disk space in the /nsm partition. However, if you have systems that run both Stenographer and Elasticsearch at the same time (like eval and standalone installations), then you’ll want to make sure that this value is no lower than 21 so that you avoid Elasticsearch hitting its watermark setting at 80% disk usage. If you have an older standalone installation, then you may need to manually change this value to 21. helpLink: stenographer.html blocks: description: The number of 1MB packet blocks used by Stenographer and AF_PACKET to store packets in memory, per thread. You shouldn't need to change this. diff --git a/setup/so-whiptail b/setup/so-whiptail index 57bd10b8c..6fc5cbba5 100755 --- a/setup/so-whiptail +++ b/setup/so-whiptail @@ -676,8 +676,8 @@ whiptail_install_type_dist_existing() { EOM install_type=$(whiptail --title "$whiptail_title" --menu "$node_msg" 19 75 7 \ - "SENSOR" "Create a forward only sensor " \ - "SEARCHNODE" "Add a search node with parsing " \ + "SENSOR" "Add a Sensor Node for monitoring network traffic " \ + "SEARCHNODE" "Add a Search Node with parsing " \ "FLEET" "Dedicated Elastic Fleet Node " \ "HEAVYNODE" "Sensor + Search Node " \ "IDH" "Intrusion Detection Honeypot Node " \ From da1cac0d53e00e2f667a3d09c4f8249b3da021a4 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Thu, 6 Nov 2025 16:32:55 -0500 Subject: [PATCH 03/15] tls-log, http-log and syslog outputs deprecated https://github.com/Security-Onion-Solutions/securityonion/issues/15203 --- salt/suricata/defaults.yaml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/salt/suricata/defaults.yaml b/salt/suricata/defaults.yaml index 88435a70a..e1b68e9d1 100644 --- a/salt/suricata/defaults.yaml +++ b/salt/suricata/defaults.yaml @@ -134,14 +134,6 @@ suricata: header: X-Forwarded-For unified2-alert: enabled: "no" - http-log: - enabled: "no" - filename: http.log - append: "yes" - tls-log: - enabled: "no" - filename: tls.log - append: "yes" tls-store: enabled: "no" pcap-log: @@ -157,9 +149,6 @@ suricata: totals: "yes" threads: "no" null-values: "yes" - syslog: - enabled: "no" - facility: local5 drop: enabled: "no" file-store: @@ -463,3 +452,6 @@ suricata: classification-file: /etc/suricata/classification.config reference-config-file: /etc/suricata/reference.config threshold-file: /etc/suricata/threshold.conf + + +# ENABLE for From 6c7ef622c1a5458ea9b1bd0830fbc3c0531065e7 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Fri, 7 Nov 2025 17:08:33 -0500 Subject: [PATCH 04/15] spaces removed from expected output --- salt/suricata/tools/sbin/so-suricata-reload-rules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/suricata/tools/sbin/so-suricata-reload-rules b/salt/suricata/tools/sbin/so-suricata-reload-rules index 2d60c3422..e21e28e2f 100644 --- a/salt/suricata/tools/sbin/so-suricata-reload-rules +++ b/salt/suricata/tools/sbin/so-suricata-reload-rules @@ -7,5 +7,5 @@ . /usr/sbin/so-common -retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c reload-rules /var/run/suricata/suricata-command.socket' '{"message": "done", "return": "OK"}' || fail "The Suricata container was not ready in time." -retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c ruleset-reload-nonblocking /var/run/suricata/suricata-command.socket' '{"message": "done", "return": "OK"}' || fail "The Suricata container was not ready in time." +retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c reload-rules /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time." +retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c ruleset-reload-nonblocking /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time." From 274295bc97382f6d670dfe19124b4ab865f43f1d Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Fri, 7 Nov 2025 17:39:13 -0500 Subject: [PATCH 05/15] return exit codes --- salt/common/tools/sbin/so-bpf-compile | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/salt/common/tools/sbin/so-bpf-compile b/salt/common/tools/sbin/so-bpf-compile index f1136cd0e..316a26775 100755 --- a/salt/common/tools/sbin/so-bpf-compile +++ b/salt/common/tools/sbin/so-bpf-compile @@ -29,9 +29,26 @@ fi interface="$1" shift -tcpdump -i $interface -ddd $@ | tail -n+2 | -while read line; do + +# Capture tcpdump output and exit code +tcpdump_output=$(tcpdump -i "$interface" -ddd "$@" 2>&1) +tcpdump_exit=$? + +if [ $tcpdump_exit -ne 0 ]; then + echo "$tcpdump_output" >&2 + exit $tcpdump_exit +fi + +# Process the output, skipping the first line +echo "$tcpdump_output" | tail -n+2 | while read -r line; do cols=( $line ) - printf "%04x%02x%02x%08x" ${cols[0]} ${cols[1]} ${cols[2]} ${cols[3]} + printf "%04x%02x%02x%08x" "${cols[0]}" "${cols[1]}" "${cols[2]}" "${cols[3]}" done + +# Check if the pipeline succeeded +if [ "${PIPESTATUS[0]}" -ne 0 ]; then + exit 1 +fi + echo "" +exit 0 From 78c193f0a2848cd4fbae5f04144c63a81a59900e Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Fri, 7 Nov 2025 17:40:24 -0500 Subject: [PATCH 06/15] handle bpf for suricata 8 pcap --- salt/suricata/config.sls | 68 +++++++++++++++++++--------------------- salt/suricata/map.jinja | 11 +++++++ 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/salt/suricata/config.sls b/salt/suricata/config.sls index 00364f384..c5ca72da3 100644 --- a/salt/suricata/config.sls +++ b/salt/suricata/config.sls @@ -7,9 +7,40 @@ {% if sls.split('.')[0] in allowed_states %} {% from 'vars/globals.map.jinja' import GLOBALS %} -{% from 'bpf/suricata.map.jinja' import SURICATABPF %} {% from 'suricata/map.jinja' import SURICATAMERGED %} -{% set BPF_STATUS = 0 %} + +suridir: + file.directory: + - name: /opt/so/conf/suricata + - user: 940 + - group: 940 + +{% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %} +{% from 'bpf/suricata.map.jinja' import SURICATABPF %} +{% from 'suricata/map.jinja' import BPF_STATUS %} +{% from 'suricata/map.jinja' import BPF_CALC %} + +# BPF compilation and configuration +{% if SURICATABPF and not BPF_STATUS %} +suribpfcompilationfailure: + test.configurable_test_state: + - changes: False + - result: False + - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ BPF_CALC['stderr'] }}" +{% endif %} + +suribpf: + file.managed: + - name: /opt/so/conf/suricata/bpf + - user: 940 + - group: 940 + {% if BPF_STATUS %} + - contents: {{ SURICATABPF }} + {% else %} + - contents: + - "" + {% endif %} +{% endif %} # Add Suricata Group suricatagroup: @@ -49,18 +80,11 @@ suricata_sbin_jinja: - file_mode: 755 - template: jinja -suridir: - file.directory: - - name: /opt/so/conf/suricata - - user: 940 - - group: 940 - suriruledir: file.directory: - name: /opt/so/conf/suricata/rules - user: 940 - group: 940 - - makedirs: True surilogdir: file.directory: @@ -136,32 +160,6 @@ suriclassifications: - user: 940 - group: 940 -# BPF compilation and configuration -{% if SURICATABPF %} - {% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + SURICATABPF|join(" "),cwd='/root') %} - {% if BPF_CALC['stderr'] == "" %} - {% set BPF_STATUS = 1 %} - {% else %} -suribpfcompilationfailure: - test.configurable_test_state: - - changes: False - - result: False - - comment: "BPF Syntax Error - Discarding Specified BPF" - {% endif %} -{% endif %} - -suribpf: - file.managed: - - name: /opt/so/conf/suricata/bpf - - user: 940 - - group: 940 - {% if BPF_STATUS %} - - contents: {{ SURICATABPF }} - {% else %} - - contents: - - "" - {% endif %} - so-suricata-eve-clean: file.managed: - name: /usr/sbin/so-suricata-eve-clean diff --git a/salt/suricata/map.jinja b/salt/suricata/map.jinja index a2c7072e0..5080b8620 100644 --- a/salt/suricata/map.jinja +++ b/salt/suricata/map.jinja @@ -7,9 +7,20 @@ {% set default_filestore_index = [] %} {% set surimeta_evelog_index = [] %} {% set surimeta_filestore_index = [] %} +{% set BPF_STATUS = 0 %} {# before we change outputs back to list, enable pcap-log if suricata is the pcapengine #} {% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %} + +{% from 'bpf/suricata.map.jinja' import SURICATABPF %} +{% if SURICATABPF %} + {% set BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %} + {% if BPF_CALC['retcode'] == 0 %} + {% set BPF_STATUS = 1 %} + {% do SURICATAMERGED.config.outputs['pcap-log'].update({'bpf-filter': SURICATABPF|join(" ")}) %} + {% endif %} +{% endif %} + {% do SURICATAMERGED.config.outputs['pcap-log'].update({'enabled': 'yes'}) %} {# move the items in suricata.pcap into suricata.config.outputs.pcap-log. these items were placed under suricata.config for ease of access in SOC #} {% do SURICATAMERGED.config.outputs['pcap-log'].update({'compression': SURICATAMERGED.pcap.compression}) %} From 18c0f197b21a3d187241cd97bf68cd2df95c944c Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Mon, 10 Nov 2025 13:28:19 -0500 Subject: [PATCH 07/15] suricata bpf --- salt/bpf/pcap.map.jinja | 11 +++++++++++ salt/bpf/soc_bpf.yaml | 4 ++-- salt/bpf/suricata.map.jinja | 9 +++++++++ salt/bpf/zeek.map.jinja | 9 +++++++++ salt/pcap/config.sls | 19 +++++-------------- salt/pcap/files/config.jinja | 2 +- salt/suricata/config.sls | 23 +++++++++++++++-------- salt/suricata/map.jinja | 13 ++++--------- salt/zeek/config.sls | 16 +++++----------- 9 files changed, 61 insertions(+), 45 deletions(-) diff --git a/salt/bpf/pcap.map.jinja b/salt/bpf/pcap.map.jinja index 4d8fef460..1b561b8d0 100644 --- a/salt/bpf/pcap.map.jinja +++ b/salt/bpf/pcap.map.jinja @@ -1,4 +1,7 @@ {% from 'vars/globals.map.jinja' import GLOBALS %} +{% set PCAP_BPF_STATUS = 0 %} +{% set STENO_BPF_COMPILED = "" %} + {% if GLOBALS.pcap_engine == "TRANSITION" %} {% set PCAPBPF = ["ip and host 255.255.255.1 and port 1"] %} {% else %} @@ -8,3 +11,11 @@ {{ MACROS.remove_comments(BPFMERGED, 'pcap') }} {% set PCAPBPF = BPFMERGED.pcap %} {% endif %} + +{% if PCAPBPF %} + {% set PCAP_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ PCAPBPF|join(" "), cwd='/root') %} + {% if PCAP_BPF_CALC['retcode'] == 0 %} + {% set PCAP_BPF_STATUS = 1 %} + {% set STENO_BPF_COMPILED = ",\\\"--filter=" + BPF_CALC['stdout'] + "\\\"" %} + {% endif %} +{% endif %} diff --git a/salt/bpf/soc_bpf.yaml b/salt/bpf/soc_bpf.yaml index d93ec98fd..629ef9d5d 100644 --- a/salt/bpf/soc_bpf.yaml +++ b/salt/bpf/soc_bpf.yaml @@ -1,11 +1,11 @@ bpf: pcap: - description: List of BPF filters to apply to Stenographer. + description: List of BPF filters to apply to the packet capture application. multiline: True forcedType: "[]string" helpLink: bpf.html suricata: - description: List of BPF filters to apply to Suricata. + description: List of BPF filters to apply to Suricata. This will apply to alerts and, if enabled, to metadata and PCAP logs generated by Suricata. multiline: True forcedType: "[]string" helpLink: bpf.html diff --git a/salt/bpf/suricata.map.jinja b/salt/bpf/suricata.map.jinja index fe4adb663..5ee1e5a92 100644 --- a/salt/bpf/suricata.map.jinja +++ b/salt/bpf/suricata.map.jinja @@ -1,7 +1,16 @@ +{% from 'vars/globals.map.jinja' import GLOBALS %} {% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %} {% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %} +{% set SURICATA_BPF_STATUS = 0 %} {% import 'bpf/macros.jinja' as MACROS %} {{ MACROS.remove_comments(BPFMERGED, 'suricata') }} {% set SURICATABPF = BPFMERGED.suricata %} + +{% if SURICATABPF %} + {% set SURICATA_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %} + {% if SURICATA_BPF_CALC['retcode'] == 0 %} + {% set SURICATA_BPF_STATUS = 1 %} + {% endif %} +{% endif %} diff --git a/salt/bpf/zeek.map.jinja b/salt/bpf/zeek.map.jinja index fdcc5e99f..789648bdb 100644 --- a/salt/bpf/zeek.map.jinja +++ b/salt/bpf/zeek.map.jinja @@ -1,7 +1,16 @@ +{% from 'vars/globals.map.jinja' import GLOBALS %} {% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %} {% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %} +{% set ZEEK_BPF_STATUS = 0 %} {% import 'bpf/macros.jinja' as MACROS %} {{ MACROS.remove_comments(BPFMERGED, 'zeek') }} {% set ZEEKBPF = BPFMERGED.zeek %} + +{% if ZEEKBPF %} + {% set ZEEK_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ ZEEKBPF|join(" "), cwd='/root') %} + {% if ZEEK_BPF_CALC['retcode'] == 0 %} + {% set ZEEK_BPF_STATUS = 1 %} + {% endif %} +{% endif %} diff --git a/salt/pcap/config.sls b/salt/pcap/config.sls index 173fecfd1..c37da9694 100644 --- a/salt/pcap/config.sls +++ b/salt/pcap/config.sls @@ -8,12 +8,9 @@ {% from 'vars/globals.map.jinja' import GLOBALS %} {% from "pcap/config.map.jinja" import PCAPMERGED %} -{% from 'bpf/pcap.map.jinja' import PCAPBPF %} - -{% set BPF_COMPILED = "" %} +{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC, STENO_BPF_COMPILED %} # PCAP Section - stenographergroup: group.present: - name: stenographer @@ -40,18 +37,12 @@ pcap_sbin: - group: 939 - file_mode: 755 -{% if PCAPBPF %} - {% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + PCAPBPF|join(" "),cwd='/root') %} - {% if BPF_CALC['stderr'] == "" %} - {% set BPF_COMPILED = ",\\\"--filter=" + BPF_CALC['stdout'] + "\\\"" %} - {% else %} - -bpfcompilationfailure: +{% if PCAPBPF and not PCAP_BPF_STATUS %} +stenoPCAPbpfcompilationfailure: test.configurable_test_state: - changes: False - result: False - - comment: "BPF Compilation Failed - Discarding Specified BPF" - {% endif %} + - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ PCAP_BPF_CALC['stderr'] }}" {% endif %} stenoconf: @@ -64,7 +55,7 @@ stenoconf: - template: jinja - defaults: PCAPMERGED: {{ PCAPMERGED }} - BPF_COMPILED: "{{ BPF_COMPILED }}" + STENO_BPF_COMPILED: "{{ STENO_BPF_COMPILED }}" stenoca: file.directory: diff --git a/salt/pcap/files/config.jinja b/salt/pcap/files/config.jinja index f0a4fc51d..90c197938 100644 --- a/salt/pcap/files/config.jinja +++ b/salt/pcap/files/config.jinja @@ -6,6 +6,6 @@ , "Interface": "{{ pillar.sensor.interface }}" , "Port": 1234 , "Host": "127.0.0.1" - , "Flags": ["-v", "--blocks={{ PCAPMERGED.config.blocks }}", "--preallocate_file_mb={{ PCAPMERGED.config.preallocate_file_mb }}", "--aiops={{ PCAPMERGED.config.aiops }}", "--uid=stenographer", "--gid=stenographer"{{ BPF_COMPILED }}] + , "Flags": ["-v", "--blocks={{ PCAPMERGED.config.blocks }}", "--preallocate_file_mb={{ PCAPMERGED.config.preallocate_file_mb }}", "--aiops={{ PCAPMERGED.config.aiops }}", "--uid=stenographer", "--gid=stenographer"{{ STENO_BPF_COMPILED }}] , "CertPath": "/etc/stenographer/certs" } diff --git a/salt/suricata/config.sls b/salt/suricata/config.sls index c5ca72da3..7de1a0fd4 100644 --- a/salt/suricata/config.sls +++ b/salt/suricata/config.sls @@ -8,6 +8,7 @@ {% from 'vars/globals.map.jinja' import GLOBALS %} {% from 'suricata/map.jinja' import SURICATAMERGED %} +{% from 'bpf/suricata.map.jinja' import SURICATABPF, SURICATA_BPF_STATUS, SURICATA_BPF_CALC %} suridir: file.directory: @@ -16,30 +17,36 @@ suridir: - group: 940 {% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %} -{% from 'bpf/suricata.map.jinja' import SURICATABPF %} -{% from 'suricata/map.jinja' import BPF_STATUS %} -{% from 'suricata/map.jinja' import BPF_CALC %} - +{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC %} # BPF compilation and configuration -{% if SURICATABPF and not BPF_STATUS %} -suribpfcompilationfailure: +{% if PCAPBPF and not PCAP_BPF_STATUS %} +suriPCAPbpfcompilationfailure: test.configurable_test_state: - changes: False - result: False - - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ BPF_CALC['stderr'] }}" + - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ PCAP_BPF_CALC['stderr'] }}" {% endif %} +{% endif %} +# BPF applied to all of Suricata - alerts/metadata/pcap suribpf: file.managed: - name: /opt/so/conf/suricata/bpf - user: 940 - group: 940 - {% if BPF_STATUS %} + {% if SURICATA_BPF_STATUS %} - contents: {{ SURICATABPF }} {% else %} - contents: - "" {% endif %} + +{% if SURICATABPF and not SURICATA_BPF_STATUS %} +suribpfcompilationfailure: + test.configurable_test_state: + - changes: False + - result: False + - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ SURICATA_BPF_CALC['stderr'] }}" {% endif %} # Add Suricata Group diff --git a/salt/suricata/map.jinja b/salt/suricata/map.jinja index 5080b8620..3d378b69d 100644 --- a/salt/suricata/map.jinja +++ b/salt/suricata/map.jinja @@ -7,19 +7,14 @@ {% set default_filestore_index = [] %} {% set surimeta_evelog_index = [] %} {% set surimeta_filestore_index = [] %} -{% set BPF_STATUS = 0 %} {# before we change outputs back to list, enable pcap-log if suricata is the pcapengine #} {% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %} -{% from 'bpf/suricata.map.jinja' import SURICATABPF %} -{% if SURICATABPF %} - {% set BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %} - {% if BPF_CALC['retcode'] == 0 %} - {% set BPF_STATUS = 1 %} - {% do SURICATAMERGED.config.outputs['pcap-log'].update({'bpf-filter': SURICATABPF|join(" ")}) %} - {% endif %} -{% endif %} +{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS %} +{% if PCAPBPF and PCAP_BPF_STATUS %} +{% do SURICATAMERGED.config.outputs['pcap-log'].update({'bpf-filter': PCAPBPF|join(" ")}) %} +{% endif %} {% do SURICATAMERGED.config.outputs['pcap-log'].update({'enabled': 'yes'}) %} {# move the items in suricata.pcap into suricata.config.outputs.pcap-log. these items were placed under suricata.config for ease of access in SOC #} diff --git a/salt/zeek/config.sls b/salt/zeek/config.sls index b3ea97507..42ea74fc9 100644 --- a/salt/zeek/config.sls +++ b/salt/zeek/config.sls @@ -8,8 +8,7 @@ {% from 'vars/globals.map.jinja' import GLOBALS %} {% from "zeek/config.map.jinja" import ZEEKMERGED %} -{% from 'bpf/zeek.map.jinja' import ZEEKBPF %} -{% set BPF_STATUS = 0 %} +{% from 'bpf/zeek.map.jinja' import ZEEKBPF, ZEEK_BPF_STATUS, ZEEK_BPF_CALC %} # Add Zeek group zeekgroup: @@ -158,18 +157,13 @@ zeekja4cfg: - user: 937 - group: 939 -# BPF compilation and configuration -{% if ZEEKBPF %} - {% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + ZEEKBPF|join(" "),cwd='/root') %} - {% if BPF_CALC['stderr'] == "" %} - {% set BPF_STATUS = 1 %} - {% else %} +# BPF compilation failed +{% if ZEEKBPF and not ZEEK_BPF_STATUS %} zeekbpfcompilationfailure: test.configurable_test_state: - changes: False - result: False - - comment: "BPF Syntax Error - Discarding Specified BPF" - {% endif %} + - comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ ZEEK_BPF_CALC['stderr'] }}" {% endif %} zeekbpf: @@ -177,7 +171,7 @@ zeekbpf: - name: /opt/so/conf/zeek/bpf - user: 940 - group: 940 -{% if BPF_STATUS %} +{% if ZEEK_BPF_STATUS %} - contents: {{ ZEEKBPF }} {% else %} - contents: From a2ff66b5d0c2e60092c432169726fb7b45246d88 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Mon, 10 Nov 2025 14:12:20 -0500 Subject: [PATCH 08/15] update annotation --- salt/bpf/soc_bpf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/bpf/soc_bpf.yaml b/salt/bpf/soc_bpf.yaml index 629ef9d5d..416c5fc60 100644 --- a/salt/bpf/soc_bpf.yaml +++ b/salt/bpf/soc_bpf.yaml @@ -1,6 +1,6 @@ bpf: pcap: - description: List of BPF filters to apply to the packet capture application. + description: List of BPF filters to apply to the PCAP engine. multiline: True forcedType: "[]string" helpLink: bpf.html From 1876c4d9df25a04d4dd95c29d1f4905f8995358f Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Mon, 10 Nov 2025 14:16:16 -0500 Subject: [PATCH 09/15] fix var name --- salt/bpf/pcap.map.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/bpf/pcap.map.jinja b/salt/bpf/pcap.map.jinja index 1b561b8d0..953b01a08 100644 --- a/salt/bpf/pcap.map.jinja +++ b/salt/bpf/pcap.map.jinja @@ -16,6 +16,6 @@ {% set PCAP_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ PCAPBPF|join(" "), cwd='/root') %} {% if PCAP_BPF_CALC['retcode'] == 0 %} {% set PCAP_BPF_STATUS = 1 %} - {% set STENO_BPF_COMPILED = ",\\\"--filter=" + BPF_CALC['stdout'] + "\\\"" %} + {% set STENO_BPF_COMPILED = ",\\\"--filter=" + PCAP_BPF_CALC['stdout'] + "\\\"" %} {% endif %} {% endif %} From 44594ba72623c3803b7e5c52024ac65a6c6028e6 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Mon, 10 Nov 2025 14:24:27 -0500 Subject: [PATCH 10/15] Update defaults.yaml --- salt/soc/defaults.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/salt/soc/defaults.yaml b/salt/soc/defaults.yaml index 813716f39..f95456924 100644 --- a/salt/soc/defaults.yaml +++ b/salt/soc/defaults.yaml @@ -2570,4 +2570,9 @@ soc: displayName: GPT-OSS 120B contextLimitSmall: 128000 contextLimitLarge: 128000 - lowBalanceColorAlert: 500000 \ No newline at end of file + lowBalanceColorAlert: 500000 + - id: qwen-235b + displayName: QWEN 235B + contextLimitSmall: 256000 + contextLimitLarge: 256000 + lowBalanceColorAlert: 500000 From 4c65975907b0390d12daeea0df44bf43ae6931e3 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Mon, 10 Nov 2025 15:44:05 -0500 Subject: [PATCH 11/15] reduce pcapMaxCount to fit better with max upload size --- salt/sensoroni/defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/sensoroni/defaults.yaml b/salt/sensoroni/defaults.yaml index acfae6766..4e726817e 100644 --- a/salt/sensoroni/defaults.yaml +++ b/salt/sensoroni/defaults.yaml @@ -15,7 +15,7 @@ sensoroni: sensoronikey: soc_host: suripcap: - pcapMaxCount: 999999 + pcapMaxCount: 100000 analyzers: echotrail: base_url: https://api.echotrail.io/insights/ From 245ceb2d4950d661e1fb522f90ac902beff0b086 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Mon, 10 Nov 2025 16:40:11 -0500 Subject: [PATCH 12/15] suricata defaults and annotation --- salt/suricata/defaults.yaml | 44 ++++++++++++++++++++++----------- salt/suricata/soc_suricata.yaml | 11 ++++++++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/salt/suricata/defaults.yaml b/salt/suricata/defaults.yaml index e1b68e9d1..9c9a7a8ed 100644 --- a/salt/suricata/defaults.yaml +++ b/salt/suricata/defaults.yaml @@ -97,6 +97,11 @@ suricata: - 4789 TEREDO_PORTS: - 3544 + SIP_PORTS: + - 5060 + - 5061 + GENEVE_PORTS: + - 6081 default-log-dir: /var/log/suricata/ stats: enabled: "yes" @@ -195,6 +200,9 @@ suricata: enabled: "yes" detection-ports: dp: 443 + ja3-fingerprints: auto + ja4-fingerprints: auto + encryption-handling: track-only dcerpc: enabled: "yes" ftp: @@ -244,19 +252,21 @@ suricata: libhtp: default-config: personality: IDS - request-body-limit: 100kb - response-body-limit: 100kb - request-body-minimal-inspect-size: 32kb - request-body-inspect-window: 4kb - response-body-minimal-inspect-size: 40kb - response-body-inspect-window: 16kb + request-body-limit: 100 KiB + response-body-limit: 100 KiB + request-body-minimal-inspect-size: 32 KiB + request-body-inspect-window: 4 KiB + response-body-minimal-inspect-size: 40 KiB + response-body-inspect-window: 16 KiB response-body-decompress-layer-limit: 2 http-body-inline: auto swf-decompression: - enabled: "yes" + enabled: "no" type: both - compress-depth: 0 - decompress-depth: 0 + compress-depth: 100 KiB + decompress-depth: 100 KiB + randomize-inspection-sizes: "yes" + randomize-inspection-range: 10 double-decode-path: "no" double-decode-query: "no" server-config: @@ -390,8 +400,12 @@ suricata: vxlan: enabled: true ports: $VXLAN_PORTS - erspan: + geneve: enabled: true + ports: $GENEVE_PORTS + max-layers: 16 + recursion-level: + use-for-tracking: true detect: profile: medium custom-values: @@ -411,7 +425,12 @@ suricata: spm-algo: auto luajit: states: 128 - + security: + lua: + allow-rules: false + max-bytes: 500000 + max-instructions: 500000 + allow-restricted-functions: false profiling: rules: enabled: "yes" @@ -452,6 +471,3 @@ suricata: classification-file: /etc/suricata/classification.config reference-config-file: /etc/suricata/reference.config threshold-file: /etc/suricata/threshold.conf - - -# ENABLE for diff --git a/salt/suricata/soc_suricata.yaml b/salt/suricata/soc_suricata.yaml index 8b5ce7b11..03f30be75 100644 --- a/salt/suricata/soc_suricata.yaml +++ b/salt/suricata/soc_suricata.yaml @@ -190,6 +190,8 @@ suricata: FTP_PORTS: *suriportgroup VXLAN_PORTS: *suriportgroup TEREDO_PORTS: *suriportgroup + SIP_PORTS: *suriportgroup + GENEVE_PORTS: *suriportgroup outputs: eve-log: types: @@ -209,7 +211,7 @@ suricata: helpLink: suricata.html pcap-log: enabled: - description: This value is ignored by SO. pcapengine in globals takes precidence. + description: This value is ignored by SO. pcapengine in globals takes precedence. readonly: True helpLink: suricata.html advanced: True @@ -297,3 +299,10 @@ suricata: ports: description: Ports to listen for. This should be a variable. helpLink: suricata.html + geneve: + enabled: + description: Enable VXLAN capabilities. + helpLink: suricata.html + ports: + description: Ports to listen for. This should be a variable. + helpLink: suricata.html From 0545e1d33b6dee22dd4f3fd78224c3f8ac7afdfb Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 11 Nov 2025 16:55:00 -0500 Subject: [PATCH 13/15] add support to so-yaml for using yaml file content for values --- salt/manager/tools/sbin/so-yaml.py | 12 +++++++++--- salt/manager/tools/sbin/so-yaml_test.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/salt/manager/tools/sbin/so-yaml.py b/salt/manager/tools/sbin/so-yaml.py index 275032ee0..4c8544893 100755 --- a/salt/manager/tools/sbin/so-yaml.py +++ b/salt/manager/tools/sbin/so-yaml.py @@ -26,8 +26,8 @@ def showUsage(args): print(' Where:', file=sys.stderr) print(' YAML_FILE - Path to the file that will be modified. Ex: /opt/so/conf/service/conf.yaml', file=sys.stderr) print(' KEY - YAML key, does not support \' or " characters at this time. Ex: level1.level2', file=sys.stderr) - print(' VALUE - Value to set for a given key', file=sys.stderr) - print(' LISTITEM - Item to append to a given key\'s list value', file=sys.stderr) + print(' VALUE - Value to set for a given key. Can be a literal value or file: to load from a YAML file.', file=sys.stderr) + print(' LISTITEM - Item to append to a given key\'s list value. Can be a literal value or file: to load from a YAML file.', file=sys.stderr) sys.exit(1) @@ -58,7 +58,13 @@ def appendItem(content, key, listItem): def convertType(value): - if isinstance(value, str) and len(value) > 0 and (not value.startswith("0") or len(value) == 1): + if isinstance(value, str) and value.startswith("file:"): + path = value[5:] # Remove "file:" prefix + if not os.path.exists(path): + print(f"File '{path}' does not exist.", file=sys.stderr) + sys.exit(1) + return loadYaml(path) + elif isinstance(value, str) and len(value) > 0 and (not value.startswith("0") or len(value) == 1): if "." in value: try: value = float(value) diff --git a/salt/manager/tools/sbin/so-yaml_test.py b/salt/manager/tools/sbin/so-yaml_test.py index 5ca46cb68..0346f0c3c 100644 --- a/salt/manager/tools/sbin/so-yaml_test.py +++ b/salt/manager/tools/sbin/so-yaml_test.py @@ -361,6 +361,29 @@ class TestRemove(unittest.TestCase): self.assertEqual(soyaml.convertType("FALSE"), False) self.assertEqual(soyaml.convertType(""), "") + def test_convert_file(self): + import tempfile + import os + + # Create a temporary YAML file + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + f.write("test:\n - name: hi\n color: blue\n") + temp_file = f.name + + try: + result = soyaml.convertType(f"file:{temp_file}") + expected = {"test": [{"name": "hi", "color": "blue"}]} + self.assertEqual(result, expected) + finally: + os.unlink(temp_file) + + def test_convert_file_nonexistent(self): + with patch('sys.exit', new=MagicMock()) as sysmock: + with patch('sys.stderr', new=StringIO()) as mock_stderr: + soyaml.convertType("file:/nonexistent/file.yaml") + sysmock.assert_called_once_with(1) + self.assertIn("File '/nonexistent/file.yaml' does not exist.", mock_stderr.getvalue()) + def test_get_int(self): with patch('sys.stdout', new=StringIO()) as mock_stdout: filename = "/tmp/so-yaml_test-get.yaml" From 7c45db2295724c53898668eb9a13c6c43070f5ad Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 11 Nov 2025 16:57:54 -0500 Subject: [PATCH 14/15] add support to so-yaml for using yaml file content for values --- .github/workflows/pythontest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythontest.yml b/.github/workflows/pythontest.yml index 49f2b32b9..21ed82ec2 100644 --- a/.github/workflows/pythontest.yml +++ b/.github/workflows/pythontest.yml @@ -4,7 +4,7 @@ on: pull_request: paths: - "salt/sensoroni/files/analyzers/**" - - "salt/manager/tools/sbin" + - "salt/manager/tools/sbin/**" jobs: build: From 80fbb313724563c4a8509565874967c863020dfa Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 11 Nov 2025 17:04:19 -0500 Subject: [PATCH 15/15] fix test --- salt/manager/tools/sbin/so-yaml_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/manager/tools/sbin/so-yaml_test.py b/salt/manager/tools/sbin/so-yaml_test.py index 0346f0c3c..f33c1300a 100644 --- a/salt/manager/tools/sbin/so-yaml_test.py +++ b/salt/manager/tools/sbin/so-yaml_test.py @@ -378,10 +378,10 @@ class TestRemove(unittest.TestCase): os.unlink(temp_file) def test_convert_file_nonexistent(self): - with patch('sys.exit', new=MagicMock()) as sysmock: + with self.assertRaises(SystemExit) as cm: with patch('sys.stderr', new=StringIO()) as mock_stderr: soyaml.convertType("file:/nonexistent/file.yaml") - sysmock.assert_called_once_with(1) + self.assertEqual(cm.exception.code, 1) self.assertIn("File '/nonexistent/file.yaml' does not exist.", mock_stderr.getvalue()) def test_get_int(self):