Move global.push config to salt.auto_apply

The active-push tunables (enabled, highstate_interval_hours, debounce_seconds,
drain_interval, batch, batch_wait) described how Salt auto-applies changes, not
general grid config, so relocate them from the global namespace to a new
salt.auto_apply settings module.

- Add salt/salt/{defaults.yaml,auto_apply.map.jinja,soc_salt.yaml,adv_salt.yaml}.
  auto_apply.map.jinja is a dedicated, side-effect-free merge map (the existing
  salt/salt/map.jinja dereferences pillar.host.mainint at import time).
- Remove the push blocks from salt/global/{defaults,soc_global}.yaml.
- Register salt.soc_salt/salt.adv_salt in pillar/top.sls; seed the local pillar
  stubs for fresh installs (make_some_dirs) and upgrades (ensure_salt_local_pillar
  in soup, wired into up_to_3.2.0).
- Repoint all consumers: GLOBALMERGED.push.* -> AUTOAPPLY.* (schedule, salt
  master, manager beacons, beacons_pushstate, orch.push_batch) and
  pillar.get('global:push...') -> 'salt:auto_apply...' (push reactors,
  so-push-drainer).
- Add a salt: fleetwide-highstate entry to pillar_push_map.yaml so edits keep
  applying immediately, matching the prior global-namespace behavior.
This commit is contained in:
Josh Patterson
2026-06-24 15:17:48 -04:00
parent 61aa963a2d
commit dfdb1fbaeb
19 changed files with 101 additions and 67 deletions
+2
View File
@@ -3,6 +3,8 @@ base:
- ca
- global.soc_global
- global.adv_global
- salt.soc_salt
- salt.adv_salt
- docker.soc_docker
- docker.adv_docker
- influxdb.token
-7
View File
@@ -1,10 +1,3 @@
global:
pcapengine: SURICATA
pipeline: REDIS
push:
enabled: true
highstate_interval_hours: 2
debounce_seconds: 30
drain_interval: 15
batch: '25%'
batch_wait: 15
-37
View File
@@ -59,41 +59,4 @@ global:
description: Allows use of Endgame with Security Onion. This feature requires a license from Endgame.
global: True
advanced: True
push:
enabled:
description: Master kill-switch for the active push feature. When disabled, rule and pillar changes are picked up at the next scheduled highstate instead of being pushed immediately.
forcedType: bool
helpLink: push
global: True
highstate_interval_hours:
description: How often every minion in the grid runs a scheduled state.highstate, in hours. Lower values keep minions closer in sync at the cost of more load; higher values reduce load but increase worst-case latency for non-pushed changes. The salt-minion health check restarts a minion if its last highstate is older than this value plus one hour.
forcedType: int
helpLink: push
global: True
advanced: True
debounce_seconds:
description: Trailing-edge debounce window in seconds. A push intent must be quiet for this long before the drainer dispatches. Rapid bursts of edits within this window coalesce into one dispatch.
forcedType: int
helpLink: push
global: True
advanced: True
drain_interval:
description: How often the push drainer checks for ready intents, in seconds. Small values lower dispatch latency at the cost of more background work on the manager.
forcedType: int
helpLink: push
global: True
advanced: True
batch:
description: "Host batch size for push orchestrations. A number (e.g. '10') or a percentage (e.g. '25%'). Limits how many minions run the push state at once so large fleets don't thundering-herd."
helpLink: push
global: True
advanced: True
regex: '^([0-9]+%?)$'
regexFailureMessage: Enter a whole number or a whole-number percentage (e.g. 10 or 25%).
batch_wait:
description: Seconds to wait between host batches in a push orchestration. Gives the fleet time to breathe between waves.
forcedType: int
helpLink: push
global: True
advanced: True
+2 -2
View File
@@ -1,10 +1,10 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'global/map.jinja' import GLOBALMERGED %}
{% from 'salt/auto_apply.map.jinja' import AUTOAPPLY %}
include:
- salt.minion
{% if GLOBALS.is_manager and GLOBALMERGED.push.enabled %}
{% if GLOBALS.is_manager and AUTOAPPLY.enabled %}
salt_beacons_pushstate:
file.managed:
- name: /etc/salt/minion.d/beacons_pushstate.conf
@@ -1,7 +1,7 @@
{% from 'global/map.jinja' import GLOBALMERGED %}
{% from 'salt/auto_apply.map.jinja' import AUTOAPPLY %}
beacons:
pillar_db:
- interval: {{ GLOBALMERGED.push.drain_interval }}
- interval: {{ AUTOAPPLY.drain_interval }}
- disable_during_state_run: True
inotify:
- disable_during_state_run: True
+3 -3
View File
@@ -60,9 +60,9 @@ def _make_logger():
def _load_push_cfg():
"""Read the global:push pillar subtree via salt-call. Returns a dict."""
"""Read the salt:auto_apply pillar subtree via salt-call. Returns a dict."""
caller = salt.client.Caller()
cfg = caller.cmd('pillar.get', 'global:push', {})
cfg = caller.cmd('pillar.get', 'salt:auto_apply', {})
return cfg if isinstance(cfg, dict) else {}
@@ -135,7 +135,7 @@ def main():
try:
push = _load_push_cfg()
except Exception:
log.exception('failed to read global:push pillar; aborting drain pass')
log.exception('failed to read salt:auto_apply pillar; aborting drain pass')
return 1
if not push.get('enabled', True):
+17
View File
@@ -690,6 +690,21 @@ ensure_postgres_local_pillar() {
chown -R socore:socore "$dir"
}
ensure_salt_local_pillar() {
# The salt.auto_apply settings (moved from global.push) are a new SOC settings
# module, so the new pillar/top.sls references salt.soc_salt / salt.adv_salt
# unconditionally. Managers upgrading from before this change have no
# /opt/so/saltstack/local/pillar/salt/ (make_some_dirs only runs at install
# time), so the stubs must be created here before salt-master restarts against
# the new top.sls.
echo "Ensuring salt local pillar stubs exist."
local dir=/opt/so/saltstack/local/pillar/salt
mkdir -p "$dir"
[[ -f "$dir/soc_salt.sls" ]] || touch "$dir/soc_salt.sls"
[[ -f "$dir/adv_salt.sls" ]] || touch "$dir/adv_salt.sls"
chown -R socore:socore "$dir"
}
ensure_postgres_secret() {
# On a fresh install, generate_passwords + secrets_pillar seed
# secrets:postgres_pass in /opt/so/saltstack/local/pillar/secrets.sls. That
@@ -851,6 +866,8 @@ kibana_backport_streams_index_template() {
}
up_to_3.2.0() {
ensure_salt_local_pillar
fix_logstash_0013_lumberjack_pipeline_name
pin_elasticsearch_data_retention_method
+3 -3
View File
@@ -1,7 +1,7 @@
{% from 'global/map.jinja' import GLOBALMERGED %}
{% from 'salt/auto_apply.map.jinja' import AUTOAPPLY %}
{% set actions = salt['pillar.get']('actions', []) %}
{% set BATCH = GLOBALMERGED.push.batch %}
{% set BATCH_WAIT = GLOBALMERGED.push.batch_wait %}
{% set BATCH = AUTOAPPLY.batch %}
{% set BATCH_WAIT = AUTOAPPLY.batch_wait %}
{% for action in actions %}
{% if action.get('highstate') %}
+11
View File
@@ -185,6 +185,17 @@ registry:
- state: registry
tgt: 'G@role:so-eval or G@role:so-import or G@role:so-manager or G@role:so-managerhype or G@role:so-managersearch or G@role:so-standalone'
# salt: fanout to a fleetwide highstate. The salt.auto_apply settings tune the
# push pipeline itself (enabled, debounce/drain intervals, batch sizing) and the
# per-minion highstate schedule; they are consumed by the manager's schedule,
# beacons, and master reactor config as well as every minion's highstate
# schedule, so a targeted re-apply isn't meaningful. A salt audit row only fires
# for SOC-driven salt.auto_apply edits -- salt version bumps go through soup, not
# SOC, so they never reach this map.
salt:
- highstate: True
tgt: '*'
# sensoroni: universal.
sensoroni:
- state: sensoroni
+2 -2
View File
@@ -59,9 +59,9 @@ def _load_push_map():
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'global:push:enabled', True))
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_pillar: pillar.get global:push:enabled failed, assuming enabled')
LOG.exception('push_pillar: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
+2 -2
View File
@@ -35,9 +35,9 @@ def _sensor_compound():
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'global:push:enabled', True))
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_strelka: pillar.get global:push:enabled failed, assuming enabled')
LOG.exception('push_strelka: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
+2 -2
View File
@@ -34,9 +34,9 @@ def _sensor_compound_plus_import():
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'global:push:enabled', True))
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_suricata: pillar.get global:push:enabled failed, assuming enabled')
LOG.exception('push_suricata: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
View File
+2
View File
@@ -0,0 +1,2 @@
{% import_yaml 'salt/defaults.yaml' as SALT_DEFAULTS %}
{% set AUTOAPPLY = salt['pillar.get']('salt:auto_apply', SALT_DEFAULTS.salt.auto_apply, merge=True) %}
+8
View File
@@ -0,0 +1,8 @@
salt:
auto_apply:
enabled: true
highstate_interval_hours: 2
debounce_seconds: 30
drain_interval: 15
batch: '25%'
batch_wait: 15
+2 -2
View File
@@ -10,7 +10,7 @@
# software that is protected by the license key."
{% from 'allowed_states.map.jinja' import allowed_states %}
{% from 'global/map.jinja' import GLOBALMERGED %}
{% from 'salt/auto_apply.map.jinja' import AUTOAPPLY %}
{% if sls in allowed_states %}
include:
@@ -65,7 +65,7 @@ engines_config:
- name: /etc/salt/master.d/engines.conf
- source: salt://salt/files/engines.conf
{% if GLOBALMERGED.push.enabled %}
{% if AUTOAPPLY.enabled %}
reactor_pushstate_config:
file.managed:
- name: /etc/salt/master.d/reactor_pushstate.conf
+38
View File
@@ -0,0 +1,38 @@
salt:
auto_apply:
enabled:
description: Master kill-switch for the active push feature. When disabled, rule and pillar changes are picked up at the next scheduled highstate instead of being pushed immediately.
forcedType: bool
helpLink: push
global: True
highstate_interval_hours:
description: How often every minion in the grid runs a scheduled state.highstate, in hours. Lower values keep minions closer in sync at the cost of more load; higher values reduce load but increase worst-case latency for non-pushed changes. The salt-minion health check restarts a minion if its last highstate is older than this value plus one hour.
forcedType: int
helpLink: push
global: True
advanced: True
debounce_seconds:
description: Trailing-edge debounce window in seconds. A push intent must be quiet for this long before the drainer dispatches. Rapid bursts of edits within this window coalesce into one dispatch.
forcedType: int
helpLink: push
global: True
advanced: True
drain_interval:
description: How often the push drainer checks for ready intents, in seconds. Small values lower dispatch latency at the cost of more background work on the manager.
forcedType: int
helpLink: push
global: True
advanced: True
batch:
description: "Host batch size for push orchestrations. A number (e.g. '10') or a percentage (e.g. '25%'). Limits how many minions run the push state at once so large fleets don't thundering-herd."
helpLink: push
global: True
advanced: True
regex: '^([0-9]+%?)$'
regexFailureMessage: Enter a whole number or a whole-number percentage (e.g. 10 or 25%).
batch_wait:
description: Seconds to wait between host batches in a push orchestration. Gives the fleet time to breathe between waves.
forcedType: int
helpLink: push
global: True
advanced: True
+4 -4
View File
@@ -1,22 +1,22 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'global/map.jinja' import GLOBALMERGED %}
{% from 'salt/auto_apply.map.jinja' import AUTOAPPLY %}
highstate_schedule:
schedule.present:
- function: state.highstate
- hours: {{ GLOBALMERGED.push.highstate_interval_hours }}
- hours: {{ AUTOAPPLY.highstate_interval_hours }}
- maxrunning: 1
{% if not GLOBALS.is_manager %}
- splay: 1800
{% endif %}
{% if GLOBALS.is_manager and GLOBALMERGED.push.enabled %}
{% if GLOBALS.is_manager and AUTOAPPLY.enabled %}
push_drain_schedule:
schedule.present:
- function: cmd.run
- job_args:
- /usr/sbin/so-push-drainer
- seconds: {{ GLOBALMERGED.push.drain_interval }}
- seconds: {{ AUTOAPPLY.drain_interval }}
- maxrunning: 1
- return_job: False
{% elif GLOBALS.is_manager %}
+1 -1
View File
@@ -1432,7 +1432,7 @@ make_some_dirs() {
mkdir -p $local_salt_dir/salt/firewall/portgroups
mkdir -p $local_salt_dir/salt/firewall/ports
for THEDIR in bpf elasticsearch ntp firewall redis backup influxdb postgres strelka sensoroni soc docker zeek suricata nginx telegraf logstash soc manager kratos hydra idh elastalert stig global kafka versionlock hypervisor vm; do
for THEDIR in bpf elasticsearch ntp firewall redis backup influxdb postgres strelka sensoroni soc docker zeek suricata nginx telegraf logstash soc manager kratos hydra idh elastalert stig global salt kafka versionlock hypervisor vm; do
mkdir -p $local_salt_dir/pillar/$THEDIR
touch $local_salt_dir/pillar/$THEDIR/adv_$THEDIR.sls
touch $local_salt_dir/pillar/$THEDIR/soc_$THEDIR.sls