diff --git a/salt/manager/sync_es_users.sls b/salt/manager/sync_es_users.sls index 29b090e18..8fc9c6bb4 100644 --- a/salt/manager/sync_es_users.sls +++ b/salt/manager/sync_es_users.sls @@ -31,11 +31,13 @@ sync_es_users: - http: wait_for_kratos - file: so-user.lock # require so-user.lock file to be missing -# we dont want this added too early in setup, so we add the onlyif to verify 'startup_states: highstate' -# is in the minion config. That line is added before the final highstate during setup +# we dont want this added too early in setup, so the onlyif gates on the +# /opt/so/state/setup-complete marker. The marker is written by +# mark_setup_complete in setup/so-functions just before the final setup +# highstate (and by an upgrade-path state for systems set up under the old gate). so-user_sync: cron.present: - user: root - name: 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin /usr/sbin/so-user sync &>> /opt/so/log/soc/sync.log' - identifier: so-user_sync - - onlyif: "grep -x 'startup_states: highstate' /etc/salt/minion" + - onlyif: "test -e /opt/so/state/setup-complete" diff --git a/salt/salt/minion/boot_highstate.sls b/salt/salt/minion/boot_highstate.sls new file mode 100644 index 000000000..eb2596dad --- /dev/null +++ b/salt/salt/minion/boot_highstate.sls @@ -0,0 +1,31 @@ +# 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. + +# 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 +# config, which fired on every salt-minion service restart (causing a redundant +# highstate whenever a highstate itself restarted salt-minion). + +include: + - systemd.reload + +so_boot_highstate_unit_file: + file.managed: + - name: /etc/systemd/system/so-boot-highstate.service + - source: salt://salt/service/so-boot-highstate.service + - onchanges_in: + - module: systemd_reload + +# 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. +so_boot_highstate_service: + service.enabled: + - name: so-boot-highstate.service + - onlyif: test -e /opt/so/state/setup-complete + - require: + - file: so_boot_highstate_unit_file + - module: systemd_reload diff --git a/salt/salt/minion/init.sls b/salt/salt/minion/init.sls index eb7018aed..59dd0289c 100644 --- a/salt/salt/minion/init.sls +++ b/salt/salt/minion/init.sls @@ -17,6 +17,7 @@ include: - repo.client - salt.mine_functions - salt.minion.service_file + - salt.minion.boot_highstate {% if GLOBALS.is_manager %} - ca.signing_policy {% endif %} @@ -80,11 +81,33 @@ set_log_levels: - "log_level: info" - "log_level_logfile: info" -enable_startup_states: - file.uncomment: +# startup_states: highstate caused a full highstate to run on every +# salt-minion service start, including the restart triggered when a highstate +# itself modified the minion config (beacons, mine, unit file). Replaced by +# 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. +remove_startup_states: + file.line: - name: /etc/salt/minion - - regex: '^startup_states: highstate$' - - unless: pgrep so-setup + - 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 {% endif %} diff --git a/salt/salt/service/so-boot-highstate.service b/salt/salt/service/so-boot-highstate.service new file mode 100644 index 000000000..cc8c6a1c6 --- /dev/null +++ b/salt/salt/service/so-boot-highstate.service @@ -0,0 +1,14 @@ +[Unit] +Description=Security Onion boot-time highstate (runs once per boot) +After=salt-minion.service network-online.target docker.service +Wants=network-online.target docker.service +Requires=salt-minion.service +ConditionPathExists=/opt/so/state/setup-complete + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/salt-call state.highstate -l info queue=True + +[Install] +WantedBy=multi-user.target diff --git a/salt/setup/virt/setSalt.sls b/salt/setup/virt/setSalt.sls index 69c8795de..59ab9e1e3 100644 --- a/salt/setup/virt/setSalt.sls +++ b/salt/setup/virt/setSalt.sls @@ -8,11 +8,6 @@ set_role_grain: - name: role - value: so-{{ grains.id.split("_") | last }} -set_highstate: - file.append: - - name: /etc/salt/minion - - text: 'startup_states: highstate' - enable_salt_minion: service.enabled: - name: salt-minion diff --git a/setup/so-functions b/setup/so-functions index c94b8eee7..5ce9a8fdc 100755 --- a/setup/so-functions +++ b/setup/so-functions @@ -539,16 +539,19 @@ configure_minion() { " x509_v2: true"\ "log_level: info"\ "log_level_logfile: info"\ - "log_file: /opt/so/log/salt/minion"\ - "#startup_states: highstate" >> "$minion_config" + "log_file: /opt/so/log/salt/minion" >> "$minion_config" } -checkin_at_boot() { - local minion_config=/etc/salt/minion +mark_setup_complete() { + # Writes the setup-complete marker. Salt's so-boot-highstate.service + # (boot-time oneshot) and the so-user_sync cron gate in + # salt/manager/sync_es_users.sls both key off this file. + local marker=/opt/so/state/setup-complete - info "Enabling checkin at boot" - sed -i 's/#startup_states: highstate/startup_states: highstate/' "$minion_config" + info "Marking setup as complete" + mkdir -p "$(dirname "$marker")" + touch "$marker" } check_requirements() { diff --git a/setup/so-setup b/setup/so-setup index 6c77e781c..c11d287eb 100755 --- a/setup/so-setup +++ b/setup/so-setup @@ -792,7 +792,7 @@ if ! [[ -f $install_opt_file ]]; then error "Failed to run so-elastic-fleet-setup" fail_setup fi - checkin_at_boot + mark_setup_complete set_initial_firewall_access initialize_elasticsearch_indices "so-case so-casehistory so-assistant-session so-assistant-chat" # run a final highstate before enabling scheduled highstates.