From 698a746d6da370f41ca7f18637b53d1c368a6bf1 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 23 Jun 2026 13:19:56 -0400 Subject: [PATCH 01/23] Add UEK8 kernel repo support across install and grid Mirror the kernel repo to full parity with the main package repo so the grid can pull the Oracle UEK8 kernel: - setup/so-functions: securityonion_repo() emits a [securityonionkernel] section in every branch (mirrorlist on non-airgap, https://$MSRV/kernelrepo for airgap/minion, file:///nsm/kernelrepo/ for manager); repo_sync_local() and create_repo() sync and build /nsm/kernelrepo. - manager/init.sls: create /nsm/kernelrepo and deploy mirror-kernel.txt. - nginx/enabled.sls: serve /nsm/kernelrepo at https:///kernelrepo. - repo/client/oracle.sls: add so_kernel_repo, gated by onlyif test -e /opt/so/state/nic_names_pinned so the kernel repo is only assigned once NICs are pinned by MAC. - update_packages(): run so-nic-pin before the dnf update that pulls the kernel, freezing interface names and dropping the pin marker so the kernel isn't downgraded then re-upgraded on the first highstate. --- salt/manager/files/mirror-kernel.txt | 2 ++ salt/manager/files/repodownload.conf | 7 ++++- salt/manager/init.sls | 17 ++++++++++++ salt/manager/tools/sbin/so-repo-sync | 4 +++ salt/nginx/enabled.sls | 1 + salt/repo/client/oracle.sls | 16 +++++++++++ setup/so-functions | 41 ++++++++++++++++++++++++++++ 7 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 salt/manager/files/mirror-kernel.txt diff --git a/salt/manager/files/mirror-kernel.txt b/salt/manager/files/mirror-kernel.txt new file mode 100644 index 000000000..1d9ce75d2 --- /dev/null +++ b/salt/manager/files/mirror-kernel.txt @@ -0,0 +1,2 @@ +https://repo.securityonion.net/file/so-repo/prod/3/oracle/9-uek8 +https://repo-alt.securityonion.net/prod/3/oracle/9-uek8 diff --git a/salt/manager/files/repodownload.conf b/salt/manager/files/repodownload.conf index 3c156a9db..67ae4b121 100644 --- a/salt/manager/files/repodownload.conf +++ b/salt/manager/files/repodownload.conf @@ -10,4 +10,9 @@ keepcache=0 name=Security Onion Repo repo mirrorlist=file:///opt/so/conf/reposync/mirror.txt enabled=1 -gpgcheck=1 \ No newline at end of file +gpgcheck=1 +[securityonionkernel] +name=Security Onion Repo repo +mirrorlist=file:///opt/so/conf/reposync/mirror-kernel.txt +enabled=1 +gpgcheck=1 diff --git a/salt/manager/init.sls b/salt/manager/init.sls index 2353bb64b..d6c154efa 100644 --- a/salt/manager/init.sls +++ b/salt/manager/init.sls @@ -86,6 +86,16 @@ repo_dir: - group - show_changes: False +kernelrepo_dir: + file.directory: + - name: /nsm/kernelrepo + - user: socore + - group: socore + - recurse: + - user + - group + - show_changes: False + manager_sbin: file.recurse: - name: /usr/sbin @@ -122,6 +132,13 @@ so-repo-mirrorlist: - user: socore - group: socore +so-repo-kernel-mirrorlist: + file.managed: + - name: /opt/so/conf/reposync/mirror-kernel.txt + - source: salt://manager/files/mirror-kernel.txt + - user: socore + - group: socore + so-repo-sync: {% if MANAGERMERGED.reposync.enabled %} cron.present: diff --git a/salt/manager/tools/sbin/so-repo-sync b/salt/manager/tools/sbin/so-repo-sync index a0393a36b..bc90122d3 100755 --- a/salt/manager/tools/sbin/so-repo-sync +++ b/salt/manager/tools/sbin/so-repo-sync @@ -10,5 +10,9 @@ NOROOT=1 set -e curl --retry 5 --retry-delay 60 -A "reposync/$(sync_options)" https://sigs.securityonion.net/checkup --output /tmp/checkup + dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionsync --download-metadata -p /nsm/repo/ createrepo /nsm/repo + +dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernel --download-metadata -p /nsm/kernelrepo/ +createrepo /nsm/kernelrepo diff --git a/salt/nginx/enabled.sls b/salt/nginx/enabled.sls index 2e4c9631c..40fde5b0e 100644 --- a/salt/nginx/enabled.sls +++ b/salt/nginx/enabled.sls @@ -59,6 +59,7 @@ so-nginx: - /opt/so/conf/navigator/layers/:/opt/socore/html/navigator/assets/so:ro - /opt/so/conf/navigator/config.json:/opt/socore/html/navigator/assets/config.json:ro - /nsm/repo:/opt/socore/html/repo:ro + - /nsm/kernelrepo:/opt/socore/html/kernelrepo:ro - /nsm/rules:/nsm/rules:ro {% if NGINXMERGED.external_suricata %} - /opt/so/rules/nids/suri:/surirules:ro diff --git a/salt/repo/client/oracle.sls b/salt/repo/client/oracle.sls index 70f529830..8c8a1ac0a 100644 --- a/salt/repo/client/oracle.sls +++ b/salt/repo/client/oracle.sls @@ -57,6 +57,22 @@ so_repo: - enabled: 1 - gpgcheck: 1 +so_kernel_repo: + pkgrepo.managed: + - name: securityonionkernel + - humanname: Security Onion Kernel Repo + {% if GLOBALS.is_manager %} + - baseurl: file:///nsm/kernelrepo/ + {% else %} + - baseurl: https://{{ GLOBALS.repo_host }}/kernelrepo + {% endif %} + - enabled: 1 + - gpgcheck: 1 + # Only assign the kernel repo once physical NIC names are pinned by MAC, so the + # UEK8 kernel update can't renumber interfaces SO binds by name (see pin_nic_names + # in salt/common/init.sls, which drops this marker via /usr/sbin/so-nic-pin). + - onlyif: 'test -e /opt/so/state/nic_names_pinned' + {% endif %} # TODO: Add a pillar entry for custom repos diff --git a/setup/so-functions b/setup/so-functions index 2d5181dc1..e8f6eb742 100755 --- a/setup/so-functions +++ b/setup/so-functions @@ -886,6 +886,7 @@ create_repo() { title "Create the repo directory" logCmd "dnf -y install yum-utils createrepo_c" logCmd "createrepo /nsm/repo" + logCmd "createrepo /nsm/kernelrepo" } @@ -1812,6 +1813,13 @@ securityonion_repo() { echo "mirrorlist=file:///etc/yum/mirror.txt" >> /etc/yum.repos.d/securityonion.repo echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "https://repo.securityonion.net/file/so-repo/prod/3/oracle/9-uek8" > /etc/yum/mirror-kernel.txt + echo "https://so-repo-east.s3.us-east-005.backblazeb2.com/prod/3/oracle/9-uek8" >> /etc/yum/mirror-kernel.txt + echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo + echo "name=Security Onion Kernel Repo repo" >> /etc/yum.repos.d/securityonion.repo + echo "mirrorlist=file:///etc/yum/mirror-kernel.txt" >> /etc/yum.repos.d/securityonion.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo logCmd "dnf repolist" else echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo @@ -1820,6 +1828,12 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo + echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonion.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo logCmd "dnf repolist" fi elif [[ ! $waitforstate ]]; then @@ -1829,12 +1843,23 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo + echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonion.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo elif [[ $waitforstate ]]; then echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo echo "name=Security Onion Repo" >> /etc/yum.repos.d/securityonion.repo echo "baseurl=file:///nsm/repo/" >> /etc/yum.repos.d/securityonion.repo echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo + echo "baseurl=file:///nsm/kernelrepo/" >> /etc/yum.repos.d/securityonion.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo fi logCmd "dnf repolist all" if [[ $waitforstate ]]; then @@ -1850,9 +1875,12 @@ repo_sync_local() { # Sync the repo from the SO repo locally. info "Adding Repo Download Configuration" mkdir -p /nsm/repo + mkdir -p /nsm/kernelrepo mkdir -p /opt/so/conf/reposync/cache echo "https://repo.securityonion.net/file/so-repo/prod/3/oracle/9" > /opt/so/conf/reposync/mirror.txt echo "https://repo-alt.securityonion.net/prod/3/oracle/9" >> /opt/so/conf/reposync/mirror.txt + echo "https://repo.securityonion.net/file/so-repo/prod/3/oracle/9-uek8" > /opt/so/conf/reposync/mirror-kernel.txt + echo "https://repo-alt.securityonion.net/prod/3/oracle/9-uek8" >> /opt/so/conf/reposync/mirror-kernel.txt echo "[main]" > /opt/so/conf/reposync/repodownload.conf echo "gpgcheck=1" >> /opt/so/conf/reposync/repodownload.conf echo "installonly_limit=3" >> /opt/so/conf/reposync/repodownload.conf @@ -1866,12 +1894,18 @@ repo_sync_local() { echo "mirrorlist=file:///opt/so/conf/reposync/mirror.txt" >> /opt/so/conf/reposync/repodownload.conf echo "enabled=1" >> /opt/so/conf/reposync/repodownload.conf echo "gpgcheck=1" >> /opt/so/conf/reposync/repodownload.conf + echo "[securityonionkernel]" >> /opt/so/conf/reposync/repodownload.conf + echo "name=Security Onion Kernel Repo repo" >> /opt/so/conf/reposync/repodownload.conf + echo "mirrorlist=file:///opt/so/conf/reposync/mirror-kernel.txt" >> /opt/so/conf/reposync/repodownload.conf + echo "enabled=1" >> /opt/so/conf/reposync/repodownload.conf + echo "gpgcheck=1" >> /opt/so/conf/reposync/repodownload.conf logCmd "dnf repolist" if [[ ! $is_airgap ]]; then curl --retry 5 --retry-delay 60 -A "netinstall/$SOVERSION/$OS/$(uname -r)/1" https://sigs.securityonion.net/checkup --output /tmp/install retry 5 60 "dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionsync --download-metadata -p /nsm/repo/" >> "$setup_log" 2>&1 || fail_setup + retry 5 60 "dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernel --download-metadata -p /nsm/kernelrepo/" >> "$setup_log" 2>&1 || fail_setup # After the download is complete run createrepo create_repo fi @@ -2228,6 +2262,13 @@ update_sudoers_for_testing() { } update_packages() { + # Pin physical NIC names by MAC BEFORE pulling packages, so the UEK8 kernel that + # the update below installs can't renumber the interfaces SO binds by name. Doing + # it here (instead of waiting for the common highstate) also drops the + # /opt/so/state/nic_names_pinned marker that gates the kernel repo, so the kernel + # repo is assigned on the very first highstate and the kernel isn't downgraded and + # then re-upgraded. Run-once: so-nic-pin no-ops if the marker already exists. + logCmd "bash ../salt/common/tools/sbin/so-nic-pin" logCmd "dnf repolist" logCmd "dnf -y update --allowerasing --exclude=salt*,docker*,containerd*" RMREPOFILES=("oracle-linux-ol9.repo" "uek-ol9.repo" "virt-ol9.repo") From 8e2753aeb833de6581c4b1947e071ad9f4cf4e10 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 23 Jun 2026 13:53:14 -0400 Subject: [PATCH 02/23] Fix duplicate securityonionkernel repo definition The install bootstrap appended the [securityonionkernel] section to the shared /etc/yum.repos.d/securityonion.repo, but the salt state so_kernel_repo (name securityonionkernel) manages its own canonical file /etc/yum.repos.d/securityonionkernel.repo. At highstate both files defined the same repo id, so dnf failed with "repository securityonionkernel is listed more than 1 time". Write the bootstrap kernel repo to /etc/yum.repos.d/securityonionkernel.repo in all four securityonion_repo() branches so the id lives in exactly one file and salt edits it in place. Mirrors how the main repo's runtime id matches its file name. --- setup/so-functions | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/setup/so-functions b/setup/so-functions index e8f6eb742..15856d710 100755 --- a/setup/so-functions +++ b/setup/so-functions @@ -1815,11 +1815,11 @@ securityonion_repo() { echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo echo "https://repo.securityonion.net/file/so-repo/prod/3/oracle/9-uek8" > /etc/yum/mirror-kernel.txt echo "https://so-repo-east.s3.us-east-005.backblazeb2.com/prod/3/oracle/9-uek8" >> /etc/yum/mirror-kernel.txt - echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo - echo "name=Security Onion Kernel Repo repo" >> /etc/yum.repos.d/securityonion.repo - echo "mirrorlist=file:///etc/yum/mirror-kernel.txt" >> /etc/yum.repos.d/securityonion.repo - echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo - echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" > /etc/yum.repos.d/securityonionkernel.repo + echo "name=Security Onion Kernel Repo repo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "mirrorlist=file:///etc/yum/mirror-kernel.txt" >> /etc/yum.repos.d/securityonionkernel.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo logCmd "dnf repolist" else echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo @@ -1828,12 +1828,12 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo - echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo - echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo - echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonion.repo - echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo - echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo - echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" > /etc/yum.repos.d/securityonionkernel.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "sslverify=0" >> /etc/yum.repos.d/securityonionkernel.repo logCmd "dnf repolist" fi elif [[ ! $waitforstate ]]; then @@ -1843,23 +1843,23 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo - echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo - echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo - echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonion.repo - echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo - echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo - echo "sslverify=0" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" > /etc/yum.repos.d/securityonionkernel.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "baseurl=https://$MSRV/kernelrepo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "sslverify=0" >> /etc/yum.repos.d/securityonionkernel.repo elif [[ $waitforstate ]]; then echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo echo "name=Security Onion Repo" >> /etc/yum.repos.d/securityonion.repo echo "baseurl=file:///nsm/repo/" >> /etc/yum.repos.d/securityonion.repo echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo - echo "[securityonionkernel]" >> /etc/yum.repos.d/securityonion.repo - echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonion.repo - echo "baseurl=file:///nsm/kernelrepo/" >> /etc/yum.repos.d/securityonion.repo - echo "enabled=1" >> /etc/yum.repos.d/securityonion.repo - echo "gpgcheck=1" >> /etc/yum.repos.d/securityonion.repo + echo "[securityonionkernel]" > /etc/yum.repos.d/securityonionkernel.repo + echo "name=Security Onion Kernel Repo" >> /etc/yum.repos.d/securityonionkernel.repo + echo "baseurl=file:///nsm/kernelrepo/" >> /etc/yum.repos.d/securityonionkernel.repo + echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo fi logCmd "dnf repolist all" if [[ $waitforstate ]]; then From 81ebea04511511cc4ca131e357ef2fa226e0b245 Mon Sep 17 00:00:00 2001 From: Dan Marr Date: Tue, 23 Jun 2026 16:07:30 -0400 Subject: [PATCH 03/23] Fix non-root exit checks at start of so-setup --- setup/so-setup | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup/so-setup b/setup/so-setup index 6c77e781c..dd024e275 100755 --- a/setup/so-setup +++ b/setup/so-setup @@ -9,14 +9,17 @@ # Make sure you are root before doing anything uid="$(id -u)" if [ "$uid" -ne 0 ]; then - echo "This script must be run using sudo!" - fail_setup + echo "This script must be run using sudo!" >&2 + exit 1 fi # Save the original argument array since we modify it original_args=("$@") -cd "$(dirname "$0")" || fail_setup +cd "$(dirname "$0")" || { + echo "Unable to change to setup directory" >&2 + exit 1 +} echo "Getting started..." From f45631af3ae03b4e684ac8327a8bdbeabe624356 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 24 Jun 2026 12:15:10 -0400 Subject: [PATCH 04/23] Guard kernel reposync on its config section existing During soup, so-repo-sync runs before the highstate deploys the new repodownload.conf. On the first upgrade to a kernel-aware version the on-disk config lacks the [securityonionkernel] section, so dnf aborts with "Unknown repo: 'securityonionkernel'" (set -e kills soup). Guard the kernel reposync on the section being present; the next sync after the highstate deploys it picks it up. --- salt/manager/tools/sbin/so-repo-sync | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/salt/manager/tools/sbin/so-repo-sync b/salt/manager/tools/sbin/so-repo-sync index bc90122d3..6c1b9d509 100755 --- a/salt/manager/tools/sbin/so-repo-sync +++ b/salt/manager/tools/sbin/so-repo-sync @@ -14,5 +14,12 @@ curl --retry 5 --retry-delay 60 -A "reposync/$(sync_options)" https://sigs.secur dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionsync --download-metadata -p /nsm/repo/ createrepo /nsm/repo -dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernel --download-metadata -p /nsm/kernelrepo/ -createrepo /nsm/kernelrepo +# 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: 'securityonionkernel'" aborting the sync (set -e). The next sync after the +# highstate deploys the section will pick it up. +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 From 27c1c35e62e90f6d11ae92817abda2a443ab992f Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 24 Jun 2026 13:20:10 -0400 Subject: [PATCH 05/23] Mark kernel repo skip_if_unavailable so an empty repo can't brick dnf When the kernel repo is assigned but /nsm/kernelrepo isn't populated yet, its missing repomd.xml makes every dnf/pkg operation fail (e.g. pkg.held for salt during highstate). The kernel repo is supplementary, so set skip_if_unavailable=1 in both the salt-managed client repo and the four install-time bootstrap repo files; dnf ignores it until it is populated instead of aborting. The main repo stays strict. --- salt/repo/client/oracle.sls | 4 ++++ setup/so-functions | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/salt/repo/client/oracle.sls b/salt/repo/client/oracle.sls index 8c8a1ac0a..2019a56d1 100644 --- a/salt/repo/client/oracle.sls +++ b/salt/repo/client/oracle.sls @@ -68,6 +68,10 @@ so_kernel_repo: {% endif %} - enabled: 1 - gpgcheck: 1 + # 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. + - skip_if_unavailable: 1 # Only assign the kernel repo once physical NIC names are pinned by MAC, so the # UEK8 kernel update can't renumber interfaces SO binds by name (see pin_nic_names # in salt/common/init.sls, which drops this marker via /usr/sbin/so-nic-pin). diff --git a/setup/so-functions b/setup/so-functions index 15856d710..b9a061168 100755 --- a/setup/so-functions +++ b/setup/so-functions @@ -1820,6 +1820,9 @@ securityonion_repo() { echo "mirrorlist=file:///etc/yum/mirror-kernel.txt" >> /etc/yum.repos.d/securityonionkernel.repo echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo + # Supplementary kernel repo: tolerate it being empty/unreachable so a missing + # repomd.xml can't make every dnf operation fail before the repo is populated. + echo "skip_if_unavailable=1" >> /etc/yum.repos.d/securityonionkernel.repo logCmd "dnf repolist" else echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo @@ -1834,6 +1837,7 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonionkernel.repo + echo "skip_if_unavailable=1" >> /etc/yum.repos.d/securityonionkernel.repo logCmd "dnf repolist" fi elif [[ ! $waitforstate ]]; then @@ -1849,6 +1853,7 @@ securityonion_repo() { echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "sslverify=0" >> /etc/yum.repos.d/securityonionkernel.repo + echo "skip_if_unavailable=1" >> /etc/yum.repos.d/securityonionkernel.repo elif [[ $waitforstate ]]; then echo "[securityonion]" > /etc/yum.repos.d/securityonion.repo echo "name=Security Onion Repo" >> /etc/yum.repos.d/securityonion.repo @@ -1860,6 +1865,7 @@ securityonion_repo() { echo "baseurl=file:///nsm/kernelrepo/" >> /etc/yum.repos.d/securityonionkernel.repo echo "enabled=1" >> /etc/yum.repos.d/securityonionkernel.repo echo "gpgcheck=1" >> /etc/yum.repos.d/securityonionkernel.repo + echo "skip_if_unavailable=1" >> /etc/yum.repos.d/securityonionkernel.repo fi logCmd "dnf repolist all" if [[ $waitforstate ]]; then From b0b022c3ada07e03fd4ecf2ee5ac9f06294e3174 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 24 Jun 2026 13:23:25 -0400 Subject: [PATCH 06/23] Seed an empty /nsm/kernelrepo so the manager repo is always valid so-repo-sync only populates /nsm/kernelrepo after the highstate, so on a manager the file:///nsm/kernelrepo repo could be assigned before any repodata exists, failing every dnf op. Run createrepo on the dir when repodata/repomd.xml is missing, leaving a synced repo untouched. --- salt/manager/init.sls | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/salt/manager/init.sls b/salt/manager/init.sls index d6c154efa..e77b1a601 100644 --- a/salt/manager/init.sls +++ b/salt/manager/init.sls @@ -96,6 +96,18 @@ kernelrepo_dir: - group - show_changes: False +# Ensure /nsm/kernelrepo is always a valid (if empty) repo before it is ever assigned to +# a client. Without repodata/repomd.xml an enabled file:///nsm/kernelrepo repo makes every +# dnf operation fail; so-repo-sync only populates it after the highstate, so seed an empty +# repo here. Only runs when repodata is missing, so it won't clobber a synced repo. +kernelrepo_init_empty: + cmd.run: + - name: createrepo /nsm/kernelrepo + - unless: 'test -e /nsm/kernelrepo/repodata/repomd.xml' + - require: + - file: kernelrepo_dir + - pkg: install_createrepo + manager_sbin: file.recurse: - name: /usr/sbin From d0edfd213176b75ea7bfe970df4822a7c8ee891e Mon Sep 17 00:00:00 2001 From: Josh Brower Date: Thu, 25 Jun 2026 14:18:43 -0400 Subject: [PATCH 07/23] set transport for ssl.established:false logs --- salt/elasticsearch/files/ingest/zeek.ssl | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/elasticsearch/files/ingest/zeek.ssl b/salt/elasticsearch/files/ingest/zeek.ssl index 0bd6fedb2..80a7b12da 100644 --- a/salt/elasticsearch/files/ingest/zeek.ssl +++ b/salt/elasticsearch/files/ingest/zeek.ssl @@ -5,6 +5,7 @@ { "remove": { "field": ["host"], "ignore_failure": true } }, { "json": { "field": "message", "target_field": "message2", "ignore_failure": true } }, { "rename": { "field": "message2.version", "target_field": "ssl.version", "ignore_missing": true } }, + { "set": { "description": "Set transport for the community_id processor", "if": "ctx.ssl?.version == null || !ctx.ssl.version.startsWith('DTLS')", "field": "network.transport", "value": "tcp", "ignore_failure": true } }, { "rename": { "field": "message2.cipher", "target_field": "ssl.cipher", "ignore_missing": true } }, { "rename": { "field": "message2.curve", "target_field": "ssl.curve", "ignore_missing": true } }, { "rename": { "field": "message2.server_name", "target_field": "ssl.server_name", "ignore_missing": true } }, From 94f31e1356e67230a607ba7ffb9b3b3e066ad92a Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Fri, 26 Jun 2026 09:21:11 -0400 Subject: [PATCH 08/23] Add so-kernel-upgrade to switch the boot default to the UEK8 kernel Installing kernel-uek-core adds a UEK8 (6.x) boot entry but doesn't make it the default, because grubby only auto-promotes within the running kernel's flavor lineage and we cross from a 5.x kernel to the new UEK8 flavor. so-kernel-upgrade finds the newest installed 6.x UEK kernel and grubby --set-default's it (idempotent, verifies the change, no reboot). --- salt/common/tools/sbin/so-kernel-upgrade | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 salt/common/tools/sbin/so-kernel-upgrade diff --git a/salt/common/tools/sbin/so-kernel-upgrade b/salt/common/tools/sbin/so-kernel-upgrade new file mode 100755 index 000000000..46d471051 --- /dev/null +++ b/salt/common/tools/sbin/so-kernel-upgrade @@ -0,0 +1,57 @@ +#!/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-kernel-upgrade — switch the boot default to the installed UEK8 (6.x) kernel. +# +# Security Onion is moving off the EL9 stock kernel / UEK7 (5.x) onto UEK8 (6.x). +# Installing the kernel-uek-core package adds a UEK8 boot entry but does NOT make it the +# default: kernel-install/grubby only auto-promote a new kernel within the running +# kernel's flavor lineage, and we're crossing from a 5.x kernel to the new 6.x UEK flavor. +# So even with UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box keeps booting +# the old kernel. This tool finds the newest installed 6.x UEK kernel and makes it the +# GRUB default via grubby so the next boot comes up on UEK8. +# +# Idempotent: if the UEK8 kernel is already the default it does nothing. It only sets the +# boot default; it does NOT reboot — the admin reboots the node on their own schedule. + +log() { echo "[so-kernel-upgrade] $*"; } + +[ "$(id -u)" -eq 0 ] || { log "must run as root"; exit 1; } +command -v grubby >/dev/null 2>&1 || { log "grubby not found"; exit 1; } + +# Newest installed UEK8 (6.x) kernel known to the bootloader. UEK8 vmlinuz paths look like +# /boot/vmlinuz-6.12.0-203.76.7.5.el9uek.x86_64; the 5.x UEK7 and 5.14 RHCK won't match. +target="$(grubby --info=ALL 2>/dev/null \ + | sed -n 's/^kernel="\(.*\)"$/\1/p' \ + | grep -E '/vmlinuz-6\.[0-9]+.*uek' \ + | sort -V | tail -1)" + +if [ -z "$target" ]; then + log "no installed 6.x UEK (UEK8) kernel found — confirm the kernel repo is assigned and" + log "'dnf update' has installed kernel-uek-core. Nothing to do." + exit 0 +fi + +current="$(grubby --default-kernel 2>/dev/null)" +if [ "$current" = "$target" ]; then + log "UEK8 kernel is already the boot default: $target" + exit 0 +fi + +log "current default kernel: ${current:-unknown}" +log "switching boot default to UEK8 kernel: $target" +grubby --set-default="$target" || { log "ERROR: grubby --set-default failed for $target"; exit 1; } + +# Verify the change actually took before claiming success. +now="$(grubby --default-kernel 2>/dev/null)" +if [ "$now" != "$target" ]; then + log "ERROR: default kernel is still '${now:-unknown}' after set-default" + exit 1 +fi + +log "boot default is now $target" +log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))." From 67a9abadf2292ac3005844ef4b581020adecc7ff Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Fri, 26 Jun 2026 09:21:11 -0400 Subject: [PATCH 09/23] Gate so_kernel_repo on running salt matching the shipped version During soup the grid is mid-salt-upgrade. Only assign the UEK8 kernel repo once the node's grains.saltversion matches salt.minion.version from minion.defaults.yaml, so the kernel repo and the update it enables don't activate until the node is fully on the target salt. --- salt/repo/client/oracle.sls | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/salt/repo/client/oracle.sls b/salt/repo/client/oracle.sls index 2019a56d1..bf0a02751 100644 --- a/salt/repo/client/oracle.sls +++ b/salt/repo/client/oracle.sls @@ -6,6 +6,10 @@ {% from 'repo/client/map.jinja' import REPOPATH with context %} {% from 'vars/globals.map.jinja' import GLOBALS %} +{% import_yaml 'salt/minion.defaults.yaml' as saltversion %} +{% set saltversion = saltversion.salt.minion.version %} +{% set INSTALLEDSALTVERSION = grains.saltversion %} + {% set role = grains.id.split('_') | last %} {% set MANAGER = salt['grains.get']('master') %} {% if grains['os'] == 'OEL' %} @@ -57,6 +61,11 @@ so_repo: - enabled: 1 - gpgcheck: 1 +# 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 +# UEK8 kernel repo (and the kernel update it enables) from activating until the node is +# fully on the target salt, the same way other states defer across the upgrade window. +{% if saltversion | string == INSTALLEDSALTVERSION | string %} so_kernel_repo: pkgrepo.managed: - name: securityonionkernel @@ -76,6 +85,7 @@ so_kernel_repo: # UEK8 kernel update can't renumber interfaces SO binds by name (see pin_nic_names # in salt/common/init.sls, which drops this marker via /usr/sbin/so-nic-pin). - onlyif: 'test -e /opt/so/state/nic_names_pinned' +{% endif %} {% endif %} From 339a5af4a3a607be62e87b518ca67cc528485414 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Fri, 26 Jun 2026 12:02:49 -0400 Subject: [PATCH 10/23] Serve /kernelrepo through nginx so minions can reach the kernel repo The /nsm/kernelrepo bind mount exposed the files, but without a matching location block external requests to /kernelrepo/ fell through to the SOC app and returned HTML, so minions hit 'repomd.xml parser error'. Add a /kernelrepo/ location mirroring /repo/. --- salt/nginx/etc/nginx.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/salt/nginx/etc/nginx.conf b/salt/nginx/etc/nginx.conf index 8150265f5..b7a70da2b 100644 --- a/salt/nginx/etc/nginx.conf +++ b/salt/nginx/etc/nginx.conf @@ -323,6 +323,16 @@ http { autoindex_localtime on; } + location /kernelrepo/ { + allow all; + sendfile on; + sendfile_max_chunk 1m; + autoindex on; + autoindex_exact_size off; + autoindex_format html; + autoindex_localtime on; + } + location /influxdb/ { auth_request /auth/sessions/whoami; rewrite /influxdb/api/(.*) /api/$1 break; From b3b7ecddede9956001a3be9f56d52e5c546a8d13 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:19:18 -0500 Subject: [PATCH 11/23] update so-stop | so-start | so-restart scripts --- salt/common/tools/sbin/so-common | 14 +++++++ salt/common/tools/sbin/so-restart | 54 ++++++++++++++++--------- salt/common/tools/sbin/so-start | 67 ++++++++++++++++++++++--------- salt/common/tools/sbin/so-stop | 38 ++++++++++++------ 4 files changed, 120 insertions(+), 53 deletions(-) diff --git a/salt/common/tools/sbin/so-common b/salt/common/tools/sbin/so-common index 812c1bb10..4e6580ae1 100755 --- a/salt/common/tools/sbin/so-common +++ b/salt/common/tools/sbin/so-common @@ -291,6 +291,20 @@ download_and_verify() { fi } +# check if container with name is running and optionally stop it +docker_check_running() { + # show running containers, only names + if docker ps --format '{{.Names}}' | grep -q "^so-${1}$"; then + if [[ "$2" == "--stop" ]]; then + docker stop "so-${1}" + fi + + return 0 + else + return 1 + fi +} + elastic_license() { read -r -d '' message <<- EOM diff --git a/salt/common/tools/sbin/so-restart b/salt/common/tools/sbin/so-restart index 7345078b8..14747d134 100755 --- a/salt/common/tools/sbin/so-restart +++ b/salt/common/tools/sbin/so-restart @@ -5,27 +5,41 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. - - -# Usage: so-restart kibana | playbook - . /usr/sbin/so-common -if [ $# -ge 1 ]; then +usage() { + echo "Usage: $0 [args]" + echo "" + echo "Supported args:" + echo " --force | -f Force stop all Salt jobs before starting component." + echo "" + echo "Examples:" + echo " $0 kibana Restart Kibana" + echo " $0 kibana --force Force stop all Salt jobs before restarting Kibana" + exit 1 +} - echo $banner - printf "Restarting $1...\n\nThis could take a while if another Salt job is running. \nRun this command with --force to stop all Salt jobs before proceeding.\n" - echo $banner - - if [ "$2" = "--force" ]; then - printf "\nForce-stopping all Salt jobs before proceeding\n\n" - salt-call saltutil.kill_all_jobs - fi - - case $1 in - "elastic-fleet") docker stop so-elastic-fleet && docker rm so-elastic-fleet && salt-call state.apply elasticfleet queue=True;; - *) docker stop so-$1 ; docker rm so-$1 ; salt-call state.apply $1 queue=True;; - esac -else - echo -e "\nPlease provide an argument by running like so-restart $component, or by using the component-specific script.\nEx. so-restart logstash, or so-logstash-restart\n" +if [[ $# -lt 1 ]]; then + usage fi + +#shellcheck disable=SC2154 +echo "$banner" +printf "Restarting %s...\n\nThis could take a while if another Salt job is running. \nRun this command with --force to stop all Salt jobs before proceeding.\n" "$1" +echo "$banner" +if [[ "$2" = "--force" ]] || [[ "$2" = "-f" ]]; then + printf "\nForce-stopping all Salt jobs before proceeding\n\n" + salt-call saltutil.kill_all_jobs +fi +case $1 in + "elastic-fleet"|"elasticfleet") + docker_check_running "elastic-fleet" "--stop" + docker rm "so-elastic-fleet" 2> /dev/null + salt-call state.apply elasticfleet queue=True + ;; + *) + docker_check_running "$1" "--stop" + docker rm "so-${1}" 2> /dev/null + salt-call state.apply "$1" queue=True + ;; +esac diff --git a/salt/common/tools/sbin/so-start b/salt/common/tools/sbin/so-start index 1a312a94d..a5c66ffe7 100755 --- a/salt/common/tools/sbin/so-start +++ b/salt/common/tools/sbin/so-start @@ -5,27 +5,54 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. - - -# Usage: so-start all | kibana | playbook - +# shellcheck disable=SC1091 . /usr/sbin/so-common -if [ $# -ge 1 ]; then - echo $banner - printf "Starting $1...\n\nThis could take a while if another Salt job is running. \nRun this command with --force to stop all Salt jobs before proceeding.\n" - echo $banner +usage() { + echo "Usage: $0 [args]" + echo "" + echo "Supported args:" + echo " --force | -f Force stop all Salt jobs before starting component." + echo "" + echo "Examples:" + echo " $0 kibana Start Kibana" + echo " $0 kibana --force Force stop all Salt jobs before starting Kibana" + exit 1 +} - if [ "$2" = "--force" ]; then - printf "\nForce-stopping all Salt jobs before proceeding\n\n" - salt-call saltutil.kill_all_jobs - fi - - case $1 in - "all") salt-call state.highstate queue=True;; - "elastic-fleet") if docker ps | grep -q so-$1; then printf "\n$1 is already running!\n\n"; else docker rm so-$1 >/dev/null 2>&1 ; salt-call state.apply elasticfleet queue=True; fi ;; - *) if docker ps | grep -E -q '^so-$1$'; then printf "\n$1 is already running\n\n"; else docker rm so-$1 >/dev/null 2>&1 ; salt-call state.apply $1 queue=True; fi ;; - esac -else - echo -e "\nPlease provide an argument by running like so-start $component, or by using the component-specific script.\nEx. so-start logstash, or so-logstash-start\n" +if [[ $# -lt 1 ]]; then + usage fi + +#shellcheck disable=SC2154 +echo "$banner" +printf "Starting %s...\n\nThis could take a while if another Salt job is running. \nRun this command with --force to stop all Salt jobs before proceeding.\n" "$1" +echo "$banner" +if [[ "$2" = "--force" ]] || [[ "$2" == "-f" ]]; then + printf "\nForce-stopping all Salt jobs before proceeding\n\n" + salt-call saltutil.kill_all_jobs +fi + +case "$1" in + "all") + salt-call state.highstate queue=True + ;; + "elastic-fleet"|"elasticfleet") + if docker_check_running "elastic-fleet"; then + printf "\nso-%s is already running!\n\n" "elastic-fleet" + /usr/sbin/so-status + else + docker rm "so-elastic-fleet" 2> /dev/null + salt-call state.apply elasticfleet queue=True + fi + ;; + *) + if docker_check_running "$1"; then + printf "\nso-%s is already running\n\n" "$1" + /usr/sbin/so-status + else + docker rm "so-${1}" 2> /dev/null + salt-call state.apply "$1" queue=True + fi + ;; +esac diff --git a/salt/common/tools/sbin/so-stop b/salt/common/tools/sbin/so-stop index 32e24f83a..d036a7b63 100755 --- a/salt/common/tools/sbin/so-stop +++ b/salt/common/tools/sbin/so-stop @@ -5,21 +5,33 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. - - -# Usage: so-stop kibana | playbook | thehive - +# shellcheck disable=SC1091 . /usr/sbin/so-common -if [ $# -ge 1 ]; then - echo $banner - printf "Stopping $1...\n" - echo $banner +usage() { + echo "Usage: $0 " + echo "" + echo "Examples:" + echo " $0 kibana Stop Kibana" + exit 1 +} - case $1 in - *) docker stop so-$1 ; docker rm so-$1 ;; - esac -else - echo -e "\nPlease provide an argument by running like so-stop $component, or by using the component-specific script.\nEx. so-stop logstash, or so-logstash-stop\n" +if [[ $# -lt 1 ]]; then + usage fi + +#shellcheck disable=SC2154 +echo "$banner" +printf "Stopping %s...\n" "$1" +echo "$banner" +case $1 in + "elasticfleet"|"elastic-fleet") + docker_check_running "elastic-fleet" "--stop" + docker rm "so-elastic-fleet" 2> /dev/null + ;; + *) + docker_check_running "$1" "--stop" + docker rm "so-${1}" 2> /dev/null + ;; +esac From 52574e21c67bdb7b3bdf75f627df47003158f65a Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Tue, 30 Jun 2026 09:40:23 -0400 Subject: [PATCH 12/23] suricata: treat in-progress rule reload as success so-suricata-reload-rules failed the surirulereload state when a rule reload was already running: suricatasc returns {"message":"Reload already in progress","return":"NOK"}, which never matched the expected output, so retry looped all 60 attempts (~3 min) and called fail. Wrap the suricatasc calls so an in-progress reload is treated as success (the in-flight reload picks up the new rules) while genuine container-not-ready conditions still retry and ultimately fail. --- .../tools/sbin/so-suricata-reload-rules | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/salt/suricata/tools/sbin/so-suricata-reload-rules b/salt/suricata/tools/sbin/so-suricata-reload-rules index e21e28e2f..aec6bc966 100644 --- a/salt/suricata/tools/sbin/so-suricata-reload-rules +++ b/salt/suricata/tools/sbin/so-suricata-reload-rules @@ -7,5 +7,19 @@ . /usr/sbin/so-common -retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c reload-rules /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time." -retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c ruleset-reload-nonblocking /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time." +reload_suricata_rules() { + # $1 = suricatasc command (reload-rules | ruleset-reload-nonblocking) + local output + output=$(docker exec so-suricata /opt/suricata/bin/suricatasc -c "$1" /var/run/suricata/suricata-command.socket) + echo "$output" + # A reload already running is fine — the new rules get picked up by it. + if [[ "$output" =~ "Reload already in progress" ]]; then + echo "A rule reload is already in progress; treating as success." + return 0 + fi + [[ "$output" =~ '{"message":"done","return":"OK"}' ]] && return 0 + return 1 +} + +retry 60 3 'reload_suricata_rules reload-rules' || fail "The Suricata container was not ready in time." +retry 60 3 'reload_suricata_rules ruleset-reload-nonblocking' || fail "The Suricata container was not ready in time." From 3b8459c6ec474c20b4e60c07b0577b6dd1fa98c2 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:43:42 -0500 Subject: [PATCH 13/23] soup upgrade kafka cluster metadata v4 --- salt/manager/tools/sbin/soup | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index 2b8680191..be99292d8 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -850,6 +850,28 @@ 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 @@ -867,6 +889,8 @@ post_to_3.2.0() { kibana_backport_streams_index_template + update_kafka_metadata "4.3" + POSTVERSION=3.2.0 } From 670d2b2757a7cd808d22aa05f6605c59a8b994a6 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:57:56 -0500 Subject: [PATCH 14/23] casing --- salt/manager/tools/sbin/soup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index be99292d8..3c4cbe7c4 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -864,7 +864,7 @@ update_kafka_metadata() { 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+=("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 From ee36f5f84c7ea9c6d3ab512377f732883fe6c3b4 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 1 Jul 2026 09:00:36 -0400 Subject: [PATCH 15/23] suricata: verify reloaded ruleset is newer than the rules file Treating an in-progress reload as instant success could report success while Suricata was still running a stale ruleset (the in-flight reload may have started before the new all-rulesets.rules was written). Make success conditional on Suricata actually having loaded the current ruleset: capture the rules-file mtime up front, trigger a blocking reload-rules, then query ruleset-reload-time and only succeed when last_reload >= mtime. An in-progress reload now retries (waits for it to clear so our own fresh reload runs) instead of short-circuiting, and a ruleset that never catches up within the retry window fails via fail(). Also drop the redundant ruleset-reload-nonblocking call (the verified blocking reload is authoritative and the async call was what left a reload running) and log human-readable timestamps. --- .../tools/sbin/so-suricata-reload-rules | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/salt/suricata/tools/sbin/so-suricata-reload-rules b/salt/suricata/tools/sbin/so-suricata-reload-rules index aec6bc966..b966e4bc0 100644 --- a/salt/suricata/tools/sbin/so-suricata-reload-rules +++ b/salt/suricata/tools/sbin/so-suricata-reload-rules @@ -7,19 +7,50 @@ . /usr/sbin/so-common -reload_suricata_rules() { - # $1 = suricatasc command (reload-rules | ruleset-reload-nonblocking) - local output - output=$(docker exec so-suricata /opt/suricata/bin/suricatasc -c "$1" /var/run/suricata/suricata-command.socket) - echo "$output" - # A reload already running is fine — the new rules get picked up by it. - if [[ "$output" =~ "Reload already in progress" ]]; then - echo "A rule reload is already in progress; treating as success." +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" + +# 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" + +# 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; } + +# 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 - [[ "$output" =~ '{"message":"done","return":"OK"}' ]] && return 0 + echo "Loaded ruleset is stale: last reload ($(fmt_time "$reload_epoch")) is older than rules file ($(fmt_time "$target_mtime")); retrying." return 1 } -retry 60 3 'reload_suricata_rules reload-rules' || fail "The Suricata container was not ready in time." -retry 60 3 'reload_suricata_rules ruleset-reload-nonblocking' || fail "The Suricata container was not ready in time." +retry 60 3 'reload_and_verify' || fail "Suricata did not load the current ruleset in time." From dc8c80633b59db2b15d0647c980800103d61b657 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:23:04 -0500 Subject: [PATCH 16/23] update airgap soup to sync uek repo from iso and retain latest packages only --- salt/manager/tools/sbin/soup | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index 3c4cbe7c4..92d89f5e6 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -245,6 +245,7 @@ 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 @@ -1004,13 +1005,19 @@ update_airgap_rules() { rsync -a $UPDATE_DIR/agrules/securityonion-resources/* /nsm/securityonion-resources/ } -update_airgap_repo() { +update_airgap_repos() { # Update the files in the repo - echo "Syncing new updates to /nsm/repo" - rsync -a $AGREPO/* /nsm/repo/ - echo "Creating repo" + echo "Syncing new updates to /nsm/repo & /nsm/kernelrepo" + # using --delete to replicate so-repo-sync behavior of keeping only latest packages in local repo + rsync -a --delete "$AGREPO"/ /nsm/repo/ + rsync -a --delete "$AGUEKREPO"/ /nsm/kernelrepo/ + 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() { @@ -1766,7 +1773,7 @@ main() { set -e if [[ $is_airgap -eq 0 ]]; then - update_airgap_repo + update_airgap_repos dnf clean all check_os_updates elif [[ $OS == 'oracle' ]]; then From e88eb65a4400b3d3b5f43bc765b9dc79ce1edb18 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:29:05 -0500 Subject: [PATCH 17/23] keep old packages for rollback ability --- salt/manager/tools/sbin/soup | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index 92d89f5e6..b84c38087 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -244,8 +244,7 @@ check_airgap() { is_airgap=0 UPDATE_DIR=/tmp/soagupdate/SecurityOnion AGDOCKER=/tmp/soagupdate/docker - AGREPO=/tmp/soagupdate/minimal/Packages - AGUEKREPO=/tmp/soagupdate/uek/Packages + AGREPO=/tmp/soagupdate/minimal/Packages AGUEKREPO=/tmp/soagupdate/uek/Packages else is_airgap=1 fi @@ -1008,9 +1007,9 @@ update_airgap_rules() { update_airgap_repos() { # Update the files in the repo echo "Syncing new updates to /nsm/repo & /nsm/kernelrepo" - # using --delete to replicate so-repo-sync behavior of keeping only latest packages in local repo - rsync -a --delete "$AGREPO"/ /nsm/repo/ - rsync -a --delete "$AGUEKREPO"/ /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/ dnf -y install yum-utils createrepo_c From c33db9d00fe82162b682532661fed7357b3ac1cd Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:12:39 -0500 Subject: [PATCH 18/23] remove outdated eval script and associated salt utility state --- pillar/data/addtotab.sh | 59 ----------------------------------- salt/allowed_states.map.jinja | 3 +- salt/top.sls | 6 ---- salt/utility/bin/eval | 29 ----------------- salt/utility/init.sls | 22 ------------- 5 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 pillar/data/addtotab.sh delete mode 100644 salt/utility/bin/eval delete mode 100644 salt/utility/init.sls diff --git a/pillar/data/addtotab.sh b/pillar/data/addtotab.sh deleted file mode 100644 index 65f9446dd..000000000 --- a/pillar/data/addtotab.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/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 diff --git a/salt/allowed_states.map.jinja b/salt/allowed_states.map.jinja index c831b45fe..72e4bbe82 100644 --- a/salt/allowed_states.map.jinja +++ b/salt/allowed_states.map.jinja @@ -37,8 +37,7 @@ 'elasticfleet', 'elasticfleet.manager', 'elasticsearch.cluster', - 'elastic-fleet-package-registry', - 'utility' + 'elastic-fleet-package-registry' ] %} {% set sensor_states = [ diff --git a/salt/top.sls b/salt/top.sls index cf743edd1..ffa43864c 100644 --- a/salt/top.sls +++ b/salt/top.sls @@ -83,7 +83,6 @@ base: - zeek - strelka - elastalert - - utility - elasticfleet - pcap.cleanup @@ -113,7 +112,6 @@ base: - zeek - strelka - elastalert - - utility - elasticfleet - stig - kafka @@ -141,7 +139,6 @@ base: - elastic-fleet-package-registry - kibana - elastalert - - utility - elasticfleet - stig - kafka @@ -168,7 +165,6 @@ base: - elastic-fleet-package-registry - kibana - elastalert - - utility - elasticfleet - kafka @@ -198,7 +194,6 @@ base: - elastic-fleet-package-registry - kibana - elastalert - - utility - elasticfleet - stig - kafka @@ -222,7 +217,6 @@ base: - elasticsearch - elastic-fleet-package-registry - kibana - - utility - suricata - zeek - elasticfleet diff --git a/salt/utility/bin/eval b/salt/utility/bin/eval deleted file mode 100644 index f30f0f421..000000000 --- a/salt/utility/bin/eval +++ /dev/null @@ -1,29 +0,0 @@ -#!/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\"]}}}}}" diff --git a/salt/utility/init.sls b/salt/utility/init.sls deleted file mode 100644 index 49bb2cb0c..000000000 --- a/salt/utility/init.sls +++ /dev/null @@ -1,22 +0,0 @@ -{% 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 %} From 24b75b4a2ba5ce8f4548bbfd3f72d1c1172d3c36 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:50:23 -0500 Subject: [PATCH 19/23] typo --- salt/manager/tools/sbin/soup | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index b84c38087..6725cc95c 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -244,7 +244,8 @@ check_airgap() { is_airgap=0 UPDATE_DIR=/tmp/soagupdate/SecurityOnion AGDOCKER=/tmp/soagupdate/docker - AGREPO=/tmp/soagupdate/minimal/Packages AGUEKREPO=/tmp/soagupdate/uek/Packages + AGREPO=/tmp/soagupdate/minimal/Packages + AGUEKREPO=/tmp/soagupdate/uek/Packages else is_airgap=1 fi From 87b9276c79784b892b0e9dd51eeeba952b535734 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:19:47 -0500 Subject: [PATCH 20/23] increase wait_for_so-kibana timeout to 10m --- salt/kibana/enabled.sls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/kibana/enabled.sls b/salt/kibana/enabled.sls index 1257f66c6..e8b561754 100644 --- a/salt/kibana/enabled.sls +++ b/salt/kibana/enabled.sls @@ -69,7 +69,7 @@ wait_for_so-kibana: - ssl: True - verify_ssl: False - status: 200 - - wait_for: 300 + - wait_for: 600 - request_interval: 15 - require: - docker_container: so-kibana From 69d77382f1ac1219c92a239d734b3505e5a7a43b Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 1 Jul 2026 15:12:53 -0400 Subject: [PATCH 21/23] suricata: timestamp each line of reload log output Route the reload/verify output (ours plus so-common's retry/fail lines) through a synchronous timestamping pipeline so every line in reload.log is prefixed with a date/time, and preserve the real exit code via PIPESTATUS. --- .../tools/sbin/so-suricata-reload-rules | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/salt/suricata/tools/sbin/so-suricata-reload-rules b/salt/suricata/tools/sbin/so-suricata-reload-rules index b966e4bc0..6db519413 100644 --- a/salt/suricata/tools/sbin/so-suricata-reload-rules +++ b/salt/suricata/tools/sbin/so-suricata-reload-rules @@ -11,13 +11,12 @@ 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" -# 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" - # 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 @@ -53,4 +52,14 @@ reload_and_verify() { return 1 } -retry 60 3 'reload_and_verify' || fail "Suricata did not load the current ruleset in time." +# 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]}" From 795aa898a30edbacb3d79769d23a0c26b6ad7729 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 1 Jul 2026 15:12:54 -0400 Subject: [PATCH 22/23] suricata: only reload rules once the ruleset file exists On a fresh install the surirulesync file.recurse creates .gitkeep before SOC has generated all-rulesets.rules. That change satisfied the surirulereload onchanges requisite, so the reload ran with no ruleset present, failed to stat the file, and reported the state (and install) as failed. Add an onlyif guard so the reload only runs when all-rulesets.rules exists. A .gitkeep-only sync now leaves the state a clean success (onlyif condition false); once SOC writes the ruleset, the reload fires normally. --- salt/suricata/enabled.sls | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/salt/suricata/enabled.sls b/salt/suricata/enabled.sls index d9d7f32ae..bb31b2c78 100644 --- a/salt/suricata/enabled.sls +++ b/salt/suricata/enabled.sls @@ -65,10 +65,11 @@ 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 From e7352eb841afe82603d7e0be2f3e4b0e69e7dbaf Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:17:55 -0500 Subject: [PATCH 23/23] duplicate repo name in so-repo-sync --- salt/manager/files/repodownload.conf | 4 ++-- salt/manager/tools/sbin/so-repo-sync | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/salt/manager/files/repodownload.conf b/salt/manager/files/repodownload.conf index 67ae4b121..9c9cb5109 100644 --- a/salt/manager/files/repodownload.conf +++ b/salt/manager/files/repodownload.conf @@ -11,8 +11,8 @@ name=Security Onion Repo repo mirrorlist=file:///opt/so/conf/reposync/mirror.txt enabled=1 gpgcheck=1 -[securityonionkernel] -name=Security Onion Repo repo +[securityonionkernelsync] +name=Security Onion Kernel Repo repo mirrorlist=file:///opt/so/conf/reposync/mirror-kernel.txt enabled=1 gpgcheck=1 diff --git a/salt/manager/tools/sbin/so-repo-sync b/salt/manager/tools/sbin/so-repo-sync index 6c1b9d509..d6a290c25 100755 --- a/salt/manager/tools/sbin/so-repo-sync +++ b/salt/manager/tools/sbin/so-repo-sync @@ -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: 'securityonionkernel'" aborting the sync (set -e). The next sync after the +# "Unknown repo: 'securityonionkernelsync'" aborting the sync (set -e). The next sync after the # highstate deploys the section will pick it up. -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/ +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/ createrepo /nsm/kernelrepo fi