mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-07-02 07:08:14 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a6cc58306 | |||
| 9217670bab |
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script adds sensors/nodes/etc to the nodes tab
|
||||
default_salt_dir=/opt/so/saltstack/default
|
||||
local_salt_dir=/opt/so/saltstack/local
|
||||
TYPE=$1
|
||||
NAME=$2
|
||||
IPADDRESS=$3
|
||||
CPUS=$4
|
||||
GUID=$5
|
||||
MANINT=$6
|
||||
ROOTFS=$7
|
||||
NSM=$8
|
||||
MONINT=$9
|
||||
#NODETYPE=$10
|
||||
#HOTNAME=$11
|
||||
|
||||
echo "Seeing if this host is already in here. If so delete it"
|
||||
if grep -q $NAME "$local_salt_dir/pillar/data/$TYPE.sls"; then
|
||||
echo "Node Already Present - Let's re-add it"
|
||||
awk -v blah=" $NAME:" 'BEGIN{ print_flag=1 }
|
||||
{
|
||||
if( $0 ~ blah )
|
||||
{
|
||||
print_flag=0;
|
||||
next
|
||||
}
|
||||
if( $0 ~ /^ [a-zA-Z0-9]+:$/ )
|
||||
{
|
||||
print_flag=1;
|
||||
}
|
||||
if ( print_flag == 1 )
|
||||
print $0
|
||||
|
||||
} ' $local_salt_dir/pillar/data/$TYPE.sls > $local_salt_dir/pillar/data/tmp.$TYPE.sls
|
||||
mv $local_salt_dir/pillar/data/tmp.$TYPE.sls $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo "Deleted $NAME from the tab. Now adding it in again with updated info"
|
||||
fi
|
||||
echo " $NAME:" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " ip: $IPADDRESS" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " manint: $MANINT" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " totalcpus: $CPUS" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " guid: $GUID" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " rootfs: $ROOTFS" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
echo " nsmfs: $NSM" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
if [ $TYPE == 'sensorstab' ]; then
|
||||
echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
fi
|
||||
if [ $TYPE == 'evaltab' ] || [ $TYPE == 'standalonetab' ]; then
|
||||
echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
if [ ! $10 ]; then
|
||||
salt-call state.apply utility queue=True
|
||||
fi
|
||||
fi
|
||||
if [ $TYPE == 'nodestab' ]; then
|
||||
salt-call state.apply elasticsearch queue=True
|
||||
# echo " nodetype: $NODETYPE" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
# echo " hotname: $HOTNAME" >> $local_salt_dir/pillar/data/$TYPE.sls
|
||||
fi
|
||||
@@ -37,7 +37,8 @@
|
||||
'elasticfleet',
|
||||
'elasticfleet.manager',
|
||||
'elasticsearch.cluster',
|
||||
'elastic-fleet-package-registry'
|
||||
'elastic-fleet-package-registry',
|
||||
'utility'
|
||||
] %}
|
||||
|
||||
{% set sensor_states = [
|
||||
|
||||
@@ -69,7 +69,7 @@ wait_for_so-kibana:
|
||||
- ssl: True
|
||||
- verify_ssl: False
|
||||
- status: 200
|
||||
- wait_for: 600
|
||||
- wait_for: 300
|
||||
- request_interval: 15
|
||||
- require:
|
||||
- docker_container: so-kibana
|
||||
|
||||
@@ -11,8 +11,8 @@ name=Security Onion Repo repo
|
||||
mirrorlist=file:///opt/so/conf/reposync/mirror.txt
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
[securityonionkernelsync]
|
||||
name=Security Onion Kernel Repo repo
|
||||
[securityonionkernel]
|
||||
name=Security Onion Repo repo
|
||||
mirrorlist=file:///opt/so/conf/reposync/mirror-kernel.txt
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
|
||||
@@ -17,9 +17,9 @@ createrepo /nsm/repo
|
||||
# The kernel repo section is deployed to repodownload.conf by the manager highstate, which
|
||||
# runs AFTER this script during soup. On the first upgrade to a kernel-aware version the
|
||||
# on-disk config still predates the section, so guard on its presence to avoid dnf's
|
||||
# "Unknown repo: 'securityonionkernelsync'" aborting the sync (set -e). The next sync after the
|
||||
# "Unknown repo: 'securityonionkernel'" aborting the sync (set -e). The next sync after the
|
||||
# highstate deploys the section will pick it up.
|
||||
if grep -q '^\[securityonionkernelsync\]' /opt/so/conf/reposync/repodownload.conf; then
|
||||
dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernelsync --download-metadata -p /nsm/kernelrepo/
|
||||
if grep -q '^\[securityonionkernel\]' /opt/so/conf/reposync/repodownload.conf; then
|
||||
dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernel --download-metadata -p /nsm/kernelrepo/
|
||||
createrepo /nsm/kernelrepo
|
||||
fi
|
||||
|
||||
@@ -245,7 +245,6 @@ check_airgap() {
|
||||
UPDATE_DIR=/tmp/soagupdate/SecurityOnion
|
||||
AGDOCKER=/tmp/soagupdate/docker
|
||||
AGREPO=/tmp/soagupdate/minimal/Packages
|
||||
AGUEKREPO=/tmp/soagupdate/uek/Packages
|
||||
else
|
||||
is_airgap=1
|
||||
fi
|
||||
@@ -851,28 +850,6 @@ kibana_backport_streams_index_template() {
|
||||
|
||||
}
|
||||
|
||||
# Runs kafka-features.sh upgrade --release-version $1
|
||||
# Upgrades Kafka KRaft cluster metadata
|
||||
update_kafka_metadata() {
|
||||
metadata_version="$1"
|
||||
global_pillar="/opt/so/saltstack/local/pillar/global/soc_global.sls"
|
||||
if PIPELINE=$(so-yaml.py get -r "$global_pillar" global.pipeline 2> /dev/null) && [[ "$PIPELINE" == "KAFKA" ]]; then
|
||||
kafka_nodes_raw=$(salt-call pillar.get kafka:nodes --out=json)
|
||||
if kafka_nodes=$(jq -er '.local | select(type == "object" and length > 0)' <<< "$kafka_nodes_raw"); then
|
||||
bootstrap_servers=$(jq -r '[to_entries[] | select(.value.role | contains("broker")) | "\(.value.ip):9092"] | join(",")' <<< "$kafka_nodes")
|
||||
echo "Upgrading Kafka KRaft cluster version"
|
||||
so-kafka-cli kafka-features.sh --bootstrap-server "$bootstrap_servers" --command-config /opt/kafka/config/kraft/client.properties upgrade --release-version "$metadata_version" 2>/dev/null || true
|
||||
|
||||
return 0
|
||||
else
|
||||
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically perform Kafka KRaft cluster metadata update. This step can be performed manually using the following command (replacing \$BROKER_IP with the ip of atleast 1 available Kafka broker):")
|
||||
FINAL_MESSAGE_QUEUE+=(" - so-kafka-cli kafka-features.sh --bootstrap-server \$BROKER_IP:9092 --command-config /opt/kafka/config/kraft/client.properties upgrade --release-version $metadata_version")
|
||||
fi
|
||||
else
|
||||
echo "Nothing to do!"
|
||||
fi
|
||||
}
|
||||
|
||||
up_to_3.2.0() {
|
||||
fix_logstash_0013_lumberjack_pipeline_name
|
||||
|
||||
@@ -890,8 +867,6 @@ post_to_3.2.0() {
|
||||
|
||||
kibana_backport_streams_index_template
|
||||
|
||||
update_kafka_metadata "4.3"
|
||||
|
||||
POSTVERSION=3.2.0
|
||||
}
|
||||
|
||||
@@ -1005,19 +980,13 @@ update_airgap_rules() {
|
||||
rsync -a $UPDATE_DIR/agrules/securityonion-resources/* /nsm/securityonion-resources/
|
||||
}
|
||||
|
||||
update_airgap_repos() {
|
||||
update_airgap_repo() {
|
||||
# Update the files in the repo
|
||||
echo "Syncing new updates to /nsm/repo & /nsm/kernelrepo"
|
||||
# Airgap soup copies new files into the local repo, but doesn't remove old packages. Retaining the ability to rollback package updates
|
||||
rsync -a "$AGREPO"/ /nsm/repo/
|
||||
rsync -a "$AGUEKREPO"/ /nsm/kernelrepo/
|
||||
|
||||
echo "Syncing new updates to /nsm/repo"
|
||||
rsync -a $AGREPO/* /nsm/repo/
|
||||
echo "Creating repo"
|
||||
dnf -y install yum-utils createrepo_c
|
||||
|
||||
echo "Running createrepo for /nsm/repo"
|
||||
createrepo /nsm/repo
|
||||
echo "Running createrepo for /nsm/kernelrepo"
|
||||
createrepo /nsm/kernelrepo
|
||||
}
|
||||
|
||||
update_salt_mine() {
|
||||
@@ -1773,7 +1742,7 @@ main() {
|
||||
set -e
|
||||
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
update_airgap_repos
|
||||
update_airgap_repo
|
||||
dnf clean all
|
||||
check_os_updates
|
||||
elif [[ $OS == 'oracle' ]]; then
|
||||
|
||||
@@ -134,6 +134,30 @@ socsigmasopipeline:
|
||||
- group: 939
|
||||
- mode: 600
|
||||
|
||||
socsigmaplaybookpipeline:
|
||||
file.managed:
|
||||
- name: /opt/so/conf/soc/sigma_playbook_pipeline.yaml
|
||||
- source: salt://soc/files/soc/sigma_playbook_pipeline.yaml
|
||||
- user: 939
|
||||
- group: 939
|
||||
- mode: 600
|
||||
|
||||
socplaybookplaceholdermap:
|
||||
file.managed:
|
||||
- name: /opt/so/conf/soc/playbook_placeholder_map.yaml
|
||||
- source: salt://soc/files/soc/playbook_placeholder_map.yaml
|
||||
- user: 939
|
||||
- group: 939
|
||||
- mode: 600
|
||||
|
||||
socplaybookplaceholdermapcustom:
|
||||
file.managed:
|
||||
- name: /opt/so/conf/soc/playbook_placeholder_map_custom.yaml
|
||||
- source: salt://soc/files/soc/playbook_placeholder_map_custom.yaml
|
||||
- user: 939
|
||||
- group: 939
|
||||
- mode: 600
|
||||
|
||||
socbanner:
|
||||
file.managed:
|
||||
- name: /opt/so/conf/soc/banner.md
|
||||
|
||||
@@ -1499,9 +1499,9 @@ soc:
|
||||
playbookRepoPath: /opt/sensoroni/playbooks/
|
||||
playbookRepos:
|
||||
default:
|
||||
- repo: https://github.com/Security-Onion-Solutions/securityonion-resources-playbooks
|
||||
- repo: https://github.com/defensivedepth/HCIP-Sigma
|
||||
branch: main
|
||||
folder: securityonion-normalized
|
||||
folder: playbooks
|
||||
airgap:
|
||||
- repo: file:///nsm/airgap-resources/playbooks/securityonion-resources-playbooks
|
||||
branch: main
|
||||
@@ -1509,8 +1509,6 @@ soc:
|
||||
assistant:
|
||||
systemPromptAddendum: ""
|
||||
systemPromptAddendumMaxLength: 50000
|
||||
maxSubSessionTokens: 0
|
||||
maxDelegationDepth: 5
|
||||
adapters:
|
||||
- name: SOAI
|
||||
protocol: securityonion_ai_cloud
|
||||
@@ -1522,10 +1520,6 @@ soc:
|
||||
serviceAccountJSON: ""
|
||||
serviceAccountLocation: ""
|
||||
healthTimeoutSeconds: 5
|
||||
agentic: false
|
||||
agentMapping:
|
||||
Orchestrator: sonnet
|
||||
Hunter: sonnet
|
||||
onionconfig:
|
||||
saltstackDir: /opt/so/saltstack
|
||||
bypassEnabled: false
|
||||
|
||||
@@ -45,7 +45,10 @@ so-soc:
|
||||
- /opt/so/conf/soc/motd.md:/opt/sensoroni/html/motd.md:ro
|
||||
- /opt/so/conf/soc/banner.md:/opt/sensoroni/html/login/banner.md:ro
|
||||
- /opt/so/conf/soc/sigma_so_pipeline.yaml:/opt/sensoroni/sigma_so_pipeline.yaml:ro
|
||||
- /opt/so/conf/soc/sigma_playbook_pipeline.yaml:/opt/sensoroni/sigma_playbook_pipeline.yaml:ro
|
||||
- /opt/so/conf/soc/sigma_final_pipeline.yaml:/opt/sensoroni/sigma_final_pipeline.yaml:rw
|
||||
- /opt/so/conf/soc/playbook_placeholder_map.yaml:/opt/sensoroni/playbook_placeholder_map.yaml:ro
|
||||
- /opt/so/conf/soc/playbook_placeholder_map_custom.yaml:/opt/sensoroni/playbook_placeholder_map_custom.yaml:rw
|
||||
- /opt/so/conf/soc/custom.js:/opt/sensoroni/html/js/custom.js:ro
|
||||
- /opt/so/conf/soc/custom_roles:/opt/sensoroni/rbac/custom_roles:ro
|
||||
- /opt/so/conf/soc/soc_users_roles:/opt/sensoroni/rbac/users_roles:rw
|
||||
@@ -99,6 +102,8 @@ so-soc:
|
||||
- file: soccustomroles
|
||||
- file: socusersroles
|
||||
- file: socclientsroles
|
||||
- file: socplaybookplaceholdermap
|
||||
- file: socplaybookplaceholdermapcustom
|
||||
|
||||
delete_so-soc_so-status.disabled:
|
||||
file.uncomment:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Global Playbook placeholder map: %token% -> event field path.
|
||||
#
|
||||
# Loaded by the SOC Playbook module and used to resolve `field|expand:%placeholder%` values
|
||||
# from an alert when converting playbook questions to OQL.
|
||||
# Left: the %token% used in a question
|
||||
# Right: the event field its value is read from (event_data.-nested or bare; the module
|
||||
# tries both).
|
||||
#
|
||||
# Example: with `src_ip: source.ip` (below), a question that writes
|
||||
# `source.ip|expand: '%src_ip%'` resolves %src_ip% to the alert's source.ip at convert time.
|
||||
#
|
||||
# This is the global base layer. To add or override tokens edit playbook_placeholder_map_custom.yaml.
|
||||
# those entries overlay this map and win on conflict.
|
||||
|
||||
CommandLine: process.command_line
|
||||
CurrentDirectory: process.working_directory
|
||||
Image: process.executable
|
||||
ImageLoaded: dll.name
|
||||
ParentImage: process.parent.executable
|
||||
ParentName: process.parent.name
|
||||
ParentProcessGuid: process.parent.entity_id
|
||||
ProcessGuid: process.entity_id
|
||||
TargetFilename: file.name
|
||||
TargetObject: registry.path
|
||||
TargetUserName: user.target.name
|
||||
User: user.name
|
||||
community_id: network.community_id
|
||||
dns_resolved_ip: dns.resolved_ip
|
||||
document_id: soc_id
|
||||
dst_ip: destination.ip
|
||||
dst_port: destination.port
|
||||
event_data_source_ip: source.ip
|
||||
file_path: file.path
|
||||
file_dirs: process.file_dirs
|
||||
file_name: process.name
|
||||
file_paths: process.file_paths
|
||||
hostname: host.name
|
||||
private_ip: network.private_ip
|
||||
public_ip: network.public_ip
|
||||
related_hosts: related.hosts
|
||||
related_ip: related.ip
|
||||
src_ip: source.ip
|
||||
dns_query_name: dns.query_name
|
||||
flow_id: log.id.uid
|
||||
payload: network.data.decoded
|
||||
rule_category: rule.category
|
||||
rule_name: rule.name
|
||||
rule_uuid: rule.uuid
|
||||
src_port: source.port
|
||||
@@ -0,0 +1,14 @@
|
||||
# Custom Playbook placeholder map: %token% -> event field path.
|
||||
#
|
||||
#
|
||||
# Left: the %token% used in a playbook question.
|
||||
# Right: the event field its value is read from (event_data.-nested or bare; the module tries
|
||||
# both). Note: a token that is simply named after a flat event field resolves automatically
|
||||
# without an entry here - only add a mapping when the token name differs from the field name.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# account_id: cloudflare.account_id
|
||||
#
|
||||
# A question that writes
|
||||
# `account_id|expand: '%account_id%'` resolves %account_id% from the alert at convert time.
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Security Onion - Playbook Pipeline
|
||||
priority: 97
|
||||
transformations:
|
||||
# Route string fields to their lowercase-normalized .caseless subfield so wildcard
|
||||
# matches are case-insensitive.
|
||||
- id: case_insensitive_string_fields
|
||||
type: field_name_mapping
|
||||
mapping:
|
||||
process.executable: process.executable.caseless
|
||||
process.parent.executable: process.parent.executable.caseless
|
||||
process.command_line: process.command_line.caseless
|
||||
process.parent.command_line: process.parent.command_line.caseless
|
||||
@@ -63,6 +63,14 @@ transformations:
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
category: antivirus
|
||||
# OS-agnostic process_creation scoping for product-less (NIDS/host-pivot) rules.
|
||||
- id: process_creation_os_agnostic
|
||||
type: add_condition
|
||||
conditions:
|
||||
event.category: process
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
category: process_creation
|
||||
# Transforms the `Hashes` field to ECS fields
|
||||
# ECS fields are used by the hash fields emitted by Elastic Defend
|
||||
# If shipped with Elastic Agent, sysmon logs will also have hashes mapped to ECS fields
|
||||
@@ -108,6 +116,40 @@ transformations:
|
||||
- type: logsource
|
||||
product: windows
|
||||
category: driver_load
|
||||
- id: ecs_fix_process_creation
|
||||
type: field_name_mapping
|
||||
mapping:
|
||||
# bare `Hashes` (the combined-string case is broken out above)
|
||||
winlog.event_data.Hashes: process.hash.sha256
|
||||
winlog.event_data.IntegrityLevel: process.Ext.token.integrity_level_name
|
||||
winlog.event_data.ParentName: process.parent.name
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
product: windows
|
||||
category: process_creation
|
||||
- id: ecs_fix_registry_set
|
||||
type: field_name_mapping
|
||||
mapping:
|
||||
winlog.event_data.Details: registry.data.strings
|
||||
# field rename only; EventType values (SetValue/CreateKey) still differ from
|
||||
# event.action values (modification/creation)
|
||||
winlog.event_data.EventType: event.action
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
product: windows
|
||||
category: registry_set
|
||||
- id: ecs_fix_image_load
|
||||
type: field_name_mapping
|
||||
mapping:
|
||||
file.path: dll.path
|
||||
file.code_signature.signed: dll.code_signature.exists
|
||||
winlog.event_data.Signature: dll.code_signature.subject_name
|
||||
file.code_signature.status: dll.code_signature.status
|
||||
winlog.event_data.Hashes: dll.hash.sha256
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
product: windows
|
||||
category: image_load
|
||||
- id: linux_security_add-fields
|
||||
type: add_condition
|
||||
conditions:
|
||||
@@ -281,6 +323,15 @@ transformations:
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
category: file_event
|
||||
# Scope image_load rules to Elastic Endpoint library events (event.category:library, dll.*
|
||||
# populated).
|
||||
- id: endpoint_image_load_add-fields
|
||||
type: add_condition
|
||||
conditions:
|
||||
event.category: 'library'
|
||||
rule_conditions:
|
||||
- type: logsource
|
||||
category: image_load
|
||||
# Maps network rules to all network logs
|
||||
# This targets all network logs, all services, generated from endpoints and network
|
||||
- id: network_add-fields
|
||||
|
||||
+9
-22
@@ -46,7 +46,15 @@ soc:
|
||||
syntax: yaml
|
||||
file: True
|
||||
global: True
|
||||
advanced: True
|
||||
advanced: False
|
||||
helpLink: security-onion-console-customization
|
||||
playbook_placeholder_map_custom__yaml:
|
||||
title: Playbook Placeholder Map
|
||||
description: Custom mappings of Playbook %placeholder% tokens to event fields.
|
||||
syntax: yaml
|
||||
file: True
|
||||
global: True
|
||||
advanced: False
|
||||
helpLink: security-onion-console-customization
|
||||
config:
|
||||
licenseKey:
|
||||
@@ -719,16 +727,6 @@ soc:
|
||||
description: Maximum length of the system prompt addendum. Longer prompts will be truncated.
|
||||
global: True
|
||||
advanced: True
|
||||
maxSubSessionTokens:
|
||||
description: Maximum number of output tokens a delegated sub-session may generate across all of its turns. When the budget is reached, the sub-agent is halted and its result is returned to the parent agent. Set to 0 to disable the limit.
|
||||
global: True
|
||||
advanced: True
|
||||
forcedType: int
|
||||
maxDelegationDepth:
|
||||
description: Maximum delegation nesting depth for sub-agents. For example, a value of 2 lets the main agent delegate to a sub-agent that may itself delegate one level deeper. Any deeper delegation is refused and the requesting agent continues without it. Set to 0 to disable the limit.
|
||||
global: True
|
||||
advanced: True
|
||||
forcedType: int
|
||||
adapters:
|
||||
description: Configuration for AI adapters used by the Onion AI assistant. Please see documentation for help on which fields are required for which protocols.
|
||||
global: True
|
||||
@@ -767,17 +765,6 @@ soc:
|
||||
label: Health Timeout Seconds
|
||||
required: False
|
||||
forcedType: int
|
||||
agentic:
|
||||
description: Indicates if the Assistant Module should operate in agentic mode or not. If true, agents can work together to solve tasks.
|
||||
global: True
|
||||
forcedType: bool
|
||||
agentMapping:
|
||||
Orchestrator:
|
||||
description: The initial agent in most agentic conversations. This agent will delegate requests to specialized agents.
|
||||
global: True
|
||||
Hunter:
|
||||
description: This agent is specialized in querying events.
|
||||
global: True
|
||||
client:
|
||||
assistant:
|
||||
enabled:
|
||||
|
||||
@@ -65,11 +65,10 @@ so-suricata:
|
||||
- file: suriclassifications
|
||||
|
||||
surirulereload:
|
||||
cmd.run:
|
||||
cmd.run:
|
||||
- name: /usr/sbin/so-suricata-reload-rules >> /opt/so/log/suricata/reload.log 2>&1
|
||||
- onchanges:
|
||||
- onchanges:
|
||||
- file: surirulesync
|
||||
- onlyif: test -f /opt/so/rules/suricata/all-rulesets.rules
|
||||
- require:
|
||||
- docker_container: so-suricata
|
||||
|
||||
|
||||
@@ -7,59 +7,5 @@
|
||||
|
||||
. /usr/sbin/so-common
|
||||
|
||||
RULES_FILE="/opt/so/rules/suricata/all-rulesets.rules"
|
||||
SOCKET="/var/run/suricata/suricata-command.socket"
|
||||
SURICATASC="docker exec so-suricata /opt/suricata/bin/suricatasc"
|
||||
|
||||
# Format an epoch as a human-readable local timestamp for log messages.
|
||||
fmt_time() { date -d "@$1" '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null; }
|
||||
|
||||
# Prefix each input line with the current timestamp.
|
||||
timestamp_lines() { while IFS= read -r line; do printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$line"; done; }
|
||||
|
||||
# Epoch of Suricata's last *completed* ruleset reload; non-zero return on failure.
|
||||
suricata_reload_epoch() {
|
||||
local out ts
|
||||
out=$($SURICATASC -c ruleset-reload-time "$SOCKET" 2>/dev/null)
|
||||
ts=$(echo "$out" | jq -r '.message[0].last_reload // empty' 2>/dev/null)
|
||||
[ -n "$ts" ] || return 1
|
||||
date -d "$ts" +%s 2>/dev/null
|
||||
}
|
||||
|
||||
# Trigger a fresh reload and confirm Suricata is running a ruleset at least as new
|
||||
# as the rules file. Returns 0 only when both hold, so retry keeps going until an
|
||||
# in-progress reload clears and our own reload completes.
|
||||
reload_and_verify() {
|
||||
local out reload_epoch
|
||||
out=$($SURICATASC -c reload-rules "$SOCKET")
|
||||
echo "reload-rules: $out"
|
||||
|
||||
if [[ "$out" =~ "Reload already in progress" ]]; then
|
||||
echo "A reload is already in progress; waiting for it to clear so a fresh reload can load the current ruleset."
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$out" =~ '{"message":"done","return":"OK"}' ]]; then
|
||||
echo "Suricata not ready or unexpected reload output; will retry."
|
||||
return 1
|
||||
fi
|
||||
|
||||
reload_epoch=$(suricata_reload_epoch) || { echo "Could not read ruleset-reload-time; will retry."; return 1; }
|
||||
if [ "$reload_epoch" -ge "$target_mtime" ]; then
|
||||
echo "Loaded ruleset is current: last reload ($(fmt_time "$reload_epoch")) is newer than rules file ($(fmt_time "$target_mtime"))."
|
||||
return 0
|
||||
fi
|
||||
echo "Loaded ruleset is stale: last reload ($(fmt_time "$reload_epoch")) is older than rules file ($(fmt_time "$target_mtime")); retrying."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Run the reload/verify, timestamping every line of output (ours and the
|
||||
# retry/fail helpers') so reload.log shows when each step ran. The pipeline is
|
||||
# synchronous, so the log is fully flushed and ordered before we exit; the
|
||||
# script's real exit code is preserved via PIPESTATUS.
|
||||
{
|
||||
# Epoch mtime of the ruleset we need Suricata to have loaded. Captured once so
|
||||
# a file update mid-reload does not move the goalpost.
|
||||
target_mtime=$(stat -c %Y "$RULES_FILE") || fail "Could not stat the Suricata rules file: $RULES_FILE"
|
||||
retry 60 3 'reload_and_verify' || fail "Suricata did not load the current ruleset in time."
|
||||
} 2>&1 | timestamp_lines
|
||||
exit "${PIPESTATUS[0]}"
|
||||
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."
|
||||
|
||||
@@ -83,6 +83,7 @@ base:
|
||||
- zeek
|
||||
- strelka
|
||||
- elastalert
|
||||
- utility
|
||||
- elasticfleet
|
||||
- pcap.cleanup
|
||||
|
||||
@@ -112,6 +113,7 @@ base:
|
||||
- zeek
|
||||
- strelka
|
||||
- elastalert
|
||||
- utility
|
||||
- elasticfleet
|
||||
- stig
|
||||
- kafka
|
||||
@@ -139,6 +141,7 @@ base:
|
||||
- elastic-fleet-package-registry
|
||||
- kibana
|
||||
- elastalert
|
||||
- utility
|
||||
- elasticfleet
|
||||
- stig
|
||||
- kafka
|
||||
@@ -165,6 +168,7 @@ base:
|
||||
- elastic-fleet-package-registry
|
||||
- kibana
|
||||
- elastalert
|
||||
- utility
|
||||
- elasticfleet
|
||||
- kafka
|
||||
|
||||
@@ -194,6 +198,7 @@ base:
|
||||
- elastic-fleet-package-registry
|
||||
- kibana
|
||||
- elastalert
|
||||
- utility
|
||||
- elasticfleet
|
||||
- stig
|
||||
- kafka
|
||||
@@ -217,6 +222,7 @@ base:
|
||||
- elasticsearch
|
||||
- elastic-fleet-package-registry
|
||||
- kibana
|
||||
- utility
|
||||
- suricata
|
||||
- zeek
|
||||
- elasticfleet
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# Wait for ElasticSearch to come up, so that we can query for version infromation
|
||||
echo -n "Waiting for ElasticSearch..."
|
||||
COUNT=0
|
||||
ELASTICSEARCH_CONNECTED="no"
|
||||
while [[ "$COUNT" -le 30 ]]; do
|
||||
curl -K /opt/so/conf/elasticsearch/curl.config -k --output /dev/null --silent --head --fail -L https://{{ GLOBALS.manager_ip }}:9200
|
||||
if [ $? -eq 0 ]; then
|
||||
ELASTICSEARCH_CONNECTED="yes"
|
||||
echo "connected!"
|
||||
break
|
||||
else
|
||||
((COUNT+=1))
|
||||
sleep 1
|
||||
echo -n "."
|
||||
fi
|
||||
done
|
||||
if [ "$ELASTICSEARCH_CONNECTED" == "no" ]; then
|
||||
echo
|
||||
echo -e "Connection attempt timed out. Unable to connect to ElasticSearch. \nPlease try: \n -checking log(s) in /var/log/elasticsearch/\n -running 'docker ps' \n -running 'sudo so-elastic-restart'"
|
||||
echo
|
||||
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Applying cross cluster search config..."
|
||||
curl -K /opt/so/conf/elasticsearch/curl.config -s -k -XPUT -L https://{{ GLOBALS.manager_ip }}:9200/_cluster/settings \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"persistent\": {\"search\": {\"remote\": {\"{{ grains.host }}\": {\"seeds\": [\"127.0.0.1:9300\"]}}}}}"
|
||||
@@ -0,0 +1,22 @@
|
||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||
|
||||
{% if sls in allowed_states %}
|
||||
{% if grains['role'] in ['so-eval', 'so-import'] %}
|
||||
fixsearch:
|
||||
cmd.script:
|
||||
- shell: /bin/bash
|
||||
- cwd: /opt/so
|
||||
- source: salt://utility/bin/eval
|
||||
- template: jinja
|
||||
- defaults:
|
||||
GLOBALS: {{ GLOBALS }}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{{sls}}_state_not_allowed:
|
||||
test.fail_without_changes:
|
||||
- name: {{sls}}_state_not_allowed
|
||||
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user