mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-10-07 23:14:48 +02:00
so-telegraf mounted /var/run/docker.sock and joined the host docker group on every node type. The :ro flag blocks write() to the inode, not connect() plus HTTP over the socket, so any code execution inside the container could reach POST /containers/create with Privileged:true and become root on the host. No telegraf script used the socket; the only consumer was the native [[inputs.docker]] plugin, and group_add 920 existed solely to feed it. Container metrics now come from so-container-stats, a collector that runs on the host from cron and writes influx line protocol to a file telegraf already had mounted. This is the pattern so-status, so-raid-status and so-elasticagent-status already use, so the privileged docker access stays on the host side where root cron already ran it. The collector runs as somon, a service account in the docker group with no login shell and a locked password, rather than root. Docker group membership is still root-equivalent on the host, so this is defense in depth rather than a privilege boundary. The telegraf scripts were root:939 mode 770, letting socore rewrite them for code execution inside the container; they are now 750, which still allows the read and execute telegraf needs. The container also ran with no group, giving it gid 0, and now runs as 939:939. That alone would have broken lasthighstate.sh, which reached /opt/so/log/salt only via the root group and could not tell an unreadable file from a missing one, so it silently reported a 56 year highstate age. Only the lasthighstate file is bind mounted now, and the script tests readability instead of existence. Everything inputs.docker collected beyond the five fields the shipped dashboards query is available per stat under telegraf:container_stats, annotated for SOC so an operator can enable it without editing files. Defaults reproduce the previous output exactly. With every stat enabled the emitted field set matches what inputs.docker wrote, verified by running the plugin against the live socket and diffing: 53 fields, no type mismatches, no field present on one side only. Two deliberate differences: max_usage carries the real cgroup peak where the daemon reports 0 on cgroup v2, and host-network containers emit no docker_container_net row, matching inputs.docker. Docker label tags are not restored, since nothing queries them and they cost significant cardinality. so-status, the influxdb size cron, so-elasticagent-status, so-raid-status and so-common-status-check truncated their output in place while telegraf read it, so telegraf periodically saw an empty file and logged a parse error or emitted empty values. They now write aside and rename. Measured on a live manager, the old so-status cron left status.log empty for 215 of 10997 reads. Tested on a fresh install, a converted grid and a 3.0 upgrade.
288 lines
6.8 KiB
YAML+Jinja
288 lines
6.8 KiB
YAML+Jinja
{% from 'allowed_states.map.jinja' import allowed_states %}
|
|
{% if sls in allowed_states %}
|
|
|
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
|
|
|
include:
|
|
- common.grains
|
|
- common.packages
|
|
{% if GLOBALS.role in GLOBALS.manager_roles %}
|
|
- manager.elasticsearch # needed for elastic_curl_config state
|
|
- manager.kibana
|
|
{% endif %}
|
|
|
|
net.core.wmem_default:
|
|
sysctl.present:
|
|
- value: 26214400
|
|
|
|
# Users are not a fan of console messages
|
|
kernel.printk:
|
|
sysctl.present:
|
|
- value: "3 4 1 3"
|
|
|
|
# Add socore Group
|
|
socoregroup:
|
|
group.present:
|
|
- name: socore
|
|
- gid: 939
|
|
|
|
# Add socore user
|
|
socore:
|
|
user.present:
|
|
- uid: 939
|
|
- gid: 939
|
|
- home: /opt/so
|
|
- createhome: True
|
|
- shell: /bin/bash
|
|
|
|
soconfperms:
|
|
file.directory:
|
|
- name: /opt/so/conf
|
|
- user: 939
|
|
- group: 939
|
|
- dir_mode: 770
|
|
|
|
sostatusconf:
|
|
file.directory:
|
|
- name: /opt/so/conf/so-status
|
|
- user: 939
|
|
- group: 939
|
|
- dir_mode: 770
|
|
|
|
so-status.conf:
|
|
file.touch:
|
|
- name: /opt/so/conf/so-status/so-status.conf
|
|
- unless: ls /opt/so/conf/so-status/so-status.conf
|
|
|
|
socore_opso_perms:
|
|
file.directory:
|
|
- name: /opt/so
|
|
- user: 939
|
|
- group: 939
|
|
|
|
so_log_perms:
|
|
file.directory:
|
|
- name: /opt/so/log
|
|
- dir_mode: 755
|
|
|
|
# Create a state directory
|
|
statedir:
|
|
file.directory:
|
|
- name: /opt/so/state
|
|
- user: 939
|
|
- group: 939
|
|
- makedirs: True
|
|
|
|
salttmp:
|
|
file.directory:
|
|
- name: /opt/so/tmp
|
|
- user: 939
|
|
- group: 939
|
|
- makedirs: True
|
|
|
|
# VIM config
|
|
vimconfig:
|
|
file.managed:
|
|
- name: /root/.vimrc
|
|
- source: salt://common/files/vimrc
|
|
- replace: False
|
|
|
|
# Always keep these packages up to date
|
|
|
|
alwaysupdated:
|
|
pkg.latest:
|
|
- pkgs:
|
|
- openssl
|
|
- openssh-server
|
|
- bash
|
|
- skip_suggestions: True
|
|
|
|
# Set time to UTC
|
|
Etc/UTC:
|
|
timezone.system
|
|
|
|
# Sync curl configuration for Elasticsearch authentication
|
|
{% if GLOBALS.is_manager or GLOBALS.role in ['so-heavynode', 'so-searchnode'] %}
|
|
elastic_curl_config:
|
|
file.managed:
|
|
- name: /opt/so/conf/elasticsearch/curl.config
|
|
- source: salt://elasticsearch/curl.config
|
|
- mode: 600
|
|
- show_changes: False
|
|
- makedirs: True
|
|
{% if GLOBALS.role in GLOBALS.manager_roles %}
|
|
- require:
|
|
- file: elastic_curl_config_distributed
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
|
|
# A non-root owner here can chmod the directory and replace any script in it, including
|
|
# the root-owned ones. 555 is the mode the filesystem RPM ships; root ignores it anyway.
|
|
usr_sbin_perms:
|
|
file.directory:
|
|
- name: /usr/sbin
|
|
- user: root
|
|
- group: root
|
|
- mode: 555
|
|
|
|
common_sbin:
|
|
file.recurse:
|
|
- name: /usr/sbin
|
|
- source: salt://common/tools/sbin
|
|
- user: root
|
|
- group: root
|
|
- file_mode: 755
|
|
- show_changes: False
|
|
- require:
|
|
- file: usr_sbin_perms
|
|
{% if GLOBALS.role == 'so-heavynode' %}
|
|
- exclude_pat:
|
|
- so-pcap-import
|
|
{% endif %}
|
|
|
|
# Pin physical NIC names by MAC (run-once) so a kernel upgrade can't renumber the
|
|
# interfaces SO binds by name. The marker keeps it a one-time setup; an admin can
|
|
# pre-create the marker to opt out.
|
|
pin_nic_names:
|
|
cmd.run:
|
|
- name: /usr/sbin/so-nic-pin
|
|
- unless: 'test -e /opt/so/state/nic_names_pinned'
|
|
- require:
|
|
- file: common_sbin
|
|
- file: statedir
|
|
|
|
# Once a node is actually running UEK8, the stock EL9 (RHCK) kernel packages are dead weight.
|
|
# They can't be removed any earlier -- dnf protects the running kernel -- so the cleanup waits
|
|
# for the reboot, which makes the highstate the natural place to catch it: fresh installs
|
|
# reboot at the end of setup, and upgraded nodes reboot whenever the admin schedules it.
|
|
# so-kernel-upgrade --cleanup checks rpm before touching dnf, so this costs an rpm query on
|
|
# every highstate after the first pass. The package list lives in the script only, so there
|
|
# is nothing here to drift out of sync with it.
|
|
remove_stock_kernel:
|
|
cmd.run:
|
|
- name: /usr/sbin/so-kernel-upgrade --cleanup
|
|
- onlyif: 'uname -r | grep -qE "^6\.[0-9]+.*uek"'
|
|
- require:
|
|
- file: common_sbin
|
|
|
|
common_sbin_jinja:
|
|
file.recurse:
|
|
- name: /usr/sbin
|
|
- source: salt://common/tools/sbin_jinja
|
|
- user: root
|
|
- group: root
|
|
- file_mode: 755
|
|
- template: jinja
|
|
- show_changes: False
|
|
{% if GLOBALS.role == 'so-heavynode' %}
|
|
- exclude_pat:
|
|
- so-import-pcap
|
|
{% endif %}
|
|
|
|
so-status_script:
|
|
file.managed:
|
|
- name: /usr/sbin/so-status
|
|
- source: salt://common/tools/sbin/so-status
|
|
- user: root
|
|
- group: root
|
|
- mode: 755
|
|
|
|
{% if GLOBALS.is_sensor %}
|
|
# Add sensor cleanup
|
|
so-sensor-clean:
|
|
cron.present:
|
|
- name: /usr/sbin/so-sensor-clean
|
|
- identifier: so-sensor-clean
|
|
- user: root
|
|
- minute: '*'
|
|
- hour: '*'
|
|
- daymonth: '*'
|
|
- month: '*'
|
|
- dayweek: '*'
|
|
{% endif %}
|
|
|
|
# Create the status directory
|
|
sostatusdir:
|
|
file.directory:
|
|
- name: /opt/so/log/sostatus
|
|
- user: 0
|
|
- group: 0
|
|
- makedirs: True
|
|
|
|
sostatus_log:
|
|
file.managed:
|
|
- name: /opt/so/log/sostatus/status.log
|
|
- mode: 644
|
|
- replace: False
|
|
|
|
# Install sostatus check cron. This is used to populate Grid.
|
|
# telegraf reads status.log on the same minute boundary this runs, so write aside and rename
|
|
# rather than truncating the file it is reading
|
|
so-status_check_cron:
|
|
cron.present:
|
|
- name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log.tmp 2>&1; mv -f /opt/so/log/sostatus/status.log.tmp /opt/so/log/sostatus/status.log'
|
|
- identifier: so-status_check_cron
|
|
- user: root
|
|
- minute: '*/1'
|
|
- hour: '*'
|
|
- daymonth: '*'
|
|
- month: '*'
|
|
- dayweek: '*'
|
|
|
|
# This cronjob/script runs a check if the node needs restarted, but should be used for future status checks as well
|
|
common_status_check_cron:
|
|
cron.present:
|
|
- name: '/usr/sbin/so-common-status-check > /dev/null 2>&1'
|
|
- identifier: common_status_check
|
|
- user: root
|
|
- minute: '*/10'
|
|
|
|
remove_post_setup_cron:
|
|
cron.absent:
|
|
- name: 'PATH=$PATH:/usr/sbin salt-call state.highstate'
|
|
- identifier: post_setup_cron
|
|
|
|
{% if GLOBALS.role not in ['eval', 'manager', 'managersearch', 'standalone'] %}
|
|
|
|
soversionfile:
|
|
file.managed:
|
|
- name: /etc/soversion
|
|
- source: salt://common/files/soversion
|
|
- mode: 644
|
|
- template: jinja
|
|
|
|
{% endif %}
|
|
|
|
{% if GLOBALS.so_model and GLOBALS.so_model not in ['SO2AMI01', 'SO2AZI01', 'SO2GCI01'] %}
|
|
{% if GLOBALS.os == 'OEL' %}
|
|
# Install Raid tools
|
|
raidpkgs:
|
|
pkg.installed:
|
|
- skip_suggestions: True
|
|
- pkgs:
|
|
- securityonion-raidtools
|
|
- securityonion-megactl
|
|
{% endif %}
|
|
|
|
# Install raid check cron
|
|
so-raid-status:
|
|
cron.present:
|
|
- name: '/usr/sbin/so-raid-status > /dev/null 2>&1'
|
|
- identifier: so-raid-status
|
|
- user: root
|
|
- minute: '*/15'
|
|
- hour: '*'
|
|
- daymonth: '*'
|
|
- month: '*'
|
|
- dayweek: '*'
|
|
|
|
{% endif %}
|
|
{% else %}
|
|
|
|
{{sls}}_state_not_allowed:
|
|
test.fail_without_changes:
|
|
- name: {{sls}}_state_not_allowed
|
|
|
|
{% endif %}
|