From 698a746d6da370f41ca7f18637b53d1c368a6bf1 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 23 Jun 2026 13:19:56 -0400 Subject: [PATCH 01/10] 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/10] 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 f45631af3ae03b4e684ac8327a8bdbeabe624356 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 24 Jun 2026 12:15:10 -0400 Subject: [PATCH 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 10/10] 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