Compare commits

...
31 Commits
Author SHA1 Message Date
Josh Patterson 9c20ef60f4 Merge remote-tracking branch 'origin/3/dev' into asasoup 2026-08-06 13:56:08 -04:00
Josh Patterson 6abf382ea8 so-grid-highstate: wait for fleet recovery and quiet the expected salt-upgrade warning
Two refinements to the --salt-upgraded path, surfaced testing a salt downgrade+soup
on a manager+heavynode grid:

- The post-upgrade settle loop settled on any stable reachable count >0, so when a
  target was briefly down for its salt-minion restart it could settle on the
  not-yet-restarted subset (observed: 'settled at 1' with 2 accepted) and release the
  tiered pass before nodes reconnected. Capture the reachable count just before the
  pass and wait for it to recover to that count (up >= pre-upgrade target) and hold
  steady, with an initial grace so the delayed restart dip is observed rather than
  skipped. Still compares against the pre-upgrade reachable set, not accepted keys, so
  an intentionally powered-off node never stalls past the backstop.

- The salt-upgrade pass returns non-zero by design (targets restart salt-minion
  mid-run), but it logged the generic 'nodes it missed will converge on the scheduled
  highstate' warning, which reads like a real failure. Mark that dispatch as
  expect_restart so it logs a benign, explanatory line instead.

Verified live: with the heavynode's salt-minion bounced during the settle window, the
loop logged 'fleet recovered to 2 minions up (>= pre-upgrade 2)' and only then ran the
tiered pass (heavynode highstate 427 succeeded, 0 failed).
2026-08-06 09:46:12 -04:00
Jason ErtelandGitHub f1f672892e Merge pull request #16142 from Security-Onion-Solutions/jertel/wip
fix template collation mismatches
2026-08-05 16:45:41 -04:00
Jason Ertel a8053e2c9d exclude template0 2026-08-05 16:43:26 -04:00
Josh Patterson 36833fdad1 so-grid-highstate: emit single-line pillar JSON and skip empty tiers
Two defects surfaced testing BRANCH=asasoup soup on a manager+heavynode grid:

1. The tiered-pass actions JSON was built from a multi-line heredoc. salt parses
   'pillar=<value>' kwargs with a non-DOTALL regex, so the embedded newlines made
   salt-run treat the whole token as a positional saltenv -- 'No matching salt
   environment for environment pillar={...}' -- and the highstate never ran, leaving
   the heavynode on the old version. Emit the actions JSON on a single line (matching
   how so-push-drainer's json.dumps payload already works).

2. orch.push_batch's salt.state step reports 'No minions returned' (a failure) for a
   tier whose compound target matches nothing, so any grid lacking a role (no receiver,
   small grids) always logged a warning and returned rc=1 even when every present node
   converged. Pre-check each tier with 'salt -C <tgt> --preview-target' and include only
   tiers that match >=1 minion; exit cleanly if none match.

Verified live: heavynode highstated 3.2.0 -> 3.3.0 (427 states, 0 failed) and a
follow-up run skips the empty receiver/remainder tiers with rc=0.
2026-08-05 15:05:24 -04:00
Josh Patterson a92d10a1e3 Merge remote-tracking branch 'origin/3/dev' into asasoup 2026-08-05 14:19:39 -04:00
Josh Patterson d3da6b3939 soup: actively push a grid-wide highstate to remote minions after upgrade
The per-minion highstate schedule moved from 15 minutes to 120 minutes
(salt:schedule:highstate_interval_minutes), so after soup upgrades the manager,
non-manager minions could otherwise sit on the old version for up to ~2.5 hours
(interval + splay) before their scheduled highstate applies the new code.

Add so-grid-highstate, a detached best-effort driver soup fires at the end of an
upgrade. It uses the existing orch.push_batch runner to highstate the grid in
role tiers (searchnodes/heavynodes -> receivers -> the rest), skips single-node
grids, and when Salt itself was upgraded first runs an untiered pass and waits
for minions to reconnect on the new salt-minion before the tiered pass.

soup gains a push_grid_highstate() helper (guarded, launched via setsid nohup so
an SSH drop can't kill it) called on both the hotfix and full-upgrade paths,
wires the previously-dead -b flag through as --batch (now accepting N or N%),
and updates the distributed-deployment message to reflect the active push.
2026-08-05 14:06:21 -04:00
Jason Ertel e998a21b4d fix template collation mismatches 2026-08-05 12:37:13 -04:00
Jason ErtelandGitHub d77760c268 Merge pull request #16133 from Security-Onion-Solutions/jertel/wip
nginx configuration improvements
2026-08-03 15:28:05 -04:00
Jason Ertel 4f7ad76d5b nginx configuration improvements 2026-08-03 15:19:18 -04:00
Mike ReevesandGitHub 41dba204c3 Merge pull request #16126 from Security-Onion-Solutions/TOoSmOotH-patch-1
Bump version from 3.2.0 to 3.3.0
2026-07-29 14:23:59 -04:00
Mike ReevesandGitHub 12bb16e89c Add version 3.3.0 to discussion template 2026-07-29 14:22:42 -04:00
Mike ReevesandGitHub 45f1a1b8b1 Bump version from 3.2.0 to 3.3.0 2026-07-29 14:21:50 -04:00
Mike ReevesandGitHub d3c6fdce7e Merge pull request #16124 from Security-Onion-Solutions/3.2.0
3.2.0
2026-07-29 13:53:18 -04:00
Mike Reeves c9642489d3 3.2.0 2026-07-29 13:48:13 -04:00
coreyogburnandGitHub 7b080797b4 Merge pull request #16122 from Security-Onion-Solutions/cogburn/gemma-mapping
Investigator and Engineer Use Gemma
2026-07-28 11:12:24 -06:00
Corey Ogburn d94c16eea1 Investigator and Engineer Use Gemma 2026-07-28 10:26:29 -06:00
coreyogburnandGitHub 3e3d409c42 Merge pull request #16121 from Security-Onion-Solutions/cogburn/update-agentmapping-defaults
Change defaults
2026-07-28 10:21:49 -06:00
Corey Ogburn a45ca12076 Change defaults
We're no longer using DisplayName as the model identifier. Refactored to use id@adapter.
2026-07-28 10:19:18 -06:00
Mike ReevesandGitHub 2e141a1ad7 Merge pull request #16119 from Security-Onion-Solutions/TOoSmOotH-patch-3
Add Gemma configuration to defaults.yaml
2026-07-27 12:18:47 -04:00
Mike ReevesandGitHub 4de8f0208f Add Gemma configuration to defaults.yaml 2026-07-27 12:15:21 -04:00
Josh BrowerandGitHub 4aabf7d638 Merge pull request #16118 from Security-Onion-Solutions/playbooks-airgap
Support sigma playbooks for airgap
2026-07-27 16:33:51 +02:00
Josh Brower 812310088e Support sigma playbooks for airgap 2026-07-27 10:30:50 -04:00
Josh PattersonandGitHub 4e17390cfa Merge pull request #16117 from Security-Onion-Solutions/saltthangs
update vm state applid for pillar_push_map
2026-07-27 10:25:00 -04:00
Josh Patterson 57d629683d update vm state applid for pillar_push_map 2026-07-27 10:14:10 -04:00
Jorge ReyesandGitHub 3666b5b0de Merge pull request #16116 from Security-Onion-Solutions/reyesj2-patch-0
ignore all  TransformTask failures for so_kibana user
2026-07-27 07:41:07 -05:00
reyesj2 7f64f143d7 ignore all TransformTask failures for so_kibana user 2026-07-26 19:25:51 -05:00
Mike ReevesandGitHub 162c66a705 Merge pull request #16115 from Security-Onion-Solutions/reyesj2-patch-0
add sslverify=0 to minion repo config. Updates are pulled from the ma…
2026-07-24 19:06:28 -04:00
reyesj2 14d11cc180 add sslverify=0 to minion repo config. Updates are pulled from the manager hosted repo 2026-07-24 16:59:42 -05:00
Josh BrowerandGitHub 112fcf7804 Merge pull request #16114 from Security-Onion-Solutions/fix/sigmapipelines
Add already_running mapping
2026-07-24 23:40:45 +02:00
Josh Brower 120b426a79 Add already_running mapping 2026-07-24 17:23:00 -04:00
13 changed files with 376 additions and 34 deletions
+1
View File
@@ -12,6 +12,7 @@ body:
- 3.0.0
- 3.1.0
- 3.2.0
- 3.3.0
- Other (please provide detail below)
validations:
required: true
+11 -11
View File
@@ -1,17 +1,17 @@
### 3.1.0-20260528 ISO image released on 2026/05/28
### 3.2.0-20260729 ISO image released on 2026/07/29
### Download and Verify
3.1.0-20260528 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
3.2.0-20260729 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-3.2.0-20260729.iso
MD5: 9D6FF58DEEE24089D722C73169765B3E
SHA1: 2B8B816B6CEC3B7F96B3C5E040EBF502DD2C412F
SHA256: 62FAB57E247C843D6A04F0796D8162C732B65D82FC3E4A59D087135B9FD32912
MD5: B1E10F46DF872B655C29325DF965A4DB
SHA1: 0F3C7ED80F6D326B7A993C2F899B986320C01BF9
SHA256: 7465163C1D1ADFCDC3935530EAFB312E987C016941ADC11841B214553314D1FF
Signature for ISO image:
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.2.0-20260729.iso.sig
Signing key:
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/
Download the signature file for the ISO:
```
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.2.0-20260729.iso.sig
```
Download the ISO image:
```
wget https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
wget https://download.securityonion.net/file/securityonion/securityonion-3.2.0-20260729.iso
```
Verify the downloaded ISO image using the signature file:
```
gpg --verify securityonion-3.1.0-20260528.iso.sig securityonion-3.1.0-20260528.iso
gpg --verify securityonion-3.2.0-20260729.iso.sig securityonion-3.2.0-20260729.iso
```
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
```
gpg: Signature made Wed 27 May 2026 03:03:59 PM EDT using RSA key ID FE507013
gpg: Signature made Tue 28 Jul 2026 06:17:34 PM EDT using RSA key ID FE507013
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
+1 -1
View File
@@ -1 +1 @@
3.2.0
3.3.0
+1 -1
View File
@@ -231,7 +231,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint|armis|o365_metrics|microsoft_sentinel|snyk|cyera|island_browser).*user so_kibana lacks the required permissions \[(logs|metrics)-\1" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user. This error should not be seen on fresh ES 9.3.3 installs or after SO 3.1.0 with soups addition of check_transform_health_and_reauthorize()
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-.*user so_kibana lacks the required permissions" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
fi
+219
View File
@@ -0,0 +1,219 @@
#!/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.
# so-grid-highstate
# =================
# Drives a batched, role-tiered highstate across every non-manager minion in the
# grid. soup fires this (detached) after it finishes upgrading the manager so the
# rest of the grid converges immediately instead of waiting for its own scheduled
# highstate -- which, since the schedule moved from 15 minutes to 120 minutes
# (salt:schedule:highstate_interval_minutes), could otherwise leave nodes on the
# old version for up to ~2.5 hours (interval + splay) while the manager runs new code.
#
# Work is done by the existing orch.push_batch orchestration (salt/orch/push_batch.sls),
# the same runner the active-push drainer uses, so batching/queueing behavior matches.
# Tiers are dispatched in declaration order: searchnodes/heavynodes (Elasticsearch data
# nodes) first, then receivers, then everything else -- so the data tier converges before
# the ingest tier before sensors/fleet/idh/etc.
#
# When soup also upgraded Salt itself, remote minions must first highstate onto the new
# salt-minion package (top.sls gates every real state on G@saltversion, so a stale-version
# minion only gets salt.minion until it upgrades and reconnects). --salt-upgraded runs that
# preliminary pass and waits for the fleet to settle before the tiered pass.
#
# This is best-effort: soup has already completed by the time this runs, and the 120-minute
# scheduled highstate remains the backstop for any node that is offline or missed a batch.
LOG_FILE=/opt/so/log/salt/so-grid-highstate.log
LOCK_FILE=/opt/so/state/so-grid-highstate.lock
SETTLE_MAX_WAIT=${GRID_HIGHSTATE_SETTLE_WAIT:-900} # backstop for the post-salt-upgrade settle loop
SETTLE_INTERVAL=15
SETTLE_STABLE_CHECKS=3
# salt-minion on an upgraded node restarts ~30s after the upgrade state runs
# (salt/salt/minion/init.sls start_minion_post_upgrade); wait past that before sampling
# so the settle loop sees the drop-off instead of settling on the pre-restart set.
SETTLE_INITIAL_WAIT=${GRID_HIGHSTATE_SETTLE_INITIAL_WAIT:-45}
BATCH=""
BATCH_WAIT=""
SALT_UPGRADED=false
REASON="manual"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') | $*" | tee -a "$LOG_FILE"
}
usage() {
echo "Usage: so-grid-highstate [--batch <spec>] [--batch-wait <sec>] [--salt-upgraded] [--reason <text>]"
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
--batch) BATCH="$2"; shift 2 ;;
--batch-wait) BATCH_WAIT="$2"; shift 2 ;;
--salt-upgraded) SALT_UPGRADED=true; shift ;;
--reason) REASON="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$LOCK_FILE")"
# Serialize: a second invocation (e.g. two soups, or a manual run overlapping soup's)
# should not dispatch a competing set of batches.
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log "another so-grid-highstate is already running (lock $LOCK_FILE held); exiting"
exit 0
fi
# Resolve batch settings from the salt:auto_apply pillar when not overridden on the
# command line, falling back to the same defaults orch.push_batch/salt.defaults use.
if [ -z "$BATCH" ]; then
BATCH=$(salt-call --out=newline_values_only pillar.get salt:auto_apply:batch 2>/dev/null)
[ -z "$BATCH" ] && BATCH='25%'
fi
if [ -z "$BATCH_WAIT" ]; then
BATCH_WAIT=$(salt-call --out=newline_values_only pillar.get salt:auto_apply:batch_wait 2>/dev/null)
[ -z "$BATCH_WAIT" ] && BATCH_WAIT=15
fi
MINIONID=$(salt-call --local --out=newline_values_only grains.get id 2>/dev/null)
[ -z "$MINIONID" ] && MINIONID=$(cat /etc/salt/minion_id 2>/dev/null)
if [ -z "$MINIONID" ]; then
log "could not determine this minion's id; aborting"
exit 1
fi
# Single-node grids (eval/standalone/import with no other accepted keys) have nothing
# remote to push -- the manager already highstated during soup.
NUM_ACCEPTED=$(salt-key --out=json --list=accepted 2>/dev/null | jq -r '.minions | length' 2>/dev/null)
NUM_ACCEPTED=${NUM_ACCEPTED:-0}
if [ "$NUM_ACCEPTED" -le 1 ]; then
log "single node grid ($NUM_ACCEPTED accepted minion(s)); nothing to push (reason=$REASON)"
exit 0
fi
log "starting grid highstate: reason=$REASON minion=$MINIONID accepted=$NUM_ACCEPTED batch=$BATCH batch_wait=$BATCH_WAIT salt_upgraded=$SALT_UPGRADED"
# Count minions currently responsive on the bus (includes this manager).
count_up() {
salt-run manage.up --out=json 2>/dev/null \
| python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null
}
# Dispatch a single synchronous orch.push_batch run for the given actions JSON.
# Synchronous is fine: soup launched us detached, so blocking here does not hold soup up.
# expect_restart=true marks a dispatch (the salt-upgrade pass) where a non-zero rc is normal
# because targets restart salt-minion mid-run -- so we don't log a misleading failure warning.
dispatch() {
local desc="$1"
local actions="$2"
local expect_restart="${3:-false}"
local rc
log "dispatching $desc"
salt-run state.orchestrate orch.push_batch pillar="{\"actions\": $actions}" >>"$LOG_FILE" 2>&1
rc=$?
if [ "$rc" -eq 0 ]; then
log "$desc dispatch completed (rc=0)"
elif [ "$expect_restart" = "true" ]; then
log "$desc returned rc=$rc; this is expected during a salt upgrade (targets restart salt-minion mid-run). Waiting for them to reconnect before the tiered pass."
else
log "WARNING: $desc dispatch returned rc=$rc; nodes it missed will converge on the scheduled highstate"
fi
}
# Wait for the reachable minion set to recover to its pre-upgrade size and hold steady.
# Used after the salt-upgrade pass, where targets restart salt-minion (~30s delayed, see
# salt/salt/minion/init.sls) and drop off the bus before reconnecting on the new version.
# target = how many minions were reachable just before the pass; requiring up >= target keeps
# us from releasing the tiered pass while nodes are still down for their restart (settling on
# the not-yet-restarted subset). We deliberately compare against the pre-upgrade reachable
# count, not accepted keys, so a node an operator intentionally powered off never stalls us.
# Bounded by SETTLE_MAX_WAIT.
wait_for_settle() {
local target="$1"
local elapsed=0 prev=-1 stable=0 up=0
# Let the delayed salt-minion restart begin before we start counting stability, otherwise
# we could see the pre-restart set as "stable" and settle before the drop-off even happens.
sleep "$SETTLE_INITIAL_WAIT"
elapsed=$SETTLE_INITIAL_WAIT
while [ "$elapsed" -lt "$SETTLE_MAX_WAIT" ]; do
up=$(count_up); up=${up:-0}
if [ "$up" -ge "$target" ] && [ "$up" -eq "$prev" ]; then
stable=$((stable + 1))
[ "$stable" -ge "$SETTLE_STABLE_CHECKS" ] && break
else
stable=0
fi
prev=$up
sleep "$SETTLE_INTERVAL"
elapsed=$((elapsed + SETTLE_INTERVAL))
done
if [ "$up" -ge "$target" ]; then
log "fleet recovered to ${up} minions up (>= pre-upgrade ${target}) after ${elapsed}s"
else
log "WARNING: ${SETTLE_MAX_WAIT}s settle backstop hit; only ${up}/${target} pre-upgrade minions back up; proceeding (stragglers converge on the scheduled highstate)"
fi
}
# Pass 0: when Salt itself was upgraded, remote minions still on the old version only match
# top.sls's 'not G@saltversion' block (salt.minion, which performs the package upgrade). Push
# an untiered highstate so they upgrade+reconnect, then wait for them to come back before the
# real tiered pass applies the new version's states.
if [ "$SALT_UPGRADED" = "true" ]; then
PRE_UP=$(count_up); PRE_UP=${PRE_UP:-1}
log "pre-upgrade reachable minions (incl. this manager): $PRE_UP"
dispatch "salt-upgrade pass (all remote minions)" \
"[{\"highstate\": true, \"tgt\": \"not $MINIONID\", \"tgt_type\": \"compound\", \"batch\": \"$BATCH\", \"batch_wait\": $BATCH_WAIT}]" \
true
log "waiting for minions to reconnect on the new salt version"
wait_for_settle "$PRE_UP"
fi
# Tiered pass: Elasticsearch data nodes first, then receivers, then the remainder. The last
# tier is defined as the complement of the earlier tiers (and of this manager) so coverage is
# exhaustive -- sensors, fleet, idh, desktop, hypervisor, and any future role are all included.
TIER_TGTS=(
"( *_searchnode or *_heavynode ) and not $MINIONID"
"*_receiver and not $MINIONID"
"not $MINIONID and not *_searchnode and not *_heavynode and not *_receiver"
)
# Count minions a compound target matches, using the master's key/cache data (no execution).
tier_count() {
salt --out=json -C "$1" --preview-target 2>/dev/null | jq 'length' 2>/dev/null
}
# Build the actions JSON, including only tiers that actually match minions. An empty target
# would make orch.push_batch's salt.state step return "No minions returned" -- a failure --
# even though nothing needed to run, and grids commonly lack a tier (no receiver, etc.).
# Keep the JSON on a single line: salt parses `pillar=<value>` kwargs with a non-DOTALL
# regex, so an embedded newline makes it treat the whole token as a positional saltenv
# instead ("No matching salt environment for environment 'pillar=...'").
actions=""
for tgt in "${TIER_TGTS[@]}"; do
n=$(tier_count "$tgt"); n=${n:-0}
if [ "$n" -ge 1 ]; then
[ -n "$actions" ] && actions="$actions, "
actions="$actions{\"highstate\": true, \"tgt\": \"$tgt\", \"tgt_type\": \"compound\", \"batch\": \"$BATCH\", \"batch_wait\": $BATCH_WAIT}"
log "tier matched $n minion(s): $tgt"
else
log "tier matched 0 minions, skipping: $tgt"
fi
done
if [ -z "$actions" ]; then
log "no remote minions matched any tier; nothing to push (reason=$REASON)"
exit 0
fi
dispatch "tiered pass (searchnodes/heavynodes -> receivers -> remainder)" "[$actions]"
log "grid highstate complete (reason=$REASON)"
exit 0
+57 -7
View File
@@ -24,7 +24,10 @@ else
POSTVERSION=$INSTALLEDVERSION
fi
INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}')
BATCHSIZE=5
# Optional -b override for the grid highstate batch size (a count like "5" or a
# percentage like "25%"). Empty means so-grid-highstate uses the salt:auto_apply:batch
# pillar default.
BATCHSIZE=
SOUP_LOG=/root/soup.log
SOUP_DEBUG_LOG=/root/soup-debug.log
WHATWOULDYOUSAYYAHDOHERE=soup
@@ -452,6 +455,31 @@ highstate() {
salt-call state.highstate -l info queue=True
}
push_grid_highstate() {
# Drive a batched, role-tiered highstate across the rest of the grid so remote minions
# pick up this upgrade now instead of waiting up to ~2.5 hours for their own scheduled
# highstate (the schedule moved from 15 to 120 minutes). so-grid-highstate does the work
# via orch.push_batch; it only exists once the manager highstate has deployed this
# version's sbin files, so guard on it. Launch fully detached (setsid) so it survives an
# SSH drop, and never let it affect soup's exit status -- it is best-effort with the
# scheduled highstate as backstop.
if [[ ! -x /usr/sbin/so-grid-highstate ]]; then
echo "so-grid-highstate not present; remote nodes will converge on their scheduled highstate."
return 0
fi
local extra_args=()
if [[ $SALTUPGRADED == true || $UPGRADESALT -eq 1 ]]; then
extra_args+=(--salt-upgraded)
fi
if [[ -n "$BATCHSIZE" ]]; then
extra_args+=(--batch "$BATCHSIZE")
fi
echo "Dispatching a grid-wide highstate to remote nodes. Progress: /opt/so/log/salt/so-grid-highstate.log"
setsid nohup /usr/sbin/so-grid-highstate --reason soup "${extra_args[@]}" >/dev/null 2>&1 &
}
masterlock() {
echo "Locking Salt Master"
mv -v $TOPFILE $BACKUPTOPFILE
@@ -479,6 +507,7 @@ preupgrade_changes() {
[[ "$INSTALLEDVERSION" =~ ^2\.4\.21[0-9]+$ ]] && up_to_3.0.0
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
[[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0
[[ "$INSTALLEDVERSION" == "3.2.0" ]] && up_to_3.3.0
true
}
@@ -496,6 +525,7 @@ postupgrade_changes() {
[[ "$POSTVERSION" =~ ^2\.4\.21[0-9]+$ ]] && post_to_3.0.0
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
[[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0
[[ "$POSTVERSION" == "3.2.0" ]] && post_to_3.3.0
# All applicable post-upgrade steps completed; clear the resume marker.
rm -f "$POSTVERSION_FILE"
true
@@ -878,7 +908,7 @@ post_to_3.1.0() {
recollate_postgres() {
echo ""
echo "Recollating PostgreSQL databases. The following output may contain warnings about a version mismatch, followed by a note indicating that the collation version has been changed."
for db in postgres securityonion so_telegraf; do
for db in template1 postgres securityonion so_telegraf; do
docker exec so-postgres psql -U postgres $db -c "reindex database $db"
docker exec so-postgres psql -U postgres $db -c "alter database $db refresh collation version"
done
@@ -1004,9 +1034,19 @@ post_to_3.2.0() {
set_postversion 3.2.0
}
### 3.2.0 End ###
### 3.2.0 Scripts ###
up_to_3.3.0() {
INSTALLEDVERSION=3.3.0
}
post_to_3.3.0() {
# Recollate again since some internal DBs were excluded during 3.2.0 soup
recollate_postgres
}
### 3.3.0 End ###
repo_sync() {
echo "Sync the local repo."
@@ -1936,6 +1976,9 @@ main() {
# rather than reporting "already latest". The soversion/pillar writes in
# update_version are no-ops here since the version is unchanged for a hotfix.
update_version
# Push the hotfix out to the rest of the grid rather than waiting for the scheduled
# highstate. Hotfixes never upgrade Salt, so no --salt-upgraded pass is needed.
push_grid_highstate
else
SOUP_UPGRADE_STARTED=true
echo ""
@@ -2102,13 +2145,18 @@ main() {
if [[ $NUM_MINIONS -gt 1 ]]; then
# Actively drive the rest of the grid to this version now. The scheduled highstate
# runs only every 120 minutes (salt:schedule:highstate_interval_minutes), so without
# this remote nodes could sit on the old version for a couple of hours after soup finishes.
push_grid_highstate
cat << EOF
This appears to be a distributed deployment. Other nodes should update themselves at the next Salt highstate (typically within 15 minutes). Do not manually restart anything until you know that all the search/heavy nodes in your deployment are updated. This is especially important if you are using true clustering for Elasticsearch.
This appears to be a distributed deployment. soup has dispatched a batched, grid-wide highstate to update the other nodes now: Elasticsearch data nodes (search/heavy nodes) first, then receivers, then sensors and the remaining nodes. Progress is logged to /opt/so/log/salt/so-grid-highstate.log, and you can watch nodes update from the Grid section of SOC. Do not manually restart anything until you know that all the search/heavy nodes in your deployment are updated. This is especially important if you are using true clustering for Elasticsearch.
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.
Nodes are updated in batches, 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. Any node that is offline or missed a batch will converge on its own scheduled highstate (every 120 minutes by default).
If it looks like youre 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.
@@ -2148,8 +2196,10 @@ while getopts ":b:f:y" opt; do
case ${opt} in
b )
BATCHSIZE="$OPTARG"
if ! [[ "$BATCHSIZE" =~ ^[1-9][0-9]*$ ]]; then
echo "Batch size must be a number greater than 0."
# Accept either a plain count (e.g. 5) or a percentage (e.g. 25%); passed through
# to so-grid-highstate --batch, which salt's batch/batch_wait accepts in both forms.
if ! [[ "$BATCHSIZE" =~ ^[1-9][0-9]*%?$ ]]; then
echo "Batch size must be a number greater than 0, optionally with a trailing % (e.g. 5 or 25%)."
exit 1
fi
;;
+23 -7
View File
@@ -96,14 +96,14 @@ http {
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header referrer-Policy no-referrer;
add_header Referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_ecdh_curve X25519:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
}
@@ -138,14 +138,14 @@ http {
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header referrer-Policy no-referrer;
add_header Referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_ecdh_curve X25519:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
@@ -172,14 +172,14 @@ http {
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header referrer-Policy no-referrer;
add_header Referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_ecdh_curve X25519:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
@@ -198,6 +198,10 @@ http {
}
location / {
if ($http_authorization ~* "^Bearer .*$") {
return 401;
}
auth_request /auth/sessions/whoami;
auth_request_set $userid $upstream_http_x_kratos_authenticated_identity_id;
proxy_set_header x-user-id $userid;
@@ -218,6 +222,13 @@ http {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob: wss:; frame-ancestors 'self'";
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy no-referrer;
}
location ~ ^/auth/.*?(login|oidc/callback) {
@@ -383,6 +394,11 @@ http {
if ($http_authorization = "") {
return 403;
}
if ($http_authorization ~* "^Bearer .*$") {
return 401;
}
proxy_pass http://{{ GLOBALS.manager }}:9822/;
proxy_read_timeout 90;
proxy_connect_timeout 90;
@@ -399,7 +415,7 @@ http {
error_page 429 = @error429;
location @error401 {
if ($request_uri ~* (^.*/api/.*|^/connect/.*|^/oauth2/.*|^/.*\.map$)) {
if ($request_uri ~* (^.*/api/.*|^.*/login.*|^.*/logout.*|^/connect/.*|^/oauth2/.*|^/.*\.map$)) {
return 401;
}
+1 -1
View File
@@ -242,7 +242,7 @@ versionlock:
# grain (compound supports nested grain matching via G@<key>:<subkey>:<value>).
# pillar/vm/soc_vm.sls write path is referenced at salt/_runners/setup_hypervisor.py:856.
vm:
- state: vm
- state: vm.user
tgt: 'G@salt-cloud:driver:libvirt'
# zeek: sensor_roles + so-import (5 roles).
+6
View File
@@ -60,6 +60,9 @@ so_repo:
{% endif %}
- enabled: 1
- gpgcheck: 1
{% if not GLOBALS.is_manager %}
- sslverify: 0
{% endif %}
# Only assign the kernel repo once this node's running salt matches the version this
# SO release ships. During a soup the grid is mid-salt-upgrade; gating here keeps the
@@ -77,6 +80,9 @@ so_kernel_repo:
{% endif %}
- enabled: 1
- gpgcheck: 1
{% if not GLOBALS.is_manager %}
- sslverify: 0
{% endif %}
# Supplementary kernel repo: tolerate it being empty/unreachable (e.g. before the
# manager has populated /nsm/kernelrepo) so a missing repomd.xml can't make every
# dnf/pkg operation on the grid fail.
+17 -3
View File
@@ -1512,6 +1512,10 @@ soc:
rulesetName: sos-resources-ag
branch: main
folder: securityonion-normalized
- repo: file:///nsm/airgap-resources/playbooks/securityonion-resources-playbooks
rulesetName: sos-published-ag
branch: published
folder: sigma
assistant:
systemPromptAddendum: ""
systemPromptAddendumMaxLength: 50000
@@ -1530,9 +1534,9 @@ soc:
healthTimeoutSeconds: 5
agentic: false
agentMapping:
Orchestrator: Claude Sonnet
Investigator: Claude Sonnet
DetectionEngineer: Claude Sonnet
Orchestrator: sonnet@SOAI
Investigator: gemma@SOAI
DetectionEngineer: gemma@SOAI
onionconfig:
saltstackDir: /opt/so/saltstack
bypassEnabled: false
@@ -2714,4 +2718,14 @@ soc:
enabled: true
adapter: SOAI
charsPerTokenEstimate: 4
- id: gemma
displayName: Gemma
origin: USA
contextLimitSmall: 256000
contextLimitLarge: 256000
lowBalanceColorAlert: 500000
enabled: true
adapter: SOAI
charsPerTokenEstimate: 4
@@ -12,6 +12,30 @@ transformations:
process.command_line: process.command_line.caseless
process.parent.command_line: process.parent.command_line.caseless
file.path: file.path.caseless
# entity_id pivots must also match processes that were already running when the
# agent started: Defend emits already_running (event.type:info), not start.
# Kept out of Playbook sigma query because Sysmon has no equivalent concept.
# contains_field is exact, so child pivots (process.parent.entity_id) stay
# start-only. Drop must precede add;
- id: playbook_process_lifecycle_drop_start_scope
type: drop_detection_item
field_name_conditions:
- type: include_fields
fields: ['event.type']
rule_conditions:
- type: logsource
category: process_creation
- type: contains_field
field: process.entity_id
- id: playbook_process_lifecycle_add-fields
type: add_condition
conditions:
event.type: ['start', 'info']
rule_conditions:
- type: logsource
category: process_creation
- type: contains_field
field: process.entity_id
# file_activity: playbook-only pseudo-category spanning all file operations.
- id: playbook_file_activity_add-fields
type: add_condition
+15 -3
View File
@@ -68,13 +68,25 @@ transformations:
- type: logsource
category: antivirus
# OS-agnostic process_creation scoping for product-less (NIDS/host-pivot) rules.
# pySigma: rule_cond_expr requires rule_conditions as a mapping, not a list.
- id: process_creation_os_agnostic
type: add_condition
conditions:
event.category: process
rule_conditions:
- type: logsource
category: process_creation
pc_cat:
type: logsource
category: process_creation
pc_win:
type: logsource
product: windows
pc_mac:
type: logsource
product: macos
pc_lin:
type: logsource
product: linux
rule_cond_expr: "pc_cat and not (pc_win or pc_mac or pc_lin)"
# 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
@@ -630,4 +642,4 @@ transformations:
tags: '*file'
rule_conditions:
- type: logsource
category: file
category: file
Binary file not shown.