Compare commits

...

43 Commits

Author SHA1 Message Date
Josh Brower
c4a70b540e Merge pull request #15232 from Security-Onion-Solutions/idstools-refactor
Idstools refactor
2025-12-05 12:58:10 -05:00
DefensiveDepth
bef85772e3 Merge branch 'idstools-refactor' of https://github.com/Security-Onion-Solutions/securityonion into idstools-refactor 2025-12-05 12:17:06 -05:00
DefensiveDepth
a6b19c4a6c Remove idstools config from manager pillar file 2025-12-05 12:13:05 -05:00
Josh Brower
44f5e6659b Merge branch '2.4/dev' into idstools-refactor 2025-12-05 10:30:54 -05:00
DefensiveDepth
3f9a9b7019 tweak threshold 2025-12-05 10:23:24 -05:00
DefensiveDepth
b7ad985c7a Add cron.abset 2025-12-05 09:48:46 -05:00
Josh Brower
dba087ae25 Update version from 2.4.0-delta to 2.4.200 2025-12-05 09:43:31 -05:00
DefensiveDepth
9304513ce8 Add support for suricata rules load status 2025-12-04 12:26:13 -05:00
DefensiveDepth
f15a39c153 Add historical hashes 2025-12-03 11:24:04 -05:00
Josh Brower
822c411e83 Update version to 2.4.0-delta 2025-12-02 21:24:24 -05:00
DefensiveDepth
41b3ac7554 Backup salt master config 2025-12-02 19:58:56 -05:00
DefensiveDepth
23575fdf6c edit actual file 2025-12-02 19:19:57 -05:00
DefensiveDepth
52f70dc49a Cleanup idstools 2025-12-02 17:40:30 -05:00
DefensiveDepth
79c9749ff7 Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-12-02 17:40:04 -05:00
DefensiveDepth
8abd4c9c78 Remove idstools files 2025-12-02 12:42:15 -05:00
DefensiveDepth
c372cd533d Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-12-01 16:10:22 -05:00
DefensiveDepth
999f83ce57 Create dir earlier 2025-12-01 14:21:58 -05:00
DefensiveDepth
bda83a47a2 Remove header 2025-11-29 17:45:22 -05:00
DefensiveDepth
e96cfd35f7 Refactor for simplicity 2025-11-29 17:00:51 -05:00
DefensiveDepth
65c96b2edf Add error handling 2025-11-29 16:27:22 -05:00
DefensiveDepth
87477ae4f6 Removed uneeded bind 2025-11-29 15:40:10 -05:00
DefensiveDepth
89a9106d79 Add context 2025-11-29 15:17:28 -05:00
DefensiveDepth
1284150382 Move to manager init 2025-11-27 08:39:19 -05:00
DefensiveDepth
4bb0a7c9d9 Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-11-25 13:52:21 -05:00
DefensiveDepth
ced3af818c Refactor for Airgap 2025-11-25 13:51:50 -05:00
DefensiveDepth
148ef7ef21 add default ruleset 2025-11-18 11:57:30 -05:00
DefensiveDepth
1b55642c86 Refactor rules location 2025-11-18 09:58:14 -05:00
DefensiveDepth
af7f7d0728 Fix file paths 2025-11-17 12:00:08 -05:00
DefensiveDepth
431e5abf89 Extract ETPRO key if found 2025-11-14 09:39:33 -05:00
DefensiveDepth
f047677d8a Check correct files 2025-11-14 09:03:08 -05:00
DefensiveDepth
b2606b6094 fix perms 2025-11-13 14:10:51 -05:00
DefensiveDepth
37b3fd9b7b add detections backup 2025-11-13 10:41:12 -05:00
DefensiveDepth
573dded921 refactor to hash 2025-11-13 09:25:20 -05:00
DefensiveDepth
81d7c313af remove dupe 2025-11-12 11:11:01 -05:00
DefensiveDepth
9a6ff75793 Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-11-12 08:51:51 -05:00
DefensiveDepth
1f24796eba Fix ETPRO check 2025-11-12 08:48:47 -05:00
DefensiveDepth
55bbbdb58d idstools removal refactor 2025-11-11 14:34:28 -05:00
DefensiveDepth
3a8a6bf5ff idstools removal refactor 2025-11-11 14:12:51 -05:00
DefensiveDepth
13789bc56f idstools removal refactor 2025-11-11 13:45:37 -05:00
DefensiveDepth
11518f6eea idstools removal refactor 2025-11-11 13:41:32 -05:00
DefensiveDepth
2f6fb717c1 Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-11-06 10:38:37 -05:00
DefensiveDepth
ded520c2c1 Merge remote-tracking branch 'origin/2.4/dev' into idstools-refactor 2025-09-17 10:42:43 -04:00
DefensiveDepth
a77157391c remove idstools 2025-09-17 10:42:05 -04:00
52 changed files with 649 additions and 678 deletions

View File

@@ -43,8 +43,6 @@ base:
- secrets
- manager.soc_manager
- manager.adv_manager
- idstools.soc_idstools
- idstools.adv_idstools
- logstash.nodes
- logstash.soc_logstash
- logstash.adv_logstash
@@ -117,8 +115,6 @@ base:
- elastalert.adv_elastalert
- manager.soc_manager
- manager.adv_manager
- idstools.soc_idstools
- idstools.adv_idstools
- soc.soc_soc
- soc.adv_soc
- kibana.soc_kibana
@@ -158,8 +154,6 @@ base:
{% endif %}
- secrets
- healthcheck.standalone
- idstools.soc_idstools
- idstools.adv_idstools
- kratos.soc_kratos
- kratos.adv_kratos
- hydra.soc_hydra

View File

@@ -38,8 +38,6 @@
'hydra',
'elasticfleet',
'elastic-fleet-package-registry',
'idstools',
'suricata.manager',
'utility'
] %}

View File

@@ -25,7 +25,6 @@ container_list() {
if [ $MANAGERCHECK == 'so-import' ]; then
TRUSTED_CONTAINERS=(
"so-elasticsearch"
"so-idstools"
"so-influxdb"
"so-kibana"
"so-kratos"
@@ -49,7 +48,6 @@ container_list() {
"so-elastic-fleet-package-registry"
"so-elasticsearch"
"so-idh"
"so-idstools"
"so-influxdb"
"so-kafka"
"so-kibana"
@@ -69,7 +67,6 @@ container_list() {
)
else
TRUSTED_CONTAINERS=(
"so-idstools"
"so-elasticsearch"
"so-logstash"
"so-nginx"

View File

@@ -85,7 +85,7 @@ function suricata() {
docker run --rm \
-v /opt/so/conf/suricata/suricata.yaml:/etc/suricata/suricata.yaml:ro \
-v /opt/so/conf/suricata/threshold.conf:/etc/suricata/threshold.conf:ro \
-v /opt/so/conf/suricata/rules:/etc/suricata/rules:ro \
-v /opt/so/rules/suricata/:/etc/suricata/rules:ro \
-v ${LOG_PATH}:/var/log/suricata/:rw \
-v ${NSM_PATH}/:/nsm/:rw \
-v "$PCAP:/input.pcap:ro" \

View File

@@ -24,11 +24,6 @@ docker:
custom_bind_mounts: []
extra_hosts: []
extra_env: []
'so-idstools':
final_octet: 25
custom_bind_mounts: []
extra_hosts: []
extra_env: []
'so-influxdb':
final_octet: 26
port_bindings:

View File

@@ -41,7 +41,6 @@ docker:
forcedType: "[]string"
so-elastic-fleet: *dockerOptions
so-elasticsearch: *dockerOptions
so-idstools: *dockerOptions
so-influxdb: *dockerOptions
so-kibana: *dockerOptions
so-kratos: *dockerOptions

View File

@@ -1,65 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls.split('.')[0] in allowed_states %}
include:
- idstools.sync_files
idstoolslogdir:
file.directory:
- name: /opt/so/log/idstools
- user: 939
- group: 939
- makedirs: True
idstools_sbin:
file.recurse:
- name: /usr/sbin
- source: salt://idstools/tools/sbin
- user: 939
- group: 939
- file_mode: 755
# If this is used, exclude so-rule-update
#idstools_sbin_jinja:
# file.recurse:
# - name: /usr/sbin
# - source: salt://idstools/tools/sbin_jinja
# - user: 939
# - group: 939
# - file_mode: 755
# - template: jinja
idstools_so-rule-update:
file.managed:
- name: /usr/sbin/so-rule-update
- source: salt://idstools/tools/sbin_jinja/so-rule-update
- user: 939
- group: 939
- mode: 755
- template: jinja
suricatacustomdirsfile:
file.directory:
- name: /nsm/rules/detect-suricata/custom_file
- user: 939
- group: 939
- makedirs: True
suricatacustomdirsurl:
file.directory:
- name: /nsm/rules/detect-suricata/custom_temp
- user: 939
- group: 939
{% else %}
{{sls}}_state_not_allowed:
test.fail_without_changes:
- name: {{sls}}_state_not_allowed
{% endif %}

View File

@@ -1,10 +0,0 @@
idstools:
enabled: False
config:
urls: []
ruleset: ETOPEN
oinkcode: ""
sids:
enabled: []
disabled: []
modify: []

View File

@@ -1,31 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls.split('.')[0] in allowed_states %}
include:
- idstools.sostatus
so-idstools:
docker_container.absent:
- force: True
so-idstools_so-status.disabled:
file.comment:
- name: /opt/so/conf/so-status/so-status.conf
- regex: ^so-idstools$
so-rule-update:
cron.absent:
- identifier: so-rule-update
{% else %}
{{sls}}_state_not_allowed:
test.fail_without_changes:
- name: {{sls}}_state_not_allowed
{% endif %}

View File

@@ -1,91 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls.split('.')[0] in allowed_states %}
{% from 'docker/docker.map.jinja' import DOCKER %}
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% set proxy = salt['pillar.get']('manager:proxy') %}
include:
- idstools.config
- idstools.sostatus
so-idstools:
docker_container.running:
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-idstools:{{ GLOBALS.so_version }}
- hostname: so-idstools
- user: socore
- networks:
- sobridge:
- ipv4_address: {{ DOCKER.containers['so-idstools'].ip }}
{% if proxy %}
- environment:
- http_proxy={{ proxy }}
- https_proxy={{ proxy }}
- no_proxy={{ salt['pillar.get']('manager:no_proxy') }}
{% if DOCKER.containers['so-idstools'].extra_env %}
{% for XTRAENV in DOCKER.containers['so-idstools'].extra_env %}
- {{ XTRAENV }}
{% endfor %}
{% endif %}
{% elif DOCKER.containers['so-idstools'].extra_env %}
- environment:
{% for XTRAENV in DOCKER.containers['so-idstools'].extra_env %}
- {{ XTRAENV }}
{% endfor %}
{% endif %}
- binds:
- /opt/so/conf/idstools/etc:/opt/so/idstools/etc:ro
- /opt/so/rules/nids/suri:/opt/so/rules/nids/suri:rw
- /nsm/rules/:/nsm/rules/:rw
{% if DOCKER.containers['so-idstools'].custom_bind_mounts %}
{% for BIND in DOCKER.containers['so-idstools'].custom_bind_mounts %}
- {{ BIND }}
{% endfor %}
{% endif %}
- extra_hosts:
- {{ GLOBALS.manager }}:{{ GLOBALS.manager_ip }}
{% if DOCKER.containers['so-idstools'].extra_hosts %}
{% for XTRAHOST in DOCKER.containers['so-idstools'].extra_hosts %}
- {{ XTRAHOST }}
{% endfor %}
{% endif %}
- watch:
- file: idstoolsetcsync
- file: idstools_so-rule-update
delete_so-idstools_so-status.disabled:
file.uncomment:
- name: /opt/so/conf/so-status/so-status.conf
- regex: ^so-idstools$
so-rule-update:
cron.present:
- name: /usr/sbin/so-rule-update > /opt/so/log/idstools/download_cron.log 2>&1
- identifier: so-rule-update
- user: root
- minute: '1'
- hour: '7'
# order this last to give so-idstools container time to be ready
run_so-rule-update:
cmd.run:
- name: '/usr/sbin/so-rule-update > /opt/so/log/idstools/download_idstools_state.log 2>&1'
- require:
- docker_container: so-idstools
- onchanges:
- file: idstools_so-rule-update
- file: idstoolsetcsync
- file: synclocalnidsrules
- order: last
{% else %}
{{sls}}_state_not_allowed:
test.fail_without_changes:
- name: {{sls}}_state_not_allowed
{% endif %}

View File

@@ -1,16 +0,0 @@
{%- set disabled_sids = salt['pillar.get']('idstools:sids:disabled', {}) -%}
# idstools - disable.conf
# Example of disabling a rule by signature ID (gid is optional).
# 1:2019401
# 2019401
# Example of disabling a rule by regular expression.
# - All regular expression matches are case insensitive.
# re:hearbleed
# re:MS(0[7-9]|10)-\d+
{%- if disabled_sids != None %}
{%- for sid in disabled_sids %}
{{ sid }}
{%- endfor %}
{%- endif %}

View File

@@ -1,16 +0,0 @@
{%- set enabled_sids = salt['pillar.get']('idstools:sids:enabled', {}) -%}
# idstools-rulecat - enable.conf
# Example of enabling a rule by signature ID (gid is optional).
# 1:2019401
# 2019401
# Example of enabling a rule by regular expression.
# - All regular expression matches are case insensitive.
# re:hearbleed
# re:MS(0[7-9]|10)-\d+
{%- if enabled_sids != None %}
{%- for sid in enabled_sids %}
{{ sid }}
{%- endfor %}
{%- endif %}

View File

@@ -1,12 +0,0 @@
{%- set modify_sids = salt['pillar.get']('idstools:sids:modify', {}) -%}
# idstools-rulecat - modify.conf
# Format: <sid> "<from>" "<to>"
# Example changing the seconds for rule 2019401 to 3600.
#2019401 "seconds \d+" "seconds 3600"
{%- if modify_sids != None %}
{%- for sid in modify_sids %}
{{ sid }}
{%- endfor %}
{%- endif %}

View File

@@ -1,23 +0,0 @@
{%- from 'vars/globals.map.jinja' import GLOBALS -%}
{%- from 'soc/merged.map.jinja' import SOCMERGED -%}
--suricata-version=7.0.3
--merged=/opt/so/rules/nids/suri/all.rules
--output=/nsm/rules/detect-suricata/custom_temp
--local=/opt/so/rules/nids/suri/local.rules
{%- if GLOBALS.md_engine == "SURICATA" %}
--local=/opt/so/rules/nids/suri/extraction.rules
--local=/opt/so/rules/nids/suri/filters.rules
{%- endif %}
--url=http://{{ GLOBALS.manager }}:7788/suricata/emerging-all.rules
--disable=/opt/so/idstools/etc/disable.conf
--enable=/opt/so/idstools/etc/enable.conf
--modify=/opt/so/idstools/etc/modify.conf
{%- if SOCMERGED.config.server.modules.suricataengine.customRulesets %}
{%- for ruleset in SOCMERGED.config.server.modules.suricataengine.customRulesets %}
{%- if 'url' in ruleset %}
--url={{ ruleset.url }}
{%- elif 'file' in ruleset %}
--local={{ ruleset.file }}
{%- endif %}
{%- endfor %}
{%- endif %}

View File

@@ -1,13 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'idstools/map.jinja' import IDSTOOLSMERGED %}
include:
{% if IDSTOOLSMERGED.enabled %}
- idstools.enabled
{% else %}
- idstools.disabled
{% endif %}

View File

@@ -1,7 +0,0 @@
{# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
https://securityonion.net/license; you may not use this file except in compliance with the
Elastic License 2.0. #}
{% import_yaml 'idstools/defaults.yaml' as IDSTOOLSDEFAULTS with context %}
{% set IDSTOOLSMERGED = salt['pillar.get']('idstools', IDSTOOLSDEFAULTS.idstools, merge=True) %}

View File

@@ -1 +0,0 @@
# Add your custom Suricata rules in this file.

View File

@@ -1,72 +0,0 @@
idstools:
enabled:
description: Enables or disables the IDStools process which is used by the Detection system.
config:
oinkcode:
description: Enter your registration code or oinkcode for paid NIDS rulesets.
title: Registration Code
global: True
forcedType: string
helpLink: rules.html
ruleset:
description: 'Defines the ruleset you want to run. Options are ETOPEN or ETPRO. Once you have changed the ruleset here, you will need to wait for the rule update to take place (every 24 hours), or you can force the update by nagivating to Detections --> Options dropdown menu --> Suricata --> Full Update. WARNING! Changing the ruleset will remove all existing non-overlapping Suricata rules of the previous ruleset and their associated overrides. This removal cannot be undone.'
global: True
regex: ETPRO\b|ETOPEN\b
helpLink: rules.html
urls:
description: This is a list of additional rule download locations. This feature is currently disabled.
global: True
multiline: True
forcedType: "[]string"
readonly: True
helpLink: rules.html
sids:
disabled:
description: Contains the list of NIDS rules (or regex patterns) disabled across the grid. This setting is readonly; Use the Detections screen to disable rules.
global: True
multiline: True
forcedType: "[]string"
regex: \d*|re:.*
helpLink: managing-alerts.html
readonlyUi: True
advanced: true
enabled:
description: Contains the list of NIDS rules (or regex patterns) enabled across the grid. This setting is readonly; Use the Detections screen to enable rules.
global: True
multiline: True
forcedType: "[]string"
regex: \d*|re:.*
helpLink: managing-alerts.html
readonlyUi: True
advanced: true
modify:
description: Contains the list of NIDS rules (SID "REGEX_SEARCH_TERM" "REGEX_REPLACE_TERM"). This setting is readonly; Use the Detections screen to modify rules.
global: True
multiline: True
forcedType: "[]string"
helpLink: managing-alerts.html
readonlyUi: True
advanced: true
rules:
local__rules:
description: Contains the list of custom NIDS rules applied to the grid. This setting is readonly; Use the Detections screen to adjust rules.
file: True
global: True
advanced: True
title: Local Rules
helpLink: local-rules.html
readonlyUi: True
filters__rules:
description: If you are using Suricata for metadata, then you can set custom filters for that metadata here.
file: True
global: True
advanced: True
title: Filter Rules
helpLink: suricata.html
extraction__rules:
description: If you are using Suricata for metadata, then you can set a list of MIME types for file extraction here.
file: True
global: True
advanced: True
title: Extraction Rules
helpLink: suricata.html

View File

@@ -1,21 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls.split('.')[0] in allowed_states %}
append_so-idstools_so-status.conf:
file.append:
- name: /opt/so/conf/so-status/so-status.conf
- text: so-idstools
- unless: grep -q so-idstools /opt/so/conf/so-status/so-status.conf
{% else %}
{{sls}}_state_not_allowed:
test.fail_without_changes:
- name: {{sls}}_state_not_allowed
{% endif %}

View File

@@ -1,37 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
idstoolsdir:
file.directory:
- name: /opt/so/conf/idstools/etc
- user: 939
- group: 939
- makedirs: True
idstoolsetcsync:
file.recurse:
- name: /opt/so/conf/idstools/etc
- source: salt://idstools/etc
- user: 939
- group: 939
- template: jinja
rulesdir:
file.directory:
- name: /opt/so/rules/nids/suri
- user: 939
- group: 939
- makedirs: True
# Don't show changes because all.rules can be large
synclocalnidsrules:
file.recurse:
- name: /opt/so/rules/nids/suri/
- source: salt://idstools/rules/
- user: 939
- group: 939
- show_changes: False
- include_pat: 'E@.rules'

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
. /usr/sbin/so-common
/usr/sbin/so-restart idstools $1

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
. /usr/sbin/so-common
/usr/sbin/so-start idstools $1

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
. /usr/sbin/so-common
/usr/sbin/so-stop idstools $1

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# if this script isn't already running
if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
. /usr/sbin/so-common
{%- from 'vars/globals.map.jinja' import GLOBALS %}
{%- from 'idstools/map.jinja' import IDSTOOLSMERGED %}
{%- set proxy = salt['pillar.get']('manager:proxy') %}
{%- set noproxy = salt['pillar.get']('manager:no_proxy', '') %}
{%- if proxy %}
# Download the rules from the internet
export http_proxy={{ proxy }}
export https_proxy={{ proxy }}
export no_proxy="{{ noproxy }}"
{%- endif %}
mkdir -p /nsm/rules/suricata
chown -R socore:socore /nsm/rules/suricata
{%- if not GLOBALS.airgap %}
# Download the rules from the internet
{%- if IDSTOOLSMERGED.config.ruleset == 'ETOPEN' %}
docker exec so-idstools idstools-rulecat -v --suricata-version 7.0.3 -o /nsm/rules/suricata/ --merged=/nsm/rules/suricata/emerging-all.rules --force
{%- elif IDSTOOLSMERGED.config.ruleset == 'ETPRO' %}
docker exec so-idstools idstools-rulecat -v --suricata-version 7.0.3 -o /nsm/rules/suricata/ --merged=/nsm/rules/suricata/emerging-all.rules --force --etpro={{ IDSTOOLSMERGED.config.oinkcode }}
{%- endif %}
{%- endif %}
argstr=""
for arg in "$@"; do
argstr="${argstr} \"${arg}\""
done
docker exec so-idstools /bin/bash -c "cd /opt/so/idstools/etc && idstools-rulecat --force ${argstr}"
fi

View File

@@ -1,15 +1,5 @@
logrotate:
config:
/opt/so/log/idstools/*_x_log:
- daily
- rotate 14
- missingok
- copytruncate
- compress
- create
- extension .log
- dateext
- dateyesterday
/opt/so/log/nginx/*_x_log:
- daily
- rotate 14

View File

@@ -1,12 +1,5 @@
logrotate:
config:
"/opt/so/log/idstools/*_x_log":
description: List of logrotate options for this file.
title: /opt/so/log/idstools/*.log
advanced: True
multiline: True
global: True
forcedType: "[]string"
"/opt/so/log/nginx/*_x_log":
description: List of logrotate options for this file.
title: /opt/so/log/nginx/*.log

View File

@@ -206,10 +206,33 @@ git_config_set_safe_dirs:
- multivar:
- /nsm/rules/custom-local-repos/local-sigma
- /nsm/rules/custom-local-repos/local-yara
- /nsm/rules/custom-local-repos/local-suricata
- /nsm/securityonion-resources
- /opt/so/conf/soc/ai_summary_repos/securityonion-resources
- /nsm/airgap-resources/playbooks
- /opt/so/conf/soc/playbooks
surinsmrulesdir:
file.directory:
- name: /nsm/rules/suricata
- user: 939
- group: 939
- makedirs: True
suriextractionrules:
file.managed:
- name: /nsm/rules/suricata/so_extraction.rules
- source: salt://suricata/files/so_extraction.rules
- user: 939
- group: 939
surifiltersrules:
file.managed:
- name: /nsm/rules/suricata/so_filters.rules
- source: salt://suricata/files/so_filters.rules
- user: 939
- group: 939
{% else %}
{{sls}}_state_not_allowed:

View File

@@ -604,16 +604,6 @@ function add_kratos_to_minion() {
fi
}
function add_idstools_to_minion() {
printf '%s\n'\
"idstools:"\
" enabled: True"\
" " >> $PILLARFILE
if [ $? -ne 0 ]; then
log "ERROR" "Failed to add idstools configuration to $PILLARFILE"
return 1
fi
}
function add_elastic_fleet_package_registry_to_minion() {
printf '%s\n'\
@@ -741,7 +731,6 @@ function createEVAL() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}
@@ -762,7 +751,6 @@ function createSTANDALONE() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}
@@ -779,7 +767,6 @@ function createMANAGER() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}
@@ -796,7 +783,6 @@ function createMANAGERSEARCH() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}
@@ -811,7 +797,6 @@ function createIMPORT() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}
@@ -896,7 +881,6 @@ function createMANAGERHYPE() {
add_soc_to_minion || return 1
add_registry_to_minion || return 1
add_kratos_to_minion || return 1
add_idstools_to_minion || return 1
add_elastic_fleet_package_registry_to_minion || return 1
}

View File

@@ -639,7 +639,9 @@ post_to_2.4.190() {
}
post_to_2.4.200() {
echo "Nothing to apply"
echo "Initiating Suricata idstools migration..."
suricata_idstools_removal_post
POSTVERSION=2.4.200
}
@@ -911,6 +913,9 @@ up_to_2.4.190() {
}
up_to_2.4.200() {
echo "Backing up idstools config..."
suricata_idstools_removal_pre
touch /opt/so/state/esfleet_logstash_config_pillar
INSTALLEDVERSION=2.4.200
@@ -999,6 +1004,8 @@ rollover_index() {
}
suricata_idstools_migration() {
# For 2.4.70
#Backup the pillars for idstools
mkdir -p /nsm/backup/detections-migration/idstools
rsync -av /opt/so/saltstack/local/pillar/idstools/* /nsm/backup/detections-migration/idstools
@@ -1099,6 +1106,209 @@ playbook_migration() {
echo "Playbook Migration is complete...."
}
suricata_idstools_removal_pre() {
# For SOUPs beginning with 2.4.200 - pre SOUP checks
# Create syncBlock file
install -d -o 939 -g 939 -m 755 /opt/so/conf/soc/fingerprints
install -o 939 -g 939 -m 644 /dev/null /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
cat > /opt/so/conf/soc/fingerprints/suricataengine.syncBlock << EOF
Suricata ruleset sync is blocked until this file is removed. Make sure that you have manually added any custom Suricata rulesets via SOC config - review the documentation for more details: securityonion.net/docs
EOF
# Backup custom rules & overrides
mkdir -p /nsm/backup/detections-migration/2-4-200
cp /usr/sbin/so-rule-update /nsm/backup/detections-migration/2-4-200
cp /opt/so/conf/idstools/etc/rulecat.conf /nsm/backup/detections-migration/2-4-200
if [[ -f /opt/so/conf/soc/so-detections-backup.py ]]; then
python3 /opt/so/conf/soc/so-detections-backup.py
# Verify backup by comparing counts
echo "Verifying detection overrides backup..."
es_override_count=$(/sbin/so-elasticsearch-query 'so-detection/_count' \
-d '{"query": {"bool": {"must": [{"exists": {"field": "so_detection.overrides"}}]}}}' | jq -r '.count') || {
echo " Error: Failed to query Elasticsearch for override count"
exit 1
}
if [[ ! "$es_override_count" =~ ^[0-9]+$ ]]; then
echo " Error: Invalid override count from Elasticsearch: '$es_override_count'"
exit 1
fi
backup_override_count=$(find /nsm/backup/detections/repo/*/overrides -type f 2>/dev/null | wc -l)
echo " Elasticsearch overrides: $es_override_count"
echo " Backed up overrides: $backup_override_count"
if [[ "$es_override_count" -gt 0 ]]; then
if [[ "$backup_override_count" -gt 0 ]]; then
echo " Override backup verified successfully"
else
echo " Error: Elasticsearch has $es_override_count overrides but backup has 0 files"
exit 1
fi
else
echo " No overrides to backup"
fi
else
echo "SOC Detections backup script not found, skipping detection backup"
fi
}
suricata_idstools_removal_post() {
# For SOUPs beginning with 2.4.200 - post SOUP checks
echo "Checking idstools configuration for custom modifications..."
# Normalize and hash file content for consistent comparison
# Args: $1 - file path
# Outputs: SHA256 hash to stdout
# Returns: 0 on success, 1 on failure
hash_normalized_file() {
local file="$1"
if [[ ! -r "$file" ]]; then
return 1
fi
sed -E \
-e 's/^[[:space:]]+//; s/[[:space:]]+$//' \
-e '/^$/d' \
-e 's|--url=http://[^:]+:7788|--url=http://MANAGER:7788|' \
"$file" | sha256sum | awk '{print $1}'
}
# Known-default hashes for so-rule-update (ETOPEN ruleset)
KNOWN_SO_RULE_UPDATE_HASHES=(
# 2.4.100+ (suricata 7.0.3, non-airgap)
"5fbd067ced86c8ec72ffb7e1798aa624123b536fb9d78f4b3ad8d3b45db1eae7" # 2.4.100-2.4.190 non-Airgap
# 2.4.90+ airgap (same for 2.4.90 and 2.4.100+)
"61f632c55791338c438c071040f1490066769bcce808b595b5cc7974a90e653a" # 2.4.90+ Airgap
# 2.4.90 (suricata 6.0, non-airgap, comment inside proxy block)
"0380ec52a05933244ab0f0bc506576e1d838483647b40612d5fe4b378e47aedd" # 2.4.90 non-Airgap
# 2.4.10-2.4.80 (suricata 6.0, non-airgap, comment outside proxy block)
"b6e4d1b5a78d57880ad038a9cd2cc6978aeb2dd27d48ea1a44dd866a2aee7ff4" # 2.4.10-2.4.80 non-Airgap
# 2.4.10-2.4.80 airgap
"b20146526ace2b142fde4664f1386a9a1defa319b3a1d113600ad33a1b037dad" # 2.4.10-2.4.80 Airgap
# 2.4.5 and earlier (no pidof check, non-airgap)
"d04f5e4015c348133d28a7840839e82d60009781eaaa1c66f7f67747703590dc" # 2.4.5 non-Airgap
)
# Known-default hashes for rulecat.conf
KNOWN_RULECAT_CONF_HASHES=(
# 2.4.100+ (suricata 7.0.3)
"302e75dca9110807f09ade2eec3be1fcfc8b2bf6cf2252b0269bb72efeefe67e" # 2.4.100-2.4.190 without SURICATA md_engine
"8029b7718c324a9afa06a5cf180afde703da1277af4bdd30310a6cfa3d6398cb" # 2.4.100-2.4.190 with SURICATA md_engine
# 2.4.80-2.4.90 (suricata 6.0, with --suricata-version and --output)
"4d8b318e6950a6f60b02f307cf27c929efd39652990c1bd0c8820aa8a307e1e7" # 2.4.80-2.4.90 without SURICATA md_engine
"a1ddf264c86c4e91c81c5a317f745a19466d4311e4533ec3a3c91fed04c11678" # 2.4.80-2.4.90 with SURICATA md_engine
# 2.4.50-2.4.70 (/suri/ path, no --suricata-version)
"86e3afb8d0f00c62337195602636864c98580a13ca9cc85029661a539deae6ae" # 2.4.50-2.4.70 without SURICATA md_engine
"5a97604ca5b820a10273a2d6546bb5e00c5122ca5a7dfe0ba0bfbce5fc026f4b" # 2.4.50-2.4.70 with SURICATA md_engine
# 2.4.20-2.4.40 (/nids/ path without /suri/)
"d098ea9ecd94b5cca35bf33543f8ea8f48066a0785221fabda7fef43d2462c29" # 2.4.20-2.4.40 without SURICATA md_engine
"9dbc60df22ae20d65738ba42e620392577857038ba92278e23ec182081d191cd" # 2.4.20-2.4.40 with SURICATA md_engine
# 2.4.5-2.4.10 (/sorules/ path for extraction/filters)
"490f6843d9fca759ee74db3ada9c702e2440b8393f2cfaf07bbe41aaa6d955c3" # 2.4.5-2.4.10 with SURICATA md_engine
# Note: 2.4.5-2.4.10 without SURICATA md_engine has same hash as 2.4.20-2.4.40 without SURICATA md_engine
)
# Check a config file against known hashes
# Args: $1 - file path, $2 - array name of known hashes
check_config_file() {
local file="$1"
local known_hashes_array="$2"
local file_display_name=$(basename "$file")
if [[ ! -f "$file" ]]; then
echo "Warning: $file not found"
echo "$file_display_name not found - manual verification required" >> /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
return 1
fi
echo "Hashing $file..."
local file_hash
if ! file_hash=$(hash_normalized_file "$file"); then
echo "Warning: Could not read $file"
echo "$file_display_name not readable - manual verification required" >> /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
return 1
fi
echo " Hash: $file_hash"
# Check if hash matches any known default
local -n known_hashes=$known_hashes_array
for known_hash in "${known_hashes[@]}"; do
if [[ "$file_hash" == "$known_hash" ]]; then
echo " Matches known default configuration"
return 0
fi
done
# No match - custom configuration detected
echo "Does not match known default - custom configuration detected"
echo "Custom $file_display_name detected (hash: $file_hash)" >> /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
# If this is so-rule-update, check for ETPRO license code and write out to the syncBlock file
# If ETPRO is enabled, the license code already exists in the so-rule-update script, this is just making it easier to migrate
if [[ "$file_display_name" == "so-rule-update" ]]; then
local etpro_code
etpro_code=$(grep -oP '\-\-etpro=\K[0-9a-fA-F]+' "$file" 2>/dev/null) || true
if [[ -n "$etpro_code" ]]; then
echo "ETPRO code found: $etpro_code" >> /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
fi
fi
return 1
}
# Check so-rule-update and rulecat.conf
SO_RULE_UPDATE="/usr/sbin/so-rule-update"
RULECAT_CONF="/opt/so/conf/idstools/etc/rulecat.conf"
custom_found=0
check_config_file "$SO_RULE_UPDATE" "KNOWN_SO_RULE_UPDATE_HASHES" || custom_found=1
check_config_file "$RULECAT_CONF" "KNOWN_RULECAT_CONF_HASHES" || custom_found=1
# If no custom configs found, remove syncBlock
if [[ $custom_found -eq 0 ]]; then
echo "idstools migration completed successfully - removing Suricata engine syncBlock"
rm -f /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
else
echo "Custom idstools configuration detected - syncBlock remains in place"
echo "Review /opt/so/conf/soc/fingerprints/suricataengine.syncBlock for details"
fi
echo "Cleaning up idstools"
echo "Stopping and removing the idstools container..."
if [ -n "$(docker ps -q -f name=^so-idstools$)" ]; then
image_name=$(docker ps -a --filter name=^so-idstools$ --format '{{.Image}}' 2>/dev/null || true)
docker stop so-idstools || echo "Warning: failed to stop so-idstools container"
docker rm so-idstools || echo "Warning: failed to remove so-idstools container"
if [[ -n "$image_name" ]]; then
echo "Removing idstools image: $image_name"
docker rmi "$image_name" || echo "Warning: failed to remove image $image_name"
fi
fi
echo "Removing idstools symlink and scripts..."
rm /opt/so/saltstack/local/salt/suricata/rules
rm -rf /usr/sbin/so-idstools*
sed -i '/^#\?so-idstools$/d' /opt/so/conf/so-status/so-status.conf
# Backup the salt master config & manager pillar before editing it
cp /opt/so/saltstack/local/pillar/minions/$MINIONID.sls /nsm/backup/detections-migration/2-4-200/
cp /etc/salt/master /nsm/backup/detections-migration/2-4-200/
so-yaml.py remove /opt/so/saltstack/local/pillar/minions/$MINIONID.sls idstools
so-yaml.py removelistitem /etc/salt/master file_roots.base /opt/so/rules/nids
}
determine_elastic_agent_upgrade() {
if [[ $is_airgap -eq 0 ]]; then
update_elastic_agent_airgap

View File

@@ -6,30 +6,6 @@ engines:
interval: 60
- pillarWatch:
fpa:
- files:
- /opt/so/saltstack/local/pillar/idstools/soc_idstools.sls
- /opt/so/saltstack/local/pillar/idstools/adv_idstools.sls
pillar: idstools.config.ruleset
default: ETOPEN
actions:
from:
'*':
to:
'*':
- cmd.run:
cmd: /usr/sbin/so-rule-update
- files:
- /opt/so/saltstack/local/pillar/idstools/soc_idstools.sls
- /opt/so/saltstack/local/pillar/idstools/adv_idstools.sls
pillar: idstools.config.oinkcode
default: ''
actions:
from:
'*':
to:
'*':
- cmd.run:
cmd: /usr/sbin/so-rule-update
- files:
- /opt/so/saltstack/local/pillar/global/soc_global.sls
- /opt/so/saltstack/local/pillar/global/adv_global.sls

View File

@@ -215,7 +215,6 @@ socsensoronirepos:
- mode: 775
- makedirs: True
create_custom_local_yara_repo_template:
git.present:
- name: /nsm/rules/custom-local-repos/local-yara
@@ -249,6 +248,39 @@ add_readme_custom_local_sigma_repo_template:
- context:
repo_type: "sigma"
create_custom_local_suricata_repo_template:
git.present:
- name: /nsm/rules/custom-local-repos/local-suricata
- bare: False
- force: True
add_readme_custom_local_suricata_repo_template:
file.managed:
- name: /nsm/rules/custom-local-repos/local-suricata/README
- source: salt://soc/files/soc/detections_custom_repo_template_readme.jinja
- user: 939
- group: 939
- template: jinja
- context:
repo_type: "suricata"
etpro_airgap_folder:
file.directory:
- name: /nsm/rules/custom-local-repos/local-etpro-suricata
- user: 939
- group: 939
- makedirs: True
add_readme_etpro_airgap_template:
file.managed:
- name: /nsm/rules/custom-local-repos/local-etpro-suricata/README
- source: salt://soc/files/soc/detections_custom_repo_template_readme.jinja
- user: 939
- group: 939
- template: jinja
- context:
repo_type: "suricata-etpro"
socore_own_custom_repos:
file.directory:
- name: /nsm/rules/custom-local-repos/

View File

@@ -1563,12 +1563,106 @@ soc:
disableRegex: []
enableRegex: []
failAfterConsecutiveErrorCount: 10
communityRulesFile: /nsm/rules/suricata/emerging-all.rules
rulesFingerprintFile: /opt/sensoroni/fingerprints/emerging-all.fingerprint
stateFilePath: /opt/sensoroni/fingerprints/suricataengine.state
integrityCheckFrequencySeconds: 1200
ignoredSidRanges:
- '1100000-1101000'
rulesetSources:
default:
- name: Emerging-Threats
description: "Emerging Threats ruleset - To enable ET Pro, enter your license key below. Leave empty for ET Open (free) rules."
licenseKey: ""
enabled: true
sourceType: url
sourcePath: 'https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz'
urlHash: "https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz.md5"
license: "BSD"
excludeFiles:
- "*deleted*"
- "*retired*"
proxyURL: ""
proxyUsername: ""
proxyPassword: ""
proxyCACert: ""
insecureSkipVerify: false
readOnly: true
deleteUnreferenced: true
- name: ABUSECH-SSLBL
deleteUnreferenced: true
description: 'Abuse.ch SSL Blacklist'
enabled: false
license: CC0-1.0
readOnly: true
sourcePath: https://sslbl.abuse.ch/blacklist/sslblacklist_tls_cert.tar.gz
sourceType: url
- name: local-rules
description: "Local rules from files (*.rules) in a directory on the filesystem"
license: "custom"
sourceType: directory
sourcePath: /nsm/rules/custom-local-repos/local-suricata
readOnly: false
deleteUnreferenced: false
enabled: true
- name: SO_FILTERS
deleteUnreferenced: true
description: Filter rules for when Suricata is set as the metadata engine
enabled: false
license: Elastic-2.0
readOnly: true
sourcePath: /nsm/rules/suricata/so_filters.rules
sourceType: directory
- name: SO_EXTRACTIONS
description: Extraction rules for when Suricata is set as the metadata engine
deleteUnreferenced: true
enabled: false
license: Elastic-2.0
readOnly: true
sourcePath: /nsm/rules/suricata/so_extraction.rules
sourceType: directory
airgap:
- name: Emerging-Threats
description: "Emerging Threats ruleset - To enable ET Pro, enter your license key below. Leave empty for ET Open (free) rules."
licenseKey: ""
enabled: true
sourceType: url
sourcePath: 'https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz'
urlHash: "https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz.md5"
license: "BSD"
excludeFiles:
- "*deleted*"
- "*retired*"
proxyURL: ""
proxyUsername: ""
proxyPassword: ""
proxyCACert: ""
insecureSkipVerify: false
readOnly: true
deleteUnreferenced: true
- name: local-rules
description: "Local rules from files (*.rules) in a directory on the filesystem"
license: "custom"
sourceType: directory
sourcePath: /nsm/rules/custom-local-repos/local-suricata
readOnly: false
deleteUnreferenced: false
enabled: true
- name: SO_FILTERS
deleteUnreferenced: true
description: Filter rules for when Suricata is set as the metadata engine
enabled: false
license: Elastic-2.0
readOnly: true
sourcePath: /nsm/rules/suricata/so_filters.rules
sourceType: directory
- name: SO_EXTRACTIONS
description: Extraction rules for when Suricata is set as the metadata engine
deleteUnreferenced: true
enabled: false
license: Elastic-2.0
readOnly: true
sourcePath: /nsm/rules/suricata/so_extraction.rules
sourceType: directory
navigator:
intervalMinutes: 30
outputPath: /opt/sensoroni/navigator

View File

@@ -27,7 +27,8 @@ so-soc:
- /opt/so/conf/strelka:/opt/sensoroni/yara:rw
- /opt/so/conf/sigma:/opt/sensoroni/sigma:rw
- /opt/so/rules/elastalert/rules:/opt/sensoroni/elastalert:rw
- /opt/so/rules/nids/suri:/opt/sensoroni/nids:ro
- /opt/so/saltstack/local/salt/suricata/rules:/opt/sensoroni/suricata/rules:rw
- /opt/so/saltstack/local/salt/suricata/files:/opt/sensoroni/suricata/threshold:rw
- /opt/so/conf/soc/fingerprints:/opt/sensoroni/fingerprints:rw
- /nsm/soc/jobs:/opt/sensoroni/jobs:rw
- /nsm/soc/uploads:/nsm/soc/uploads:rw

View File

@@ -45,6 +45,61 @@ Finally, commit it:
The next time the Strelka / YARA engine syncs, the new rule should be imported
If there are errors, review the sync log to troubleshoot further.
{% elif repo_type == 'suricata' %}
# Suricata Local Custom Rules Repository
This folder has already been initialized as a git repo
and your Security Onion grid is configured to import any Suricata rule files found here.
Just add your rule file and commit it.
For example:
** Note: If this is your first time making changes to this repo, you may run into the following error:
fatal: detected dubious ownership in repository at '/nsm/rules/custom-local-repos/local-suricata'
To add an exception for this directory, call:
git config --global --add safe.directory /nsm/rules/custom-local-repos/local-suricata
This means that the user you are running commands as does not match the user that is used for this git repo (socore).
You will need to make sure your rule files are accessible to the socore user, so either su to socore
or add the exception and then chown the rule files later.
Also, you will be asked to set some configuration:
```
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
```
Run these commands, ommitting the `--global`.
With that out of the way:
First, create the rule file with a .rules extension:
`vi my_custom_rules.rules`
Next, use git to stage the new rule to be committed:
`git add my_custom_rules.rules`
Finally, commit it:
`git commit -m "Initial commit of my_custom_rule.rules"`
The next time the Suricata engine syncs, the new rule/s should be imported
If there are errors, review the sync log to troubleshoot further.
{% elif repo_type == 'suricata-etpro' %}
# Suricata ETPRO - Airgap
This folder has been initialized for use with ETPRO during Airgap deployment.
Just add your ETPRO rule/s file to this folder and the Suricata engine will import them.
If there are errors, review the sync log to troubleshoot further.
{% elif repo_type == 'sigma' %}
# Sigma Local Custom Rules Repository

View File

@@ -50,17 +50,86 @@
{% do SOCMERGED.config.server.modules.elastalertengine.update({'enabledSigmaRules': SOCMERGED.config.server.modules.elastalertengine.enabledSigmaRules.default}) %}
{% endif %}
{# set elastalertengine.rulesRepos and strelkaengine.rulesRepos based on airgap or not #}
{# set elastalertengine.rulesRepos, strelkaengine.rulesRepos, and suricataengine.rulesetSources based on airgap or not #}
{% if GLOBALS.airgap %}
{% do SOCMERGED.config.server.modules.elastalertengine.update({'rulesRepos': SOCMERGED.config.server.modules.elastalertengine.rulesRepos.airgap}) %}
{% do SOCMERGED.config.server.modules.strelkaengine.update({'rulesRepos': SOCMERGED.config.server.modules.strelkaengine.rulesRepos.airgap}) %}
{#% if SOCMERGED.config.server.modules.suricataengine.rulesetSources is mapping %#}
{% do SOCMERGED.config.server.modules.suricataengine.update({'rulesetSources': SOCMERGED.config.server.modules.suricataengine.rulesetSources.airgap}) %}
{#% endif %#}
{% do SOCMERGED.config.server.update({'airgapEnabled': true}) %}
{% else %}
{% do SOCMERGED.config.server.modules.elastalertengine.update({'rulesRepos': SOCMERGED.config.server.modules.elastalertengine.rulesRepos.default}) %}
{% do SOCMERGED.config.server.modules.strelkaengine.update({'rulesRepos': SOCMERGED.config.server.modules.strelkaengine.rulesRepos.default}) %}
{#% if SOCMERGED.config.server.modules.suricataengine.rulesetSources is mapping %#}
{% do SOCMERGED.config.server.modules.suricataengine.update({'rulesetSources': SOCMERGED.config.server.modules.suricataengine.rulesetSources.default}) %}
{#% endif %#}
{% do SOCMERGED.config.server.update({'airgapEnabled': false}) %}
{% endif %}
{# Define the Detections custom ruleset that should always be present #}
{% set CUSTOM_RULESET = {
'name': 'custom',
'description': 'User-created custom rules created via the Detections module in the SOC UI',
'sourceType': 'elasticsearch',
'sourcePath': 'so_detection.ruleset:__custom__',
'readOnly': false,
'deleteUnreferenced': false,
'license': 'Custom',
'enabled': true
} %}
{# Always append the custom ruleset to suricataengine.rulesetSources if not already present #}
{% if SOCMERGED.config.server.modules.suricataengine is defined and SOCMERGED.config.server.modules.suricataengine.rulesetSources is defined %}
{% if SOCMERGED.config.server.modules.suricataengine.rulesetSources is not mapping %}
{% set custom_names = SOCMERGED.config.server.modules.suricataengine.rulesetSources | selectattr('name', 'equalto', 'custom') | list %}
{% if custom_names | length == 0 %}
{% do SOCMERGED.config.server.modules.suricataengine.rulesetSources.append(CUSTOM_RULESET) %}
{% endif %}
{% endif %}
{% endif %}
{# Enable SO_FILTERS and SO_EXTRACTIONS when Suricata is the metadata engine #}
{% if SOCMERGED.config.server.modules.suricataengine is defined and SOCMERGED.config.server.modules.suricataengine.rulesetSources is defined %}
{% if SOCMERGED.config.server.modules.suricataengine.rulesetSources is not mapping %}
{% for ruleset in SOCMERGED.config.server.modules.suricataengine.rulesetSources %}
{% if ruleset.name in ['SO_FILTERS', 'SO_EXTRACTIONS'] and GLOBALS.md_engine == 'SURICATA' %}
{% do ruleset.update({'enabled': true}) %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{# Transform Emerging-Threats ruleset based on license key #}
{% if SOCMERGED.config.server.modules.suricataengine is defined and SOCMERGED.config.server.modules.suricataengine.rulesetSources is defined %}
{% if SOCMERGED.config.server.modules.suricataengine.rulesetSources is not mapping %}
{% for ruleset in SOCMERGED.config.server.modules.suricataengine.rulesetSources %}
{% if ruleset.name == 'Emerging-Threats' %}
{% if ruleset.licenseKey and ruleset.licenseKey != '' %}
{# License key is defined - transform to ETPRO #}
{# Engine Version is hardcoded in the URL - this does not change often: https://community.emergingthreats.net/t/supported-engines/71 #}
{% do ruleset.update({
'name': 'ETPRO',
'sourcePath': 'https://rules.emergingthreatspro.com/' ~ ruleset.licenseKey ~ '/suricata-7.0.3/etpro.rules.tar.gz',
'urlHash': 'https://rules.emergingthreatspro.com/' ~ ruleset.licenseKey ~ '/suricata-7.0.3/etpro.rules.tar.gz.md5',
'license': 'Commercial'
}) %}
{% else %}
{# No license key - explicitly set to ETOPEN #}
{% do ruleset.update({
'name': 'ETOPEN',
'sourcePath': 'https://rules.emergingthreats.net/open/suricata-7.0.3/emerging.rules.tar.gz',
'urlHash': 'https://rules.emergingthreats.net/open/suricata-7.0.3/emerging.rules.tar.gz.md5',
'license': 'BSD'
}) %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{# set playbookRepos based on airgap or not #}
{% if GLOBALS.airgap %}
{% do SOCMERGED.config.server.modules.playbook.update({'playbookRepos': SOCMERGED.config.server.modules.playbook.playbookRepos.airgap}) %}

View File

@@ -563,6 +563,52 @@ soc:
advanced: True
forcedType: "[]string"
helpLink: detections.html#rule-engine-status
rulesetSources:
default: &serulesetSources
description: "Ruleset sources for Suricata rules. Supports URL downloads and local directories. Refer to the linked documentation for details on how to configure this setting."
global: True
advanced: False
forcedType: "[]{}"
helpLink: suricata.html
syntax: json
uiElements:
- field: name
label: Ruleset Name (This will be the name of the ruleset in the UI)
required: True
readonly: True
- field: description
label: Description
- field: enabled
label: Enabled (If false, existing rules & overrides will be removed)
forcedType: bool
required: True
- field: licenseKey
label: License Key
required: False
- field: sourceType
label: Source Type
required: True
options:
- url
- directory
- field: sourcePath
label: Source Path (full url or directory path)
required: True
- field: excludeFiles
label: Exclude Files (list of file names to exclude, separated by commas)
required: False
- field: license
label: Ruleset License
required: True
- field: readOnly
label: Read Only (Prevents changes to the rule itself - can still be enabled/disabled/tuned)
forcedType: bool
required: False
- field: deleteUnreferenced
label: Delete Unreferenced (Deletes rules that are no longer referenced by ruleset source)
forcedType: bool
required: False
airgap: *serulesetSources
navigator:
intervalMinutes:
description: How often to generate the Navigator Layers. (minutes)

View File

@@ -10,12 +10,6 @@
{% from 'suricata/map.jinja' import SURICATAMERGED %}
{% from 'bpf/suricata.map.jinja' import SURICATABPF, SURICATA_BPF_STATUS, SURICATA_BPF_CALC %}
suridir:
file.directory:
- name: /opt/so/conf/suricata
- user: 940
- group: 940
{% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC %}
# BPF compilation and configuration
@@ -28,6 +22,14 @@ suriPCAPbpfcompilationfailure:
{% endif %}
{% endif %}
suridir:
file.directory:
- name: /opt/so/conf/suricata
- user: 940
- group: 939
- mode: 775
- makedirs: True
# BPF applied to all of Suricata - alerts/metadata/pcap
suribpf:
file.managed:
@@ -89,9 +91,11 @@ suricata_sbin_jinja:
suriruledir:
file.directory:
- name: /opt/so/conf/suricata/rules
- name: /opt/so/rules/suricata
- user: 940
- group: 940
- group: 939
- mode: 775
- makedirs: True
surilogdir:
file.directory:
@@ -115,14 +119,12 @@ suridatadir:
- mode: 770
- makedirs: True
# salt:// would resolve to /opt/so/rules/nids because of the defined file_roots and
# not existing under /opt/so/saltstack/local/salt or /opt/so/saltstack/default/salt
surirulesync:
file.recurse:
- name: /opt/so/conf/suricata/rules/
- source: salt://suri/
- name: /opt/so/rules/suricata/
- source: salt://suricata/rules/
- user: 940
- group: 940
- group: 939
- show_changes: False
surilogscript:
@@ -155,10 +157,9 @@ suriconfig:
surithresholding:
file.managed:
- name: /opt/so/conf/suricata/threshold.conf
- source: salt://suricata/files/threshold.conf.jinja
- source: salt://suricata/files/threshold.conf
- user: 940
- group: 940
- template: jinja
suriclassifications:
file.managed:
@@ -176,6 +177,14 @@ so-suricata-eve-clean:
- template: jinja
- source: salt://suricata/cron/so-suricata-eve-clean
so-suricata-rulestats:
file.managed:
- name: /usr/sbin/so-suricata-rulestats
- user: root
- group: root
- mode: 755
- source: salt://suricata/cron/so-suricata-rulestats
{% else %}
{{sls}}_state_not_allowed:

View File

@@ -0,0 +1,30 @@
#!/bin/bash
#
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
# Query Suricata for ruleset stats and reload time, write to JSON file for Telegraf to consume
OUTFILE="/opt/so/log/suricata/rulestats.json"
SURICATASC="docker exec so-suricata /opt/suricata/bin/suricatasc"
SOCKET="/var/run/suricata/suricata-command.socket"
query() {
timeout 10 $SURICATASC -c "$1" "$SOCKET" 2>/dev/null
}
STATS=$(query "ruleset-stats")
RELOAD=$(query "ruleset-reload-time")
if echo "$STATS" | jq -e '.return == "OK"' > /dev/null 2>&1; then
LOADED=$(echo "$STATS" | jq -r '.message[0].rules_loaded')
FAILED=$(echo "$STATS" | jq -r '.message[0].rules_failed')
LAST_RELOAD=$(echo "$RELOAD" | jq -r '.message[0].last_reload')
jq -n --argjson loaded "$LOADED" --argjson failed "$FAILED" --arg reload "$LAST_RELOAD" \
'{rules_loaded: $loaded, rules_failed: $failed, last_reload: $reload, return: "OK"}' > "$OUTFILE"
else
echo '{"return":"FAIL"}' > "$OUTFILE"
fi

View File

@@ -467,7 +467,7 @@ suricata:
append: "yes"
default-rule-path: /etc/suricata/rules
rule-files:
- all.rules
- all-rulesets.rules
classification-file: /etc/suricata/classification.config
reference-config-file: /etc/suricata/reference.config
threshold-file: /etc/suricata/threshold.conf

View File

@@ -23,6 +23,11 @@ clean_suricata_eve_files:
cron.absent:
- identifier: clean_suricata_eve_files
# Remove rulestats cron
rulestats:
cron.absent:
- identifier: suricata_rulestats
{% else %}
{{sls}}_state_not_allowed:

View File

@@ -36,7 +36,7 @@ so-suricata:
- /opt/so/conf/suricata/suricata.yaml:/etc/suricata/suricata.yaml:ro
- /opt/so/conf/suricata/threshold.conf:/etc/suricata/threshold.conf:ro
- /opt/so/conf/suricata/classification.config:/etc/suricata/classification.config:ro
- /opt/so/conf/suricata/rules:/etc/suricata/rules:ro
- /opt/so/rules/suricata:/etc/suricata/rules:ro
- /opt/so/log/suricata/:/var/log/suricata/:rw
- /nsm/suricata/:/nsm/:rw
- /nsm/suricata/extracted:/var/log/suricata//filestore:rw
@@ -90,6 +90,18 @@ clean_suricata_eve_files:
- month: '*'
- dayweek: '*'
# Add rulestats cron - runs every minute to query Suricata for rule load status
suricata_rulestats:
cron.present:
- name: /usr/sbin/so-suricata-rulestats > /dev/null 2>&1
- identifier: suricata_rulestats
- user: root
- minute: '*'
- hour: '*'
- daymonth: '*'
- month: '*'
- dayweek: '*'
{% else %}
{{sls}}_state_not_allowed:

View File

@@ -9,3 +9,4 @@
#config tls any any -> any any (tls.fingerprint; content:"4f:a4:5e:58:7e:d9:db:20:09:d7:b6:c7:ff:58:c4:7b:dc:3f:55:b4"; config: logging disable, type tx, scope tx; sid:1200003;)
# Example of filtering out a md5 of a file from being in the files log.
#config fileinfo any any -> any any (fileinfo.filemd5; content:"7a125dc69c82d5caf94d3913eecde4b5"; config: logging disable, type tx, scope tx; sid:1200004;)

View File

@@ -0,0 +1,2 @@
# Threshold configuration generated by Security Onion
# This file is automatically generated - do not edit manually

View File

@@ -1,35 +0,0 @@
{% import_yaml 'suricata/thresholding/sids.yaml' as THRESHOLDING %}
{% if THRESHOLDING -%}
{% for EACH_SID in THRESHOLDING -%}
{% for ACTIONS_LIST in THRESHOLDING[EACH_SID] -%}
{% for EACH_ACTION in ACTIONS_LIST -%}
{%- if EACH_ACTION == 'threshold' %}
{{ EACH_ACTION }} gen_id {{ ACTIONS_LIST[EACH_ACTION].gen_id }}, sig_id {{ EACH_SID }}, type {{ ACTIONS_LIST[EACH_ACTION].type }}, track {{ ACTIONS_LIST[EACH_ACTION].track }}, count {{ ACTIONS_LIST[EACH_ACTION].count }}, seconds {{ ACTIONS_LIST[EACH_ACTION].seconds }}
{%- elif EACH_ACTION == 'rate_filter' %}
{%- if ACTIONS_LIST[EACH_ACTION].new_action not in ['drop','reject'] %}
{{ EACH_ACTION }} gen_id {{ ACTIONS_LIST[EACH_ACTION].gen_id }}, sig_id {{ EACH_SID }}, track {{ ACTIONS_LIST[EACH_ACTION].track }}, count {{ ACTIONS_LIST[EACH_ACTION].count }}, seconds {{ ACTIONS_LIST[EACH_ACTION].seconds }}, new_action {{ ACTIONS_LIST[EACH_ACTION].new_action }}, timeout {{ ACTIONS_LIST[EACH_ACTION].timeout }}
{%- else %}
##### Security Onion does not support drop or reject actions for rate_filter
##### {{ EACH_ACTION }} gen_id {{ ACTIONS_LIST[EACH_ACTION].gen_id }}, sig_id {{ EACH_SID }}, track {{ ACTIONS_LIST[EACH_ACTION].track }}, count {{ ACTIONS_LIST[EACH_ACTION].count }}, seconds {{ ACTIONS_LIST[EACH_ACTION].seconds }}, new_action {{ ACTIONS_LIST[EACH_ACTION].new_action }}, timeout {{ ACTIONS_LIST[EACH_ACTION].timeout }}
{%- endif %}
{%- elif EACH_ACTION == 'suppress' %}
{%- if ACTIONS_LIST[EACH_ACTION].track is defined %}
{{ EACH_ACTION }} gen_id {{ ACTIONS_LIST[EACH_ACTION].gen_id }}, sig_id {{ EACH_SID }}, track {{ ACTIONS_LIST[EACH_ACTION].track }}, ip {{ ACTIONS_LIST[EACH_ACTION].ip }}
{%- else %}
{{ EACH_ACTION }} gen_id {{ ACTIONS_LIST[EACH_ACTION].gen_id }}, sig_id {{ EACH_SID }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endfor %}
{%- else %}
##### Navigate to suricata > thresholding > SIDS in SOC to define thresholding
{%- endif %}

View File

@@ -1,30 +0,0 @@
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls in allowed_states %}
surilocaldir:
file.directory:
- name: /opt/so/saltstack/local/salt/suricata
- user: socore
- group: socore
- makedirs: True
ruleslink:
file.symlink:
- name: /opt/so/saltstack/local/salt/suricata/rules
- user: socore
- group: socore
- target: /opt/so/rules/nids/suri
refresh_salt_master_fileserver_suricata_ruleslink:
salt.runner:
- name: fileserver.update
- onchanges:
- file: ruleslink
{% else %}
{{sls}}_state_not_allowed:
test.fail_without_changes:
- name: {{sls}}_state_not_allowed
{% endif %}

View File

View File

@@ -21,6 +21,7 @@ telegraf:
- sostatus.sh
- stenoloss.sh
- suriloss.sh
- surirules.sh
- zeekcaptureloss.sh
- zeekloss.sh
standalone:
@@ -36,6 +37,7 @@ telegraf:
- sostatus.sh
- stenoloss.sh
- suriloss.sh
- surirules.sh
- zeekcaptureloss.sh
- zeekloss.sh
- features.sh
@@ -81,6 +83,7 @@ telegraf:
- sostatus.sh
- stenoloss.sh
- suriloss.sh
- surirules.sh
- zeekcaptureloss.sh
- zeekloss.sh
- features.sh
@@ -95,6 +98,7 @@ telegraf:
- sostatus.sh
- stenoloss.sh
- suriloss.sh
- surirules.sh
- zeekcaptureloss.sh
- zeekloss.sh
idh:

View File

@@ -0,0 +1,30 @@
#!/bin/bash
#
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
# Read Suricata ruleset stats from JSON file written by so-suricata-rulestats cron job
# JSON format: {"rules_loaded":45879,"rules_failed":1,"last_reload":"2025-12-04T14:10:57+0000","return":"OK"}
# or on failure: {"return":"FAIL"}
# if this script isn't already running
if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
STATSFILE="/var/log/suricata/rulestats.json"
# Check file exists, is less than 90 seconds old, and has valid data
if [ -f "$STATSFILE" ] && [ $(($(date +%s) - $(stat -c %Y "$STATSFILE"))) -lt 90 ] && jq -e '.return == "OK" and .rules_loaded != null and .rules_failed != null' "$STATSFILE" > /dev/null 2>&1; then
LOADED=$(jq -r '.rules_loaded' "$STATSFILE")
FAILED=$(jq -r '.rules_failed' "$STATSFILE")
RELOAD_TIME=$(jq -r '.last_reload // ""' "$STATSFILE")
echo "surirules loaded=${LOADED}i,failed=${FAILED}i,reload_time=\"${RELOAD_TIME}\",status=\"ok\""
else
echo "surirules loaded=0i,failed=0i,reload_time=\"\",status=\"unknown\""
fi
fi
exit 0

View File

@@ -74,8 +74,6 @@ base:
- sensoroni
- telegraf
- firewall
- idstools
- suricata.manager
- healthcheck
- elasticsearch
- elastic-fleet-package-registry
@@ -106,8 +104,6 @@ base:
- firewall
- sensoroni
- telegraf
- idstools
- suricata.manager
- healthcheck
- elasticsearch
- logstash
@@ -142,8 +138,6 @@ base:
- sensoroni
- telegraf
- backup.config_backup
- idstools
- suricata.manager
- elasticsearch
- logstash
- redis
@@ -177,8 +171,6 @@ base:
- sensoroni
- telegraf
- backup.config_backup
- idstools
- suricata.manager
- elasticsearch
- logstash
- redis
@@ -208,8 +200,6 @@ base:
- sensoroni
- telegraf
- firewall
- idstools
- suricata.manager
- pcap
- elasticsearch
- elastic-fleet-package-registry

View File

@@ -816,7 +816,6 @@ create_manager_pillars() {
backup_pillar
docker_pillar
redis_pillar
idstools_pillar
kratos_pillar
hydra_pillar
soc_pillar
@@ -1282,11 +1281,6 @@ ls_heapsize() {
}
idstools_pillar() {
title "Ading IDSTOOLS pillar options"
touch $adv_idstools_pillar_file
}
nginx_pillar() {
title "Creating the NGINX pillar"
[[ -z "$TESTING" ]] && return
@@ -1462,7 +1456,7 @@ make_some_dirs() {
mkdir -p $local_salt_dir/salt/firewall/portgroups
mkdir -p $local_salt_dir/salt/firewall/ports
for THEDIR in bpf pcap elasticsearch ntp firewall redis backup influxdb strelka sensoroni soc docker zeek suricata nginx telegraf logstash soc manager kratos hydra idstools idh elastalert stig global kafka versionlock hypervisor vm; do
for THEDIR in bpf pcap elasticsearch ntp firewall redis backup influxdb strelka sensoroni soc docker zeek suricata nginx telegraf logstash soc manager kratos hydra idh elastalert stig global kafka versionlock hypervisor vm; do
mkdir -p $local_salt_dir/pillar/$THEDIR
touch $local_salt_dir/pillar/$THEDIR/adv_$THEDIR.sls
touch $local_salt_dir/pillar/$THEDIR/soc_$THEDIR.sls

View File

@@ -166,12 +166,6 @@ export hydra_pillar_file
adv_hydra_pillar_file="$local_salt_dir/pillar/hydra/adv_hydra.sls"
export adv_hydra_pillar_file
idstools_pillar_file="$local_salt_dir/pillar/idstools/soc_idstools.sls"
export idstools_pillar_file
adv_idstools_pillar_file="$local_salt_dir/pillar/idstools/adv_idstools.sls"
export adv_idstools_pillar_file
nginx_pillar_file="$local_salt_dir/pillar/nginx/soc_nginx.sls"
export nginx_pillar_file