diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup
index b6be60709..94fb1305f 100755
--- a/salt/manager/tools/sbin/soup
+++ b/salt/manager/tools/sbin/soup
@@ -344,6 +344,16 @@ check_cluster_health() {
check_fleet_server() {
echo "Checking that Elastic Fleet Server is responding."
+ # Before checking fleet health, check for and fix known issue with elastic-agent container and fs.protected_symlinks
+ local protected_symlinks=$(sysctl -b fs.protected_symlinks)
+ if [[ "$protected_symlinks" == "1" ]]; then
+ # disable fs.protected_symlinks and restart elasticfleet
+ sysctl -w fs.protected_symlinks=0
+ docker stop so-elastic-fleet; docker rm -f so-elastic-fleet
+ printf "\nUpdated sysctl fs.protected_symlinks. Restarting fleet before running health check and continuing with soup.\n"
+ salt-call state.apply elasticfleet queue=True
+ fi
+
# Modeled on the wait_for_so-elastic-fleet state check in elasticfleet/enabled.sls,
# which waits for HTTP 200 from the Fleet Server status API.
if curl -sk --fail --retry 3 --retry-delay 10 --max-time 30 "https://localhost:8220/api/status" > /dev/null 2>&1; then
@@ -1036,8 +1046,20 @@ post_to_3.2.0() {
}
### 3.2.0 End ###
-### 3.2.0 Scripts ###
+### 3.3.0 Scripts ###
+
+ # Sets fs.protected_symlinks=0
+ #
+ # Elastic Agent docker image chowns its directory to the running UID
+ # but does not chown the elastic-agent launcher symlink.
+ # Preventing non-root users from following that launcher symlink.
+disable_sysctl_fs_protected_symlink() {
+ salt -C 'I@stig:enabled' state.single sysctl.present name=fs.protected_symlinks value=0 config=/etc/sysctl.conf || true
+}
+
up_to_3.3.0() {
+ disable_sysctl_fs_protected_symlink
+
INSTALLEDVERSION=3.3.0
}
diff --git a/salt/salt/minion/boot_highstate.sls b/salt/salt/minion/boot_highstate.sls
index eb2596dad..b97972c9f 100644
--- a/salt/salt/minion/boot_highstate.sls
+++ b/salt/salt/minion/boot_highstate.sls
@@ -3,6 +3,8 @@
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
+{% from 'vars/globals.map.jinja' import GLOBALS %}
+
# Manages /etc/systemd/system/so-boot-highstate.service, a Type=oneshot
# RemainAfterExit=yes unit that runs `salt-call state.highstate` exactly once
# per system boot. Replaces the legacy `startup_states: highstate` minion
@@ -19,9 +21,25 @@ so_boot_highstate_unit_file:
- onchanges_in:
- module: systemd_reload
+# Non-managers never apply salt.minion during setup, so reaching this state means
+# setup is finished and the marker is safe to write unconditionally. This also
+# heals nodes installed before this fix, which have no marker and no legacy
+# startup_states line to grep for. Managers do highstate mid-setup, so they only
+# get the marker from the legacy upgrade signal; fresh installs get it from
+# mark_setup_complete in setup/so-functions.
+mark_setup_complete:
+ file.managed:
+ - name: /opt/so/state/setup-complete
+ - replace: false
+ - makedirs: True
+{% if GLOBALS.is_manager %}
+ - onlyif: "grep -qx 'startup_states: highstate' /etc/salt/minion"
+{% endif %}
+ - require_in:
+ - service: so_boot_highstate_service
+
# Only enable once setup is complete. Until then the gate file is missing and
-# the unit's own ConditionPathExists would no-op it anyway -- this just keeps
-# `systemctl is-enabled` honest for the sync_es_users gate.
+# the unit's own ConditionPathExists would no-op it anyway.
so_boot_highstate_service:
service.enabled:
- name: so-boot-highstate.service
diff --git a/salt/salt/minion/init.sls b/salt/salt/minion/init.sls
index fa94ec7be..a608788c2 100644
--- a/salt/salt/minion/init.sls
+++ b/salt/salt/minion/init.sls
@@ -87,27 +87,15 @@ set_log_levels:
# so-boot-highstate.service (managed in salt.minion.boot_highstate), which
# runs once per system boot only. Strip the line from /etc/salt/minion on
# upgrade; both the commented and uncommented forms historically existed.
+# Ordered after mark_setup_complete (salt.minion.boot_highstate); the manager
+# gate there greps for this line, so it must run before we delete it.
remove_startup_states:
file.line:
- name: /etc/salt/minion
- match: 'startup_states: highstate'
- mode: delete
-
-# Upgrade-path bridge: systems that already passed setup under the old gate
-# (`grep -x 'startup_states: highstate' /etc/salt/minion`) get a /opt/so/state/setup-complete
-# marker so so-boot-highstate.service can be enabled and the so-user_sync cron
-# in sync_es_users.sls keeps installing. Setup-in-progress systems instead get
-# the marker from `mark_setup_complete` in setup/so-functions at the right
-# moment. `replace: false` means we never overwrite a marker once written.
-mark_setup_complete_for_upgrades:
- file.managed:
- - name: /opt/so/state/setup-complete
- - replace: false
- - makedirs: True
- - onlyif: "grep -qx 'startup_states: highstate' /etc/salt/minion"
- - require_in:
- - file: remove_startup_states
- - service: so_boot_highstate_service
+ - require:
+ - file: mark_setup_complete
{% endif %}
diff --git a/salt/setup/virt/setSalt.sls b/salt/setup/virt/setSalt.sls
index 59ab9e1e3..41496fea5 100644
--- a/salt/setup/virt/setSalt.sls
+++ b/salt/setup/virt/setSalt.sls
@@ -8,6 +8,15 @@ set_role_grain:
- name: role
- value: so-{{ grains.id.split("_") | last }}
+# salt-cloud guests never run so-setup, so nothing else marks them setup-complete.
+# Replaces the 'startup_states: highstate' line this state used to append. No
+# GLOBALS import -- this runs before the guest's pillars exist.
+mark_setup_complete_vm_guest:
+ file.managed:
+ - name: /opt/so/state/setup-complete
+ - replace: false
+ - makedirs: True
+
enable_salt_minion:
service.enabled:
- name: salt-minion
diff --git a/salt/soc/defaults.yaml b/salt/soc/defaults.yaml
index 4bb1b0f8b..c5e14d31b 100644
--- a/salt/soc/defaults.yaml
+++ b/salt/soc/defaults.yaml
@@ -2671,7 +2671,7 @@ soc:
# The id (UUIDv4) is pregenerated and can safely be used.
# Click "Convert" to convert the Sigma rule to use Security Onion field mappings within an EQL query
#
- # Rule Creation Guide: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide
+ # Rule Creation Guide: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-High%E2%80%90Level-Guide
# Logsources: https://sigmahq.io/docs/basics/log-sources.html
title: 'A Short Capitalized Title With Less Than 50 Characters'
@@ -2683,7 +2683,7 @@ soc:
references:
- 'https://local.invalid'
author: '@SecurityOnion'
- date: 'YYYY/MM/DD'
+ date: '[today]'
tags:
- detection.threat_hunting
- attack.technique_id
diff --git a/salt/stig/enabled.sls b/salt/stig/enabled.sls
index 91aae7069..b53256e04 100644
--- a/salt/stig/enabled.sls
+++ b/salt/stig/enabled.sls
@@ -65,6 +65,15 @@ run_remediate:
- success_retcodes:
- 2
+# Elastic Agent docker image chowns its directory to the running UID but does not
+# chown the elastic-agent launcher symlink. fs.protected_symlinks=1 then prevents
+# non-root users from following that launcher symlink.
+{# OSCAP rule id: xccdf_org.ssgproject.content_rule_sysctl_fs_protected_symlinks #}
+fs.protected_symlinks:
+ sysctl.present:
+ - value: 0
+ - config: /etc/sysctl.conf
+
{# OSCAP rule id: xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction #}
disable_ctrl_alt_del_action:
file.replace:
diff --git a/salt/stig/files/sos-oscap.xml b/salt/stig/files/sos-oscap.xml
index aa5b2ed31..9fc8dacf8 100644
--- a/salt/stig/files/sos-oscap.xml
+++ b/salt/stig/files/sos-oscap.xml
@@ -1601,7 +1601,7 @@ DISA STIG for Oracle Linux 9 V1R3.
-
+
@@ -2202,7 +2202,7 @@ standard DISA STIG for Oracle Linux 9 profile.
-
+
diff --git a/setup/so-setup b/setup/so-setup
index 896505ba5..79fcabfeb 100755
--- a/setup/so-setup
+++ b/setup/so-setup
@@ -833,6 +833,7 @@ if ! [[ -f $install_opt_file ]]; then
check_sos_appliance
drop_install_options
hypervisor_local_states
+ mark_setup_complete
verify_setup
fi